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);  //可通行
                }
            }
        }
    }

}

编辑一张地图,保存成配置文件,并读取。效果以下

(注意射线检测的距离,以及层设置)

相关文章
相关标签/搜索