游戏开发过程当中常常用到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); //可通行 } } } } }
编辑一张地图,保存成配置文件,并读取。效果以下
(注意射线检测的距离,以及层设置)