Unity地图路径编辑器

发表于2019-01-28
评论0 8.7k浏览
游戏开发的过程中,我们经常需要让某些物体按照固定的路线来移动,这时候就需要提供给策划同学们一个路径编辑器来让他们充分发挥“想象力”……

先来看下最终效果:

下面来简单说下实现过程

制作路径点

首先制作路径点,每一个路径点要记录自己都连接到哪些点,我们用一个数组来记录,所以路径点的脚本应该是这样:
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;
    }
}

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引