UnityAStar算法入门解析(A*算法寻路)

A*算法广泛用于MMORPG游戏的寻路功能,本人在B站Up主唐老湿的视频中学习了一段时间,总结出其原理和实现过程。
视频链接

公式:f(寻路消耗)= g(自身离起点距离)+ h(自身离终点距离)
原理:
将地图分成若干个方块(结点),从起点周围的8个结点找到不是位于地图边缘和障碍物的结点,放到开启列表中,再从开启列表中找出f值最小的结点,放入关闭列表中;重复执行该步骤,直到起点等于终点为止。
此时,关闭列表存放的就是最短路径,从最后一个结点开始判断自身有无父结点,有的话就存放到新的列表path中。最后反转path列表,得到的就是由起点到终点的结点路径。

图片解析:
UnityAStar算法入门解析(A*算法寻路)_第1张图片

下面是代码以及效果图片展示:
**AStarManager:**用于A*核心算法和主要逻辑的实现

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AStarManager
{
    private static AStarManager instance;
    public static AStarManager Instance
    {
        get
        {
            if (instance == null)
                instance = new AStarManager();
            return instance;
        }
    }

    /// 
    /// 地图相关的所有格子对象容器
    /// 
    public AStarNode[,] nodes;

    /// 
    /// 开启列表
    /// 
    private List<AStarNode> openList = new List<AStarNode>();

    /// 
    /// 关闭列表
    /// 
    private List<AStarNode> closeList = new List<AStarNode>();

    /// 
    /// 地图的宽高
    /// 
    private int mapW;
    private int mapH;

    /// 
    /// 初始化地图信息
    /// 
    /// 
    /// 
    public void InitMapInfo(int w,int h)
    {
        //记录地图的宽高
        this.mapW = w;
        this.mapH = h;
        //根据宽高,创建格子,障碍物可以随机生成
        nodes = new AStarNode[w, h];

        for (int i = 0; i < w; ++i)
        {
            for (int j = 0; j < h; ++j)
            {
                //随机生成的障碍物格子
                //实际开发中障碍物需要从配置文件中获取
                AStarNode node = new AStarNode(i, j, Random.Range(0, 100) > 20 ? E_Node_Type.Walk : E_Node_Type.Stop);
                nodes[i, j] = node;
            }
        }
    }

    /// 
    /// 寻路方法,提供给外界使用
    /// 
    /// 
    /// 
    /// 
    public List<AStarNode> FindPath(Vector2 startPos,Vector2 endPos)
    {
        //实际项目中,传入的点是坐标系的点(有可能是小数,需要除以每个格子的宽高
        //这里省略换算步骤,直接当作传进来的格子坐标

        //判断传入的两个点是否合法
        //1.要在地图范围内
        if(startPos.x<0||startPos.x>=mapW||
            startPos.y<0||startPos.y>=mapH||
            endPos.x < 0 || endPos.x >= mapW ||
            endPos.y < 0 || endPos.y >= mapH)
        {
            Debug.Log("开始或结束点在地图外面");
            return null;
        }
        //2.不是障碍物
        AStarNode start = nodes[(int)startPos.x, (int)startPos.y];
        AStarNode end = nodes[(int)endPos.x, (int)endPos.y];
        if (start.type == E_Node_Type.Stop || end.type == E_Node_Type.Stop)
        {
            Debug.Log("开始或结束点是障碍物");
            return null;
        }

        //清空列表(清空上一次的相关数据,避免影响这一次的寻路计算
        openList.Clear();
        closeList.Clear();

        //把开始点放入关闭列表中
        start.father = null;
        start.f = 0;
        start.g = 0;
        start.h = 0;
        closeList.Add(start);

        //如果不合法,直接返回null,意味着不能寻路

        //应该得到起点和终点对应的格子

        while (true)
        {
            //从起点开始,找周围的点,并放入开启列表中
            //左上
            FindNearlyNodeToOpenList(start.x - 1, start.y - 1, 1.4f, start, end);
            //上
            FindNearlyNodeToOpenList(start.x, start.y - 1, 1f, start, end);
            //右上
            FindNearlyNodeToOpenList(start.x + 1, start.y - 1, 1.4f, start, end);
            //左
            FindNearlyNodeToOpenList(start.x - 1, start.y, 1f, start, end);
            //右
            FindNearlyNodeToOpenList(start.x + 1, start.y, 1f, start, end);
            //左下
            FindNearlyNodeToOpenList(start.x - 1, start.y + 1, 1.4f, start, end);
            //下
            FindNearlyNodeToOpenList(start.x, start.y + 1, 1f, start, end);
            //右下
            FindNearlyNodeToOpenList(start.x + 1, start.y + 1, 1.4f, start, end);

            //判断死路
            if (openList.Count == 0)
            {
                Debug.Log("死路");
                return null;
            }
            //判断这些点是不是边界和阻挡,是否在开启和关闭列表,如果都不是,才放入开启列表

            //选出开启列表中,寻路消耗最小的点
            openList.Sort(SortOpenList);
            //放入关闭列表中,再从开启列表中移除该点
            closeList.Add(openList[0]);
            //开启列表的第一个点,变成下一次寻路的起点
            start = openList[0];
            openList.RemoveAt(0);

            //如果这个点已经是终点了,那么得到最终的值返回出去
            //如果这个点不是终点,那么继续寻路
            if (start == end)
            {
                //找完了,已经找到路径了
                List<AStarNode> path = new List<AStarNode>();

                //将路径存起来
                path.Add(end);
                while (end.father!=null)
                {
                    path.Add(end.father);
                    end = end.father;
                }

                //列表元素反转,得到由起点到终点的结点列表
                path.Reverse();
                return path;
            }
        }
        
    }

    /// 
    /// 排序函数,找到开启列表中f值最小的点
    /// 
    /// 
    /// 
    /// 
    private int SortOpenList(AStarNode a,AStarNode b)
    {
        if (a.f > b.f)
            return 1;
        else if (a.f == b.f)
            return 1;
        else
            return -1;
    }

    /// 
    /// 找到相邻的点并存放到开启列表中
    /// 
    /// 
    /// 
    /// g值
    /// 上一个点
    /// 终点
    private void FindNearlyNodeToOpenList(int x,int y,float g,AStarNode father,AStarNode end)
    {
        //判断有没有超过地图边界
        if (x < 0 || x >= mapW || y < 0 || y >= mapH)
            return;
        //在地图范围内,再去取点
        AStarNode node = nodes[x, y];
        //判断这些点是否是边界,是否是障碍物,是否在开启或关闭列表
        if (node == null || node.type == E_Node_Type.Stop ||
            openList.Contains(node) || closeList.Contains(node))
        {
            return;
        }

        //计算f值
            //1.记录父对象
        node.father = father;
            //2.计算g值:自身离起点的距离 = 父节点离起点的距离 + 自身离父节点的距离
        node.g = father.g + g;
            //3.计算h值
        node.h = Mathf.Abs(end.x - node.x) + Mathf.Abs(end.y - node.y);
        //计算寻路消耗f
        node.f = node.g + node.h;
        //如果通过上面的验证,就存到开启列表中
        openList.Add(node);
    }
}

**AStarNode:**用于定义每个结点的基本属性

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum E_Node_Type
{
    //可以走的地方
    Walk,
    //障碍物
    Stop,
}

public class AStarNode
{
    //格子对象的坐标
    public int x;
    public int y;

    //寻路消耗
    public float f;
    //离起点的距离
    public float g;
    //离终点的距离
    public float h;

    //父对象
    public AStarNode father;

    //格子类型
    public E_Node_Type type;

    /// 
    /// 构造函数,传入格子的坐标和类型
    /// 
    /// 
    /// 
    /// 
    public AStarNode(int x,int y,E_Node_Type type)
    {
        this.x = x;
        this.y = y;
        this.type = type;
    }
}

**TestAStar:**用来演示A*寻路的效果

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestAStar : MonoBehaviour
{using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestAStar : MonoBehaviour
{
    //实例化cube的起始点位置
    public int beginX = -3;
    public int beginY = -3;

    //每个cube的偏移量
    public int offsetX = 2;
    public int offsetY = 2;

    //地图的大小
    public int mapW = 5;
    public int mapH = 5;

    //结点的开始位置和结束位置
    private Vector2 beginPos = Vector2.right * -1;
    private Vector2 endPos = Vector2.right * -1;

    //存放最短路径
    List<AStarNode> list = new List<AStarNode>();

    //存放cube
    private Dictionary<string, GameObject> cubes = new Dictionary<string, GameObject>();
    // Start is called before the first frame update
    void Start()
    {
        //初始化地图信息
        AstarManager.Instance.InitMapInfo(mapW, mapH);

        //创建cube
        for (int i = 0; i < mapW; ++i)
        {
            for (int j = 0; j < mapH; ++j)
            {
                GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
                obj.transform.position = new Vector3(beginX + i * offsetX, beginY + j * offsetY, 0);
                obj.name = i + "_" + j;
                //将cube存放到字典中
                cubes.Add(obj.name, obj);
                //遍历结点列表,把是障碍物的结点变为红色
                AStarNode node = AstarManager.Instance.nodes[i, j];
                if (node.type == NodeType.Stop)
                {
                    obj.transform.GetComponent<MeshRenderer>().material.color = Color.red;
                }
            }
        }
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            RaycastHit hitInfo;
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if(Physics.Raycast(ray,out hitInfo, 100))
            {
                //将上一次的路径变回白色
                if (beginPos == Vector2.right * -1)
                {
                    if (list != null)
                    {
                        for (int i = 0; i < list.Count; ++i)
                        {
                            cubes[list[i].x + "_" + list[i].y].GetComponent<MeshRenderer>().material.color = Color.white;
                        }
                    }
                    //将点击的位置变成黄色
                    string[] strs = hitInfo.collider.gameObject.name.Split('_');
                    beginPos = new Vector2(int.Parse(strs[0]), int.Parse(strs[1]));
                    hitInfo.collider.gameObject.GetComponent<MeshRenderer>().material.color = Color.yellow;

                }

                else
                {
                    string[] strs = hitInfo.collider.gameObject.name.Split('_');
                    endPos = new Vector2(int.Parse(strs[0]), int.Parse(strs[1]));
                    //从结点管理器中获取最短路径
                    list = AstarManager.Instance.FindPath(beginPos, endPos);
                    //将路径变为绿色
                    if (list != null)
                    {
                        for (int i = 0; i < list.Count; ++i)
                        {
                            cubes[list[i].x + "_" + list[i].y].GetComponent<MeshRenderer>().material.color = Color.green;

                        }
                    }
                    //下次点击的时候更新起点位置
                    beginPos = Vector2.right * -1;
                }
            }
        }
    }
}


最终寻路效果图片:
UnityAStar算法入门解析(A*算法寻路)_第2张图片

你可能感兴趣的:(Unity)