Unity地图路径编辑器
发表于2019-01-28
游戏开发的过程中,我们经常需要让某些物体按照固定的路线来移动,这时候就需要提供给策划同学们一个路径编辑器来让他们充分发挥“想象力”……
先来看下最终效果:
下面来简单说下实现过程
制作路径点
首先制作路径点,每一个路径点要记录自己都连接到哪些点,我们用一个数组来记录,所以路径点的脚本应该是这样:
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; } }