A*寻路学习,附代码

这几天学习了一下A*寻路,这里记录一下代码,网上有很多详细的教程,代码里注释很详细。这里就不赘述了,直接上代码(萌新一枚,如有错还请指出!一起学习!)

首先是格子对象类

/// <summary>
/// 格子对象类
/// </summary>
public class AstarNode
{
    public int x;//格子坐标信息  X  Y
    public int y;

    public float f;//格子消耗信息
    public float g;
    public float h;

    public AstarNode parent;//格子父对象
    public NodeType nodeType;//格子类型

    public AstarNode(){}//构造函数
    public AstarNode(int x,int y,NodeType type)
    {
        this.x = x;
        this.y = y;
        this.nodeType = type;
    }

}
public enum NodeType
{
    Wlak,
    Stop,

}

然后是核心管理类

/// <summary>
/// A*寻路核心类
/// </summary>
public class AstarManager : MonoBehaviour
{
    private static AstarManager manager;
    public static AstarManager Manager
    {
        get
        {
            //if (manager == null)
            //{
            //    manager = this;
            //}
            return manager;
        }

    }
    private void Awake()
    {
        manager = this;
    }
    //地图最大高度
    public int maxHight;
    //地图最大宽度
    public int maxWidth;

    //获取场景网格父对象
    public Transform NodeParent;

    //存储格子的二维数组
    private AstarNode[,] nodes;
    //开启列表
    private List<AstarNode> openList = new List<AstarNode>();
    //关闭列表
    private List<AstarNode> closeList = new List<AstarNode>();

    //存储场景中的格子对象
    private List<RectTransform> nodeObj = new List<RectTransform>();

    /// <summary>
    /// 初始化地图大小
    /// </summary>
    /// <param name="w">宽</param>
    /// <param name="h">高</param>
    public void InitMap(int w, int h)
    {
        //获取场景中的格子父对象
        NodeParent = GameObject.Find("Canvas/BG").GetComponent<Transform>();
        //设置高
        maxHight = h;
        //设置宽
        maxWidth = w;

        //初始化node二维数组
        nodes = new AstarNode[w, h];

        for (int i = 0; i < w; i++)
        {
            for (int j = 0; j < h; j++)
            {
                //生成格子对象,并存入二维数组
                nodes[i, j] = new AstarNode(i, j, Random.Range(0, 100) < 20 ? NodeType.Stop : NodeType.Wlak);
                //------------------在场景中生成对象
                GameObject temp = Instantiate<GameObject>(Resources.Load<GameObject>("node"));
                temp.transform.SetParent(NodeParent);
                temp.transform.localScale = Vector3.one;

                //这里*105是因为格子在数值里是与场景里的格子大小不一样,如格子(1,1)位置,场景里一个对象大小我设置的是宽高都是100,
                //所以这里位置是宽高分别 *100,写105是想保持间隔
                ((temp.transform) as RectTransform).anchoredPosition = new Vector3(i * 105, j * 105, 0);
                nodeObj.Add(temp.GetComponent<RectTransform>());

                if (nodes[i, j].nodeType==NodeType.Stop)
                {
                    //如果格子是障碍 则改变颜色为黑色
                    temp.GetComponent<Image>().color = Color.black;
                }
                //------------------在场景中生成对象
            }
        }
    }

    /// <summary>
    /// 开始寻路
    /// </summary>
    /// <param name="startPos">起点</param>
    /// <param name="endPos">终点</param>
    /// <returns></returns>
    public List<AstarNode> FindPath(Vector2 startPos, Vector2 endPos)
    {
        //判断起点终点是否合法,这里是不能超过地图
        if (startPos.x >= maxWidth || startPos.y >= maxHight || startPos.x < 0 || startPos.y < 0
            || endPos.x >= maxWidth || endPos.y >= maxHight || endPos.x < 0 || endPos.y < 0)
        { Debug.Log("起点或终点可能已超过地图大小"); return null; }


        AstarNode starNode = nodes[(int)startPos.x, (int)startPos.y];
        AstarNode endNode = nodes[(int)endPos.x, (int)endPos.y];
        //这里拿到格子信息后继续判断格子是否合法,起点或终点格子不能是障碍物
        if (starNode.nodeType == NodeType.Stop || endNode.nodeType == NodeType.Stop)
        { Debug.Log("起点或终点可能是障碍物"); return null; }

        //此方法可能多次调用,所以要清空开启和关闭列表
        openList.Clear();
        closeList.Clear();

        //初始化起点信息,父对象与寻路消耗 并加入关闭列表
        starNode.parent = null;
        starNode.f = 0;
        starNode.h = 0;
        starNode.g = 0;
        closeList.Add(starNode);

        //-----------------------------这里是在场景中改变其颜色,标记出起点和终点的位置
        for (int i = 0; i < nodeObj.Count; i++)
        {
            
            if (new Vector2(starNode.x * 105, starNode.y * 105) == nodeObj[i].anchoredPosition)
            {
                nodeObj[i].GetComponent<Image>().color = Color.red;
                continue;
            }
            if (new Vector2(endNode.x * 105, endNode.y * 105) == nodeObj[i].anchoredPosition)
            {
                nodeObj[i].GetComponent<Image>().color = Color.blue;
                continue;
            }
        }
        //-----------------------------

        //死循环开始分别计算起点周围8个方位的格子,
        while (true)
        {
            //top
            CheckNode(starNode.x, starNode.y + 1, 1, starNode, endNode);
            //down
            CheckNode(starNode.x, starNode.y - 1, 1, starNode, endNode);
            //left
            CheckNode(starNode.x - 1, starNode.y, 1, starNode, endNode);
            //right
            CheckNode(starNode.x + 1, starNode.y, 1, starNode, endNode);

            //topleft
            CheckNode(starNode.x - 1, starNode.y - 1, 1.4f, starNode, endNode);
            //downleft
            CheckNode(starNode.x - 1, starNode.y + 1, 1.4f, starNode, endNode);
            //topright
            CheckNode(starNode.x + 1, starNode.y + 1, 1.4f, starNode, endNode);
            //downright
            CheckNode(starNode.x + 1, starNode.y - 1, 1.4f, starNode, endNode);

            //死路判断,当开启列表为空时,说明已经找完了地图上所有的格子,此时便是死路,所有退出循环
            if (openList.Count == 0)return null;
            
            //对开启列表进行排序,找到消耗最小的格子
            openList.Sort(SortOpenList);
            //将其加入关闭列表
            closeList.Add(openList[0]);
            //设置为下一个起点
            starNode = openList[0];
            //并从开启列表里 移除
            openList.RemoveAt(0);

            //结束判断,如果当前起点等于终点,说明已经寻路完成,开始导出正确路径
            if (starNode == endNode)
            {
                //这里的思路是新建一个格子列表,终点开始,向其中加入父对象,加入后变更终点对象,以此根据父对象找到正确的路径
                //切忌不可使用关闭列表里的格子当做路径,因为关闭列表里除了有正确的路径还有可能存在其它路径
                List<AstarNode> path = new List<AstarNode>();
                path.Add(endNode);
                while (endNode.parent != null)
                {
                    path.Add(endNode.parent);
                    endNode = endNode.parent;
                }
                //这里反转数组是因为我们是从最后一个开始找的,如:6 5 4 3 2 1 所以反转过来就是正确的路径
                path.Reverse();
                //----------------------------------------------这里是在场景中绘制出正确的路径
                for (int i = 0; i < path.Count; i++)
                {
                    for (int j = 0; j < nodeObj.Count; j++)
                    {
                        if (new Vector2(path[i].x * 105, path[i].y * 105) == nodeObj[j].anchoredPosition)
                        {
                            if (nodeObj[j].GetComponent<Image>().color == Color.white)
                            {
                                nodeObj[j].GetComponent<Image>().color = Color.yellow;
                                continue;
                            }

                        }
                    }
                }
                //----------------------------------------------
                //最后返回路径格子信息
                return path;
            }
        }



    }

    /// <summary>
    /// 查找某个点周围的格子消耗信息,并加入开启列表
    /// </summary>
    /// <param name="x">坐标X</param>
    /// <param name="y">坐标Y</param>
    /// <param name="g">拟消耗值</param>
    /// <param name="parent">其父对象</param>
    /// <param name="endNode">终点格子信息</param>
    public void CheckNode(int x, int y, float g, AstarNode parent, AstarNode endNode)
    {
        //node合法判断,是否超出边界
        if (x < 0 || x >= maxWidth ||
            y < 0 || y >= maxHight)
            return;


        AstarNode node = nodes[x, y];
        //node合法判断,是否为空,是否为障碍物,是否已经存在于Openlist或Closelist
        if (node == null || node.nodeType == NodeType.Stop ||
            openList.Contains(node) || closeList.Contains(node))
            return;
        //赋予父节点
        node.parent = parent;
        //计算自己的g
        node.g = parent.g + g;
        //计算自己的h 为终点的x,y减去自己的x,y相加,这里取绝对值,因为可能是负数
        node.h = Mathf.Abs(endNode.x - node.x) + Mathf.Abs(endNode.y - node.y);
        //计算最终消耗
        node.f = node.g + node.h;

        //加入开启列表
        openList.Add(node);
    }

    //重写一个比较方法,方便传入sort进行排序
    private int SortOpenList(AstarNode a, AstarNode b)
    {
        if (a.g > b.g)
        {
            return 1;
        }
        else if (a.g == b.g)
        {
            return 1;
        }
        else
        {
            return -1;
        }
    }
}

效果图
在这里插入图片描述
这里是会从2个障碍物直接穿过去 如下图,
按道理这种也应该是不存在的,后续修改了我会再上传的
在这里插入图片描述
--------------------------------------------------7.3更新--------------------------------
之前说到的斜着走的问题,现在解决了,用了比较笨的方法
在这里插入图片描述
在这里插入图片描述
在原来的节点判断的合法检测部分加上新的方法,并且多了一个bool参数,是否进行斜走判断,默认是false

/// <summary>
    /// 判断某格是否可以斜着走
    /// </summary>
    /// <param name="x">X轴</param>
    /// <param name="y">Y轴</param>
    /// <returns></returns>
    public bool CheckNodeType(int x, int y, AstarNode star)
    {
        //判断起点终点是否合法,这里是不能超过地图
        if (x >= maxWidth || y >= maxHight || x < 0 || y < 0
            || x >= maxWidth || y >= maxHight || x < 0 || y < 0)
        { Debug.Log("起点或终点可能已超过地图大小"); return false; }

        AstarNode starNode = nodes[x, y];

        //这里拿到格子信息后继续判断格子是否是障碍
        if (starNode.nodeType == NodeType.Stop) return false;
		//这里根据xy判断在那个斜方向,然后进行对应的判断
        if (x > star.x && y > star.y)
        {
            if (nodes[x - 1, y].nodeType == NodeType.Stop && nodes[x, y - 1].nodeType == NodeType.Stop)
            { return false; }
            else { return true; }
            //右上
        }

        if (x < star.x && y > star.y)
        {
            if (nodes[x + 1, y].nodeType == NodeType.Stop && nodes[x, y - 1].nodeType == NodeType.Stop)
            { return false; }
            else { return true; }

            //左上
        }

        if (x < star.x && y < star.y)
        {
            if (nodes[x + 1, y].nodeType == NodeType.Stop && nodes[x, y + 1].nodeType == NodeType.Stop)
            { return false; }
            else { return true; }

            //左下
        }

        if (x > star.x && y < star.y)
        {
            if (nodes[x - 1, y].nodeType == NodeType.Stop && nodes[x, y + 1].nodeType == NodeType.Stop)
            { return false; }
            else { return true; }
            //右下
        }
        return false;
    }

在这里插入图片描述
效果如上,不会再直接走过有障碍的斜角了

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Python的A*寻路算法是一种常用的路径规划算法。在游戏开发、机器人导航等领域中被广泛应用。 A*寻路算法的核心思想是在搜索过程中综合考虑每个节点的实际代价和启发式估计值,选择一个代价最低的路径。它基于图的搜索,首先将起始节点放入Open表中。然后,从Open表中选择具有最小总代价的节点进行扩展,将其加入到Closed表中,并根据启发式估计值更新该节点周围的可行节点的代价估计和路径。这个过程重复进行,直到找到终点节点或Open表为空。 在Python中实现A*寻路算法的关键是定义启发式函数和代价函数。启发式函数用于估计节点到目标节点的启发式代价,为了提高搜索效率,启发式函数一般要满足启发式函数值小于等于实际代价的性质。代价函数用于计算节点的实际代价,通常是节点的路径长度。 在实现过程中,我们可以使用优先队列来维护Open表,选择启发式估计值最小的节点进行扩展。同时,使用一个字典来记录每个节点的实际代价和路径信息。 最后,在找到终点节点后,可以通过逆向追踪记录的路径信息,从终点节点一直追踪到起始节点,得到最终的路径。 总之,Python的A*寻路算法是一种基于图搜索的路径规划算法,通过综合考虑节点的实际代价和启发式估计值,选择代价最低的路径。在实际应用中,合理定义启发式函数和代价函数是实现算法的关键。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值