Unity A* 尋路網格編輯工具(在Scene 視圖下編輯)

 

遊戲開發過程當中常常用到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);  //可通行
                }
            }
        }
    }

}

編輯一張地圖,保存成配置文件,並讀取。效果以下

(注意射線檢測的距離,以及層設置)

相關文章
相關標籤/搜索