Unity A*寻路算法

1.节点

cs 复制代码
    //是否可以行走
    public bool isWalkable;
    //节点的位置
    public Vector2Int gridPos;
    //起点->当前节点(准确值)
    public float gCost;
    //当前节点->终点(预测值)
    public float hCost;
    //总代价
    public float FCost => gCost + hCost;
    //上一个节点
    public Node parent;

2.网格管理

1)生成网格地图

cs 复制代码
    public void CreateMap()
    {
        Map = new Node[mapX,mapY];
        
        for (int x = 0; x < mapX; x++)
        {
            for (int y = 0; y < mapY; y++)
            {
                //确定一个地图的原点(第一象限)
                Vector2Int pos = new Vector2Int(mapOrigin.x + x,mapOrigin.y + y);
                GameObject obj = Instantiate(nodePrefab, new Vector3(pos.x , pos.y , 0), Quaternion.identity,mapRoot);
                
                Node node = obj.GetComponent<Node>();
                node.gridPos = new Vector2Int(x,y);//将真实的世界坐标改变为虚拟坐标
                node.isWalkable = true;
                
                Map[x,y] = node;
            }
        }
    }

注:黑色为真实世界坐标,红色为虚拟坐标(参与实际路径计算)。

2)获取网格任意一个节点周围的节点

cs 复制代码
    public List<Node> GetNeighbors(Node node)
    {
        List<Node> neighbors = new List<Node>();
        for (int x = - 1; x <= 1; x++)
        {
            for (int y = - 1; y <= 1; y++)
            {
                //指的是中心节点
                if(x == 0 && y == 0)
                    continue;
                
                //x与y其实是node的偏移值
                int nodeX = node.gridPos.x + x;
                int nodeY = node.gridPos.y + y;
                //如果在地图里面就添加到列表中
                if (nodeX >= 0 && nodeX < mapX && nodeY >= 0 && nodeY < mapY)
                {
                    neighbors.Add(Map[nodeX,nodeY]);
                }
            }
        }
        
        return neighbors;
    }

3.算法核心

1)定义待探索队列与已探索(走过)队列

cs 复制代码
List<Node> openList = new List<Node>();//待探索队列
HashSet<Node> closeList = new HashSet<Node>();//已探索队列
        
openList.Add(startNode);

2)当探索队列还有时一直循环

3)找出探索队里fCost最小的节点(意味着总代价最小,路径更短);如果fCost相同就比较hCost(预估距离最小,gCost因为是确定值不做考虑),找出其最小的。

cs 复制代码
            Node currentNode = openList[0];
            for (int i = 1; i < openList.Count; i++)
            {
                if (openList[i].FCost < currentNode.FCost ||
                    openList[i].FCost == currentNode.FCost && openList[i].hCost < currentNode.hCost)
                {
                    currentNode = openList[i];
                }
            }

4)找到最小的节点,就将其从待探索队列移到已探索队列。

cs 复制代码
openList.Remove(currentNode);
closeList.Add(currentNode);

5)如果当前节点等于目标节点就退出循环,并且回溯路径。

cs 复制代码
            //到达终点
            if (currentNode == targetNode)
            {
                RetracePath(startNode, targetNode);
                return;
            }
cs 复制代码
        void RetracePath(Node startNode, Node endNode) {
        List<Node> path = new List<Node>();
        Node currentNode = endNode;

        while (currentNode != startNode) {
            currentNode.spriteRenderer.color = Color.green;
            path.Add(currentNode);
            
            currentNode = currentNode.parent;
        }
        path.Reverse();
    }

注:根据节点的Parent进行回溯操作。

6)如果没有到达终点,就获取当前节点的周围邻居节点 。周围邻居节点中如果时是障碍,或者已经探索过了就跳过。其余的就计算其gCost,如果比自己之前的小(距离初始点更近)(周围节点一开始gCost为零 ,因此newGCost > node.gCost,但是之前没待过任何队列,一定会加入待探索队列中 。 之后gCost赋值后,就比较谁的路径更短(往回走的话newCost会更大)),或者节点不在探索队列(不在任何队列),就计算这个邻居节点g/h/fCost,并且将当前节点标记为该节点的父节点,放入待探索队列。

cs 复制代码
            foreach (var node in gridManager.GetNeighbors(currentNode))
            {
                if(!node.isWalkable || closeList.Contains(node))
                    continue;

                float newGCost = currentNode.gCost + GetDistance(currentNode, node);
                
                if (newGCost < node.gCost || !openList.Contains(node))
                {
                    node.gCost = newGCost;
                    node.hCost = GetDistance(node, targetNode);
                    node.parent = currentNode;
                    
                    if(!openList.Contains(node)) openList.Add(node);
                }
            }

7)预估代价的距离计算

1)四方向:哈曼顿距离

cs 复制代码
        int xDis = Mathf.Abs(node1.gridPos.x - node2.gridPos.x);
        int yDis = Mathf.Abs(node1.gridPos.y - node2.gridPos.y);

        return xDis + yDis;

2)八方向:切比雪夫距离

cs 复制代码
        int xDis = Mathf.Abs(node1.gridPos.x - node2.gridPos.x);
        int yDis = Mathf.Abs(node1.gridPos.y - node2.gridPos.y);
        
        //横竖移动代价:10,斜向移动代价:14(√2≈1.414,取整)
        const int straightCost = 10;
        const int diagonalCost = 14;

        //先斜向走min(dx,dy)步,再横竖走|dx-dy|步
        return diagonalCost * Mathf.Min(xDis, yDis) + straightCost * Mathf.Abs(xDis - yDis);

4.多单位A*算法的优化

1)待探索队列用Priority Queue(优先队列,方便查找最小值),已探索队列用HashSet(哈希表,查找方便)。

注:优先队列逻辑上是完全二叉,实际上是数组(通过公式计算索引),根为最小值,查找更快。

2)起始位置相同:直接复用缓存路径。

3)起点不同,终点相同:用迪杰斯特拉算法从终点反向扩散,生成每个节点的方向。

相关推荐
罗西的思考1 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
五仁烧饼1 小时前
Unity Addressables 静默资源策略
游戏·unity·addressables
蛋先生DX2 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面2 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号2 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽3 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI3 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者4 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者4 小时前
【征程6】校准量化中HistogramObserver解析
算法
OuO-25 小时前
笔试强训 Day 34:ISBN 号码、kotori 和迷宫、矩阵最长递增路径
java·算法·矩阵