遊戲開發過程當中常常用到A*尋路算法,在美術製做完場景後,須要編輯行走區域和不可行走區域。能在Unity Scene場景中直接編輯,並導出數據文件,將會方便不少。算法
1 數據類編輯器
先定義一個網格數據類型,定義網格長,寬數量,以及實現序列化。values 中存放是否可通行的數據,0表示障礙,不可通行。ide
public class MapData { /// <summary> /// 網格寬度 /// </summary> public int mapLen = 0; /// <summary> /// 網格寬度 /// </summary> public int mapWidth = 0; /// <summary> /// 網格狀態數據 /// </summary> private List<int> values = new List<int>(); public int GetValue(int index) { if (index >= values.Count) { UnityTools.LogError("MapData GetValue index is not exsist! index->>"+ index.ToString()); return -1; } return values[index]; } public int GetValue(int x ,int y) { int index = x * mapWidth + y; GetValue(index); return values[index]; } public void AddValue(int value) { values.Add(value); } public void SetValue(int x,int y,int value) { int index = x * mapWidth + y; values[index] = value; } public int GetValuesCount() { return values.Count; } public void ClearValues() { values.Clear(); } public string Serialized() { string str = mapLen.ToString() + "," + mapWidth.ToString() + ","; for (int i = 0; i < values.Count; i++) { str += values[i].ToString() + ","; } return str; } public MapData DeSerialized(string data) { values.Clear(); string[] strList = data.Split(','); mapLen = int.Parse(strList[0]); mapWidth = int.Parse(strList[1]); for (int i = 2; i < strList.Length; i++) { if (!string.IsNullOrEmpty(strList[i])) values.Add(int.Parse(strList[i])); } return this; } }
2. 在場景中繪製網格this
使用Gizmos DrowCube 繪製方格,每一個方格表明實際Unity距離爲1,這裏爲了有邊界,size設置成0.8。不一樣的value用不一樣的顏色區分code
using System.Collections; using System.Collections.Generic; using UnityEngine; //using UnityEditor; [ExecuteInEditMode] public class MapGizmos : MonoBehaviour { /// <summary> /// 顯示Gird /// </summary> public bool bShowGizmos = true; public float height = 2; /// <summary> /// 格子大小 實際每一個格子是1的距離,要有間隔 這裏取O.8 /// </summary> float size = 0.8f; public LockStep.MapData mapData = null; private BoxCollider collider = null; //用來作射線檢測 private void Start() { //這裏設置Layer 根據具體項目設置 gameObject.layer = 31; collider = GetComponent<BoxCollider>(); if (collider == null) { collider = gameObject.AddComponent<BoxCollider>(); collider.size = new Vector3(1000, 0.1f, 1000); } } private void OnDrawGizmos() { if (mapData == null) return; if (bShowGizmos == false || Application.isEditor == false ) return; for (int i = 0; i < mapData.mapLen; i++) { for (int j = 0; j < mapData.mapWidth; j++) { Gizmos.color = mapData.GetValue(i,j) == 0 ? new Color(1, 0, 1, 0.5f) : new Color(1, 1, 1, 0.5f); Gizmos.DrawCube(new Vector3(i, height, j), new Vector3(size, 0.1f, size)); } } } }
3. 實現網格編輯器,編輯基本信息server
先設置合適的長寬(注:默認所有可通行,修改長寬,會重置數據),勾上編輯不可通行區域。blog
代碼以下:遊戲
using System.IO; using UnityEditor; using UnityEngine; using LockStep; public class MapEditor : EditorWindow { private static MapEditor instance = null; public static MapEditor Instance { get { if (instance == null) { instance = GetWindow<MapEditor>(typeof(MapEditor).Name); } return instance; } private set { } } /// <summary> /// 打開格子編輯器 /// </summary> private static MapGizmos mapGizmos = null; private static MapData mapData = new MapData(); private string mapFileName = "PathMap_Fight"; public bool editorGrid = false; public float radius = 4; [MenuItem("地圖編輯/尋路編輯")] static void Open() { if (instance == null) { instance = GetWindow<MapEditor>(typeof(MapEditor).Name); } instance.Show(); FindMapGizemos(); } static void FindMapGizemos() { GameObject obj = GameObject.Find("MapGizmos"); if (obj == null) { obj = new GameObject("MapGizmos"); mapGizmos = obj.AddComponent<MapGizmos>(); } else mapGizmos = obj.GetComponent<MapGizmos>(); mapGizmos.mapData = mapData; } /// <summary> /// 存文件 /// </summary> public void SaveData() { string text = mapData.Serialized(); string path = EditorUtility.SaveFilePanel("保存地圖數據", Application.dataPath + "/pathMap", mapFileName, "map"); if (!path.EndsWith(".map")) return; FileStream fs = File.Open(path, FileMode.Create, FileAccess.Write); byte[] myByte = System.Text.Encoding.UTF8.GetBytes(text); fs.Write(myByte, 0, myByte.Length); fs.Close(); } /// <summary> /// 讀文件 /// </summary> public void LoadData() { string path = EditorUtility.OpenFilePanel("讀取地圖數據", Application.dataPath + "/pathMap", "map"); if (!path.EndsWith(".map")) return; using (FileStream fsRead = new FileStream(path, FileMode.Open)) { int fsLen = (int)fsRead.Length; byte[] heByte = new byte[fsLen]; int r = fsRead.Read(heByte, 0, heByte.Length); string text = System.Text.Encoding.UTF8.GetString(heByte); text.Replace("\r\n", ""); Debug.Log(@text); mapData.DeSerialized(text); } mapGizmos.mapData = mapData; } void OnGUI() { if (mapGizmos == null) FindMapGizemos(); EditorFunc.GUILayout_LableFiled("尋路編輯", ""); EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("讀取地圖信息")) { LoadData(); } if (GUILayout.Button("存儲地圖信息")) { SaveData(); } EditorGUILayout.EndHorizontal(); mapFileName = EditorFunc.GUILayout_TextField("地圖文件名(不要使用中文)", mapFileName); mapData.mapLen = int.Parse(EditorFunc.GUILayout_TextField("地圖長度", mapData.mapLen.ToString())); mapData.mapWidth = int.Parse(EditorFunc.GUILayout_TextField("地圖寬度", mapData.mapWidth.ToString())); if (mapData.GetValuesCount() != mapData.mapLen * mapData.mapWidth) { mapData.ClearValues(); for (int i = 0; i < mapData.mapLen; i++) for (int j = 0; j < mapData.mapWidth; j++) { mapData.AddValue(1); } } editorGrid = GUILayout.Toggle(editorGrid, "編輯不可通行區域(Ctrl設置障礙,Alt關閉障礙)"); radius = float.Parse(EditorFunc.GUILayout_TextField("編輯半徑", radius.ToString())); } public void EditorObserver(float x, float y,int value) { int min_x = System.Convert.ToInt32(x - radius); if (min_x < 0) min_x = 0; int min_y = System.Convert.ToInt32(y - radius); if (min_y < 0) min_y = 0; int max_x = System.Convert.ToInt32(x + radius); if (max_x > mapData.mapLen) max_x = mapData.mapLen; int max_y = System.Convert.ToInt32(y + radius); if (max_y > mapData.mapWidth) max_y = mapData.mapWidth; if (radius == 0 && x > 0 && y > 0 && x < mapData.mapLen && y < mapData.mapWidth) { mapData.SetValue((int)x, (int)y, value); } for (int i = min_x; i < max_x; i++) for (int j = min_y; j < max_y; j++) { mapData.SetValue(i, j, value); } } }
4. 獲取Unity Scene 編輯器下的事件事件
經過鼠標射線獲取位置信息,Alt設置障礙,Ctrl移除障礙,MapEditor中能夠設障礙編輯半徑遊戲開發
using UnityEditor; using UnityEngine; [CustomEditor(typeof(MapGizmos))] public class MapGizmosEditor : Editor { public override void OnInspectorGUI() { base.OnInspectorGUI(); } public void OnSceneGUI() { if (MapEditor.Instance == null || MapEditor.Instance.editorGrid == false) return; Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition); RaycastHit hitInfo; if (Physics.Raycast(ray, out hitInfo, 2000,1 << 31)) //這裏設置層 { float x = hitInfo.point.x; float y = hitInfo.point.z; Event e = Event.current; if (e.isKey) { if (e.keyCode == KeyCode.LeftAlt) { MapEditor.Instance.EditorObserver(x, y,0); //設置障礙 } if (e.keyCode == KeyCode.LeftControl) { MapEditor.Instance.EditorObserver(x, y, 1); //可通行 } } } } }
編輯一張地圖,保存成配置文件,並讀取。效果以下
(注意射線檢測的距離,以及層設置)