游戏开发的过程中,我们经常需要让某些物体按照固定的路线来移动,这时候就需要提供给策划同学们一个路径编辑器来让他们充分发挥"想象力"。。。
先来看下最终效果:
下面来简单说下实现过程
制作路径点
首先制作路径点,每一个路径点要记录自己都连接到哪些点,我们用一个数组来记录,所以路径点的脚本应该是这样:
public class MapWayPoint : MonoBehaviour {
public List<GameObject> pointList;
}
然后制作预制体,随便创建一个空物体,或者任何你喜欢的物体,将这个脚本赋给这个物体,然后保存成为预制体即可。
绘制路径
接下来就是在这些路径点之间绘制出连接的带箭头的线:
创建一个脚本用来专门绘制这些线:MapDraw
这个脚本要引入命名空间:
#if UNITY_EDITOR
using UnityEditor;
#endif
这里要判断一下平台,因为在其它平台上是没有这个命名空间的
我们把绘制逻辑放在MonoBehaviour的OnDrawGizmos中:
public void OnDrawGizmos()
{
#if UNITY_EDITOR
Gizmos.color = Color.blue;
Handles.color = Color.blue;
//获取场景中全部道具
Object[] objects = Object.FindObjectsOfType(typeof(GameObject));
Dictionary<string, List<string>> post = new Dictionary<string, List<string>>();
foreach (GameObject sceneObject in objects){
if(sceneObject.name == "WayPoint"){
foreach (Transform child in sceneObject.transform)
{
MapWayPoint editor = child.GetComponent<MapWayPoint>();
if(editor != null){
for(int i = 0 ; i < editor.pointList.Count ; i ++){
if(editor.pointList[i] == null){
continue;
}
editor.transform.LookAt(editor.pointList[i].transform);
DrawLine(editor.transform,editor.pointList[i].transform,Vector3.Distance(editor.transform.position,editor.pointList[i].transform.position));
}
}
}
}
}
#endif
}
这里主要就是在场景中找到WayPoint对象,因为我们规定所有的路径点都要放到这个对象里边,所以我们遍历这个对象的所有子物体就找到了全部的路径点。然后遍历每个路径点所连接的所有目标点并调用绘制方法。
绘制方法:
public void DrawLine(Transform t,Transform t2,float dis){
#if UNITY_EDITOR
Handles.ArrowCap(0, t.position + (dis-Mathf.Min(1,dis)) * t.forward, t.rotation, Mathf.Min(1,dis));
Gizmos.DrawLine(t.position,t2.position);
#endif
}
这里调用了2个系统的绘制方法,第一个是绘制箭头,第二个是绘制线,绘制箭头时处理了一下,保证箭头只有1个长度,否则箭头太大了特别丑。
绘制部分就这些内容,接下来看下数据的存储和读取
存储和读取路径数据
存储和读取放在MEMapWayPoint脚本中
首先保存数据:
[MenuItem("关卡编辑器/保存路径")]
public static void SaveLevel(){
//获取场景中全部道具
Object[] objects = Object.FindObjectsOfType(typeof(GameObject));
Dictionary<string, List<string>> post = new Dictionary<string, List<string>>();
foreach (GameObject sceneObject in objects){
if(sceneObject.name == "WayPoint"){
foreach (Transform child in sceneObject.transform)
{
MapWayPoint editor = child.GetComponent<MapWayPoint>();
if(editor != null){
if(editor.pointList.Count <= 0) Debug.LogError("The point child is null : " + child.transform.position );
List<string > childlist = new List<string>();
for(int i = 0 ; i < editor.pointList.Count ; i ++){
if(editor.pointList[i] == null){
continue;
}
childlist.Add(GetPosString(editor.pointList[i].transform.position));
}
post.Add(GetPosString(editor.transform.position),childlist);
}
}
}
}
//保存文件
string filePath = GetDataFilePath(mapname + ".text");
byte[] byteArray = System.Text.Encoding.Default.GetBytes ( JsonMapper.ToJson(post) );
WriteByteToFile(byteArray,filePath );
Debug.Log(JsonMapper.ToJson(post));
}
这里和绘制的时候差不多,遍历WayPoint下的所有路径点,这里所有路径点的信息都是以字符串的形式保存的,并且只保存坐标信息。坐标信息使用Dictionary记录,key为当前点坐标,value为路径列表,最用把
Dictionary转为json字符串进行存储。
下面是读取数据:
[MenuItem("关卡编辑器/读取路径")]
public static void LoadLevel(){
List<GameObject> delarr = new List<GameObject>();
GameObject WayPoint = null;
foreach (GameObject sceneObject in Object.FindObjectsOfType(typeof(GameObject))){
if( sceneObject.name == "WayPoint"){
WayPoint = sceneObject;
break;
}
}
if(WayPoint == null){
GameObject tGO = new GameObject("WayPoint");
tGO.AddComponent<MapDraw>();
WayPoint = tGO;
}
//读取文件
byte[] pointData = ReadByteToFile(GetDataFilePath(mapname+".text"));
if(pointData == null){
return;
}
foreach (Transform child in WayPoint.transform){
delarr.Add(child.gameObject);
}
//删除旧物体
foreach(GameObject obj in delarr){
DestroyImmediate(obj);
}
string str = System.Text.Encoding.Default.GetString ( pointData );
Debug.Log(str);
Dictionary<string, List<string>> post = JsonMapper.ToObject<Dictionary<string, List<string>>>(str);
Dictionary<string,MapWayPoint> temp = new Dictionary<string,MapWayPoint>();
foreach (KeyValuePair<string, List<string>> pair in post)
{
List<string> list = pair.Value;
MapWayPoint obj = GetObj(WayPoint,temp,pair.Key);
for(int i = 0 ; i < list.Count ; i ++){
Debug.Log("add");
MapWayPoint child = GetObj(WayPoint,temp,list[i]);
obj.pointList.Add(child.gameObject);
}
}
// Debug.Log(JsonMapper.ToJson(post["0"][0]));
}
这里需要判断一下场景中是否存在WayPoint物理,不存在则创建一个,并挂上MapDraw脚本。数据直接用JsonMapper转会对象,然后在场景中生成物体就行了。
这里使用json字符串来进行数据存储并不是最好的方案,因为我做这个的时候,服务器端也需要使用到这个数据,所以才采用json,如果像我前篇给出的地图编辑器一样用二进制数据保存,服务器还要实现一套解析程序,太费事了。
下面给出MEMapWayPoint的完整代码:
public class MEMapWayPoint : Editor {
public static string mapname = "mapway";
[MenuItem("关卡编辑器/保存路径")]
public static void SaveLevel(){
// 场景路径
/*
string scenePath = AssetDatabase.GetAssetPath(selectObject);
Debug.Log("=====================================");
Debug.Log(sceneName + " path : " + scenePath );
// 打开这个关卡
EditorApplication.OpenScene(scenePath);
*/
//获取场景中全部道具
Object[] objects = Object.FindObjectsOfType(typeof(GameObject));
Dictionary<string, List<string>> post = new Dictionary<string, List<string>>();
foreach (GameObject sceneObject in objects){
if(sceneObject.name == "WayPoint"){
foreach (Transform child in sceneObject.transform)
{
MapWayPoint editor = child.GetComponent<MapWayPoint>();
if(editor != null){
if(editor.pointList.Count <= 0) Debug.LogError("The point child is null : " + child.transform.position );
List<string > childlist = new List<string>();
for(int i = 0 ; i < editor.pointList.Count ; i ++){
if(editor.pointList[i] == null){
continue;
}
childlist.Add(GetPosString(editor.pointList[i].transform.position));
}
post.Add(GetPosString(editor.transform.position),childlist);
}
}
}
}
//保存文件
string filePath = GetDataFilePath(mapname + ".text");
byte[] byteArray = System.Text.Encoding.Default.GetBytes ( JsonMapper.ToJson(post) );
WriteByteToFile(byteArray,filePath );
Debug.Log(JsonMapper.ToJson(post));
}
public static void Save(string name){
mapname = name;
SaveLevel();
}
//================================读取================================
[MenuItem("关卡编辑器/读取路径")]
public static void LoadLevel(){
List<GameObject> delarr = new List<GameObject>();
GameObject WayPoint = null;
foreach (GameObject sceneObject in Object.FindObjectsOfType(typeof(GameObject))){
if( sceneObject.name == "WayPoint"){
WayPoint = sceneObject;
break;
}
}
if(WayPoint == null){
GameObject tGO = new GameObject("WayPoint");
tGO.AddComponent<MapDraw>();
WayPoint = tGO;
}
//读取文件
byte[] pointData = ReadByteToFile(GetDataFilePath(mapname+".text"));
if(pointData == null){
return;
}
foreach (Transform child in WayPoint.transform){
delarr.Add(child.gameObject);
}
//删除旧物体
foreach(GameObject obj in delarr){
DestroyImmediate(obj);
}
string str = System.Text.Encoding.Default.GetString ( pointData );
Debug.Log(str);
Dictionary<string, List<string>> post = JsonMapper.ToObject<Dictionary<string, List<string>>>(str);
Dictionary<string,MapWayPoint> temp = new Dictionary<string,MapWayPoint>();
foreach (KeyValuePair<string, List<string>> pair in post)
{
List<string> list = pair.Value;
MapWayPoint obj = GetObj(WayPoint,temp,pair.Key);
for(int i = 0 ; i < list.Count ; i ++){
Debug.Log("add");
MapWayPoint child = GetObj(WayPoint,temp,list[i]);
obj.pointList.Add(child.gameObject);
}
}
// Debug.Log(JsonMapper.ToJson(post["0"][0]));
}
public static void Load(string name){
mapname = name;
LoadLevel();
}
public static string GetPosString(Vector3 pos){
return pos.x + "," + pos.y + "," + pos.z;
}
public static Vector3 GetPosByString(string pos){
Vector3 ret = Vector3.zero;
try{
string[] s = pos.Split(',');
ret.x = float.Parse(s[0]);
ret.y = float.Parse(s[1]);
ret.z = float.Parse(s[2]);
}catch(System.Exception e){
Debug.Log(e.Message);
}
return ret;
}
//加载路径点时,获取存档中的路径点,没有则创建
public static MapWayPoint GetObj(GameObject parent, Dictionary<string,MapWayPoint> temp,string name){
MapWayPoint obj;
if(temp.ContainsKey(name)){
obj = temp[name];
}else{
GameObject tempObj = Resources.Load("Prefabs/WayPoingUnit") as GameObject;
tempObj = (GameObject)Instantiate(tempObj);
tempObj.transform.parent = parent.transform;
tempObj.transform.position = GetPosByString(name);
obj = tempObj.GetComponent<MapWayPoint>();
temp.Add(name,obj);
}
return obj;
}
/// <summary>
/// 读取文件二进制数据 Reads the byte to file.
/// </summary>
/// <returns>The byte to file.</returns>
/// <param name="path">Path.</param>
public static byte[] ReadByteToFile(string path)
{
//如果文件不存在,就提示错误
if (!File.Exists(path))
{
Debug.Log("读取失败!不存在此文件");
return null;
}
FileStream fs = new FileStream(path, FileMode.Open);
BinaryReader br = new BinaryReader(fs);
byte[] data = br.ReadBytes((int)br.BaseStream.Length);
fs.Close();
fs.Dispose();
br.Close();
return data;
}
/// <summary>
/// 二进制数据写入文件 Writes the byte to file.
/// </summary>
/// <param name="data">Data.</param>
/// <param name="tablename">path.</param>
public static void WriteByteToFile(byte[] data, string path)
{
FileStream fs = new FileStream(path, FileMode.Create);
fs.Write(data, 0, data.Length);
fs.Close();
}
public static string GetDataFilePath(string filename)
{
return Application.dataPath + "/Resources/MapWayData/" + filename;
}
}