A*寻路算法

各寻路算法之间的比较

深度优先算法(BFS)在所有方向上平等的探索,是最好写的寻路算法

Dijkstra算法优先考虑低成本的路径,在游戏中可以规定远离敌人、避开森林需要更高的成本,所以当移动成本发生变化的时候我们使用该算法而不是BFS

A*寻路是对Dijkstra算法的修改,针对单个目标时进行了优化,Dijkstra的算法可以找到所有位置的路径;A 查找到一个位置的路径,或多个位置中最近的路径。它优先考虑似乎更接近目标的路径。

下图依次表示他们的搜索区间

在这里插入图片描述

Dijkstra的算法可以很好地找到最短的路径,但它浪费了时间探索那些没有希望的方向。Greedy Best-First在有希望的方向上探索,但它可能不会找到最短的路径。A* 算法同时使用从起点开始的实际距离和到目标的估计距离。

不同算法情况下不同的搜索范围和最终路线

在这里插入图片描述

A*寻路算法的过程

对于任意一个格子n,其估价函数如下:

f(n) = g(n) + h(n)

其中 g(n) 指的是从起始格子到格子n的实际代价,而 h(n) 指的是从格子n到终点格子的估计代价。

  1. 我们找到周围格子中f(n)值最小的各格子,在计算他周围格子的f(n)
  2. 一直找出边界格子中的f(n)最小的格子,直到找到与重点相邻的格子。

A*对启发式方法的使用

启发式算法,即优先搜索最有可能产生最佳路径的格子

对于h(n)函数,不同的表示方法会导致最终结果的不同,比如如果采用曼哈顿距离(只能水平和垂直移动)和欧几里得距离(直线距离)

h(n)的具体影响为:

  • 在一个极端,如果是0,那么会漫无目的的寻找,A*变成Dijkstra的算法,它保证找到一个最短的路径。
  • 如果始终低于到目标实际距离,则保证 A* 找到最短路径。越低,节点 A* 扩展得越多,速度越慢。
  • 如果 正好等于到目标实际距离,则 A* 只会遵循最佳路径,从不扩展其他任何内容,使其非常快。虽然您无法在所有情况下都做到这一点,但在某些特殊情况下可以使其精确。很高兴知道,给定完美的信息,A *将表现完美。
  • 如果大于到目标实际距离,则A *不能保证找到最短的路径,但它可以运行得更快。
  • 在另一个极端,如果非常高,那么主要取决与h(n),而A*变成了贪心算法

使用具体的移动方式匹配地图:

  • 在允许 4 个方向移动正方形网格上,使用曼哈顿距离 .
  • 在允许 8 个方向移动的方形网格上,使用对角线距离 .

A*算法的具体实现

地图中允许8D运动,h(n)采用对角线加直线运动。我们就可以根据水平方向的差值与竖直方向的差值中较小的那个值,计算出对角线,然后再平移。

横向纵向的格子的单位消耗为10,对角单位消耗为14。

定义一个OpenList,用于存储和搜索当前最小值的格子。

定义一个CloseList,用于标记已经处理过的格子,以防止重复搜索。

OpenList中f值最小的点作为当前点,加入邻居点到OpenList

对于邻居点执行以下代码伪代码:

如果邻居点在OpenList中
    计算当前值的G与该邻居点的G值
    如果G值比该邻居点的G值小
        将当前点设置为该邻居点的父节点
        更新该邻居点的GF值
若不在
    计算并设置当前点与该邻居点的G值
    计算并设置当前点与该邻居点的H值
    计算并设置该邻居点的F值
    将当前点设置为该邻居点的父节点

判断终点是否在OpenList中,如果已在OpenList中,则返回该点,其父节点连起来的路径就是A*搜索的路径。如果不在,则重复执行2,3,4,5。直到找到终点,或者OpenList中节点数量为0。

格子的记录值

public class Node
{
    Int2 m_position;//下标
    public Int2 position => m_position;
    public Node parent;//上一个node
    
    //角色到该节点的实际距离
    int m_g;
    public int g {
        get => m_g;
        set {
            m_g = value;
            m_f = m_g + m_h;
        }
    }

    //该节点到目的地的估价距离
    int m_h;
    public int h {
        get => m_h;
        set {
            m_h = value;
            m_f = m_g + m_h;
        }
    }

    int m_f;
    public int f => m_f;

    public Node(Int2 pos, Node parent, int g, int h) {
        m_position = pos;
        this.parent = parent;
        m_g = g;
        m_h = h;
        m_f = m_g + m_h;
    }
}

A*过程

public class AStar {
    static int FACTOR = 10;//水平竖直相邻格子的距离
    static int FACTOR_DIAGONAL = 14;//对角线相邻格子的距离

    bool m_isInit = false;
    public bool isInit => m_isInit;

    UIGridController[,] m_map;//地图数据
    Int2 m_mapSize;
    Int2 m_player, m_destination;//起始点和结束点坐标
    EvaluationFunctionType m_evaluationFunctionType;//估价方式

    Dictionary<Int2, Node> m_openDic = new Dictionary<Int2, Node>();//准备处理的网格
    Dictionary<Int2, Node> m_closeDic = new Dictionary<Int2, Node>();//完成处理的网格

    Node m_destinationNode;

    public void Init(UIGridController[,] map, Int2 mapSize, Int2 player, Int2 destination, EvaluationFunctionType type = EvaluationFunctionType.Diagonal) {
        m_map = map;
        m_mapSize = mapSize;
        m_player = player;
        m_destination = destination;
        m_evaluationFunctionType = type;

        m_openDic.Clear();
        m_closeDic.Clear();

        m_destinationNode = null;

        //将起始点加入open中
        AddNodeInOpenQueue(new Node(m_player, null, 0, 0));
        m_isInit = true;
    }

    //计算寻路
    public IEnumerator Start() {
        while(m_openDic.Count > 0 && m_destinationNode == null) {
            //按照f的值升序排列
            m_openDic = m_openDic.OrderBy(kv => kv.Value.f).ToDictionary(p => p.Key, o => o.Value);
            //提取排序后的第一个节点
            Node node = m_openDic.First().Value;
            //因为使用的不是Queue,因此要从open中手动删除该节点
            m_openDic.Remove(node.position);
            //处理该节点相邻的节点
            OperateNeighborNode(node);
            //处理完后将该节点加入close中
            AddNodeInCloseDic(node);
            yield return null;
        }
        if(m_destinationNode == null)
            Debug.LogError("找不到可用路径");
        else
            ShowPath(m_destinationNode);
    }

    //处理相邻的节点
    void OperateNeighborNode(Node node) {
        for(int i = -1; i < 2; i++) {
            for(int j = -1; j < 2; j++) {
                if(i == 0 && j == 0)
                    continue;
                Int2 pos = new Int2(node.position.x + i, node.position.y + j);
                //超出地图范围
                if(pos.x < 0 || pos.x >= m_mapSize.x || pos.y < 0 || pos.y >= m_mapSize.y)
                    continue;
                //已经处理过的节点
                if(m_closeDic.ContainsKey(pos))
                    continue;
                //障碍物节点
                if(m_map[pos.x, pos.y].state == GridState.Obstacle)
                    continue;
                //将相邻节点加入open中
                if(i == 0 || j == 0)
                    AddNeighborNodeInQueue(node, pos, FACTOR);
                else
                    AddNeighborNodeInQueue(node, pos, FACTOR_DIAGONAL);
            }
        }
    }

    //将节点加入到open中
    void AddNeighborNodeInQueue(Node parentNode, Int2 position, int g) {
        //当前节点的实际距离g等于上个节点的实际距离加上自己到上个节点的实际距离
        int nodeG = parentNode.g + g;
        //如果该位置的节点已经在open中
        if(m_openDic.ContainsKey(position)) {
            //比较实际距离g的值,用更小的值替换
            if(nodeG < m_openDic[position].g) {
                m_openDic[position].g = nodeG;
                m_openDic[position].parent = parentNode;
                ShowOrUpdateAStarHint(m_openDic[position]);
            }
        }
        else {
            //生成新的节点并加入到open中
            Node node = new Node(position, parentNode, nodeG, GetH(position));
            //如果周边有一个是终点,那么说明已经找到了。
            if(position == m_destination)
                m_destinationNode = node;
            else
                AddNodeInOpenQueue(node);
        }
    }

    //加入open中,并更新网格状态
    void AddNodeInOpenQueue(Node node) {
        m_openDic[node.position] = node;
        ShowOrUpdateAStarHint(node);
    }

    void ShowOrUpdateAStarHint(Node node) {
        m_map[node.position.x, node.position.y].ShowOrUpdateAStarHint(node.g, node.h, node.f,
            node.parent == null ? Vector2.zero : new Vector2(node.parent.position.x - node.position.x, node.parent.position.y - node.position.y));
    }

    //加入close中,并更新网格状态
    void AddNodeInCloseDic(Node node) {
        m_closeDic.Add(node.position, node);
        m_map[node.position.x, node.position.y].ChangeInOpenStateToInClose();
    }

    //寻路完成,显示路径
    void ShowPath(Node node) {
        while(node != null) {
            m_map[node.position.x, node.position.y].ChangeToPathState();
            node = node.parent;
        }
    }

    //获取估价距离
    int GetH(Int2 position) {
        if(m_evaluationFunctionType == EvaluationFunctionType.Manhattan)
            return GetManhattanDistance(position);
        else if(m_evaluationFunctionType == EvaluationFunctionType.Diagonal)
            return GetDiagonalDistance(position);
        else
            return Mathf.CeilToInt(GetEuclideanDistance(position));
    }

    //获取对角线距离
    int GetDiagonalDistance(Int2 position) {
        int x = Mathf.Abs(m_destination.x - position.x);
        int y = Mathf.Abs(m_destination.y - position.y);
        int min = Mathf.Min(x, y);
        return min * FACTOR_DIAGONAL + Mathf.Abs(x - y) * FACTOR;
    }

    //获取曼哈顿距离
    int GetManhattanDistance(Int2 position) {
        return Mathf.Abs(m_destination.x - position.x) * FACTOR + Mathf.Abs(m_destination.y - position.y) * FACTOR;
    }



    public void Clear() {
        foreach(var pos in m_openDic.Keys) {
            m_map[pos.x, pos.y].ClearAStarHint();
        }
        m_openDic.Clear();

        foreach(var pos in m_closeDic.Keys) {
            m_map[pos.x, pos.y].ClearAStarHint();
        }
        m_closeDic.Clear();
        m_destinationNode = null;
        m_isInit = false;
    }
}

最终结果

在unity中呈现效果

我们在生成的格子中添加显示F,G,H值的文本,在创建一个MapController来管理网格的属性、颜色等,暴露函数给按钮调用

在这里插入图片描述

在这里插入图片描述

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
A*寻路算法是一种常用的路径搜索算法,通过对搜索空间中的节点进行评估和排序,可以找到最优路径。它结合了启发式搜索和贪婪搜索的思想,具有高效性和准确性。 算法的核心思想是通过使用一个启发函数,对每个节点进行估价,以选择最有希望的节点进行搜索。启发函数一般使用曼哈顿距离或欧几里得距离来评估节点与目标节点的距离。同时,A*算法还维护了一个开放列表和一个关闭列表,用于保存已搜索和待搜索的节点。 首先,将起点节点加入到开放列表中,并初始化各个节点的启发函数值和代价值。然后,从开放列表中选取具有最小代价值的节点,进行扩展。扩展过程中,评估每个相邻节点的代价值,并更新节点的父节点和代价值。将扩展完成的节点加入到关闭列表中。 重复上述步骤,直到找到目标节点或者开放列表为空。如果找到目标节点,可以通过回溯从目标节点到起点节点,得到最优路径。如果开放列表为空,则表示无法找到路径。 以下是使用Python实现A*寻路算法的基本步骤: 1. 定义节点类,包括节点坐标、启发函数值、代价值等属性,并实现比较运算符以便排序。 2. 初始化起点节点和目标节点,并将起点节点加入到开放列表中。 3. 进入循环,遍历开放列表中的节点。 4. 从开放列表中选取代价值最小的节点,并将其从开放列表中移除,加入到关闭列表中。 5. 对选中的节点进行扩展,生成所有相邻节点,并计算它们的代价值和启发函数值。 6. 遍历所有相邻节点,并更新它们的父节点和代价值。 7. 检查相邻节点是否在开放列表或关闭列表中,若不在,则加入开放列表。 8. 重复步骤3到7,直到找到目标节点或开放列表为空。 9. 如果找到目标节点,可以通过回溯从目标节点到起点节点,得到最优路径。 10. 如果开放列表为空,则表示无法找到路径。 总之,A*寻路算法是一种高效而准确的路径搜索算法,通过合理的启发函数和排序策略,可以找到最优路径,并且在实践中常被广泛应用。使用Python实现该算法时,需要定义节点类、初始化起点和目标节点、维护开放列表和关闭列表,并进行节点的评估和更新。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值