A star寻路算法

前言

在开发中,如何编写NPC一直是一个值得研究的话题,其中,NPC的寻路问题值得学习研究。在这个问题上,A*算法很受欢迎。今天来记录一下它的算法思想及实现。

算法思想

  1. A*寻路算法是基于网格(Grid)的。
  2. 它是一种启发式(heuristic)算法

简略地说:A*算法会优先搜索最有可能产生最短路径的网格。

接下来我们引入下面两个概念细说。

针对不同需求来度量两点间距离

在不同游戏中,NPC的移动是多种多样的:有四向移动的,有八向移动的,甚至有允许向任何方向移动的。
针对不同的移动需求,分别有对应的方法来度量两点间距离(现在先不考虑障碍物)。

  1. 四向移动:使用曼哈顿距离
曼哈顿距离
    function heuristic(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    return D * (dx + dy)

这是一个标准的启发式函数,其中D表示相邻正方形网格间的最低移动成本。返回值是从node节点(将网格视为节点)到goal节点的代价。

  1. 八向移动:使用对角线距离
对角线距离
    function heuristic(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    return D * (dx + dy) + (D2 - 2 * D) * min(dx, dy)
    //等价于D * max(dx, dy) + (D2-D) * min(dx, dy)
    //等价于if (dx > dy) (D * (dx-dy) + D2 * dy) else (D * (dy-dx) + D2 * dx)

相对于四向移动,八向移动允许NPC沿对角线行走,所以在原先的曼哈顿估价函数上引入了沿对角线移动的代价,即D2,在数值上一般可以定为1.414D。
至于返回值的计算中,为什么要取 min(dx, dy) ,这是因为对角线的长度总是被较短直角边限制住,看看上方的第二条注释应该可以明白。

  1. 任意向移动:可以使用欧式距离,也可以按照需求更改。
欧式距离
    function heuristic(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    return D * sqrt(dx * dx + dy * dy)
任意向移动允许NPC沿任意方向移动,看起来很诱人是不是?然而如果直接套用上面的函数,结果可能会寄:显然,这个函数的时间开销太大了。

GameProgramming/Heuristics中简略地提到了各种优化办法,然而解决了一个问题,又会出现新的问题。在优化上,综合来说,我认为还是采取快速平方根近似来做优化比较合适,虽然开销仍然很大,但这也是没有办法中的办法了。

估价函数

A*中的估价函数可以表示为:f(n) = g(n) + h(n)
其含义为:
已知起点格与终点格,对于任一格n,g(n)为起点到n的实际代价,h(n)为n到终点的估计代价,f(n)是综合优先级,为这两者之和,在一定程度上反映了总代价。不太明白的话可以看看下面的例子讲解。

下面我们来看一个A*算法样例,在这其中感受估价函数的作用。关于障碍物的问题也见下文。

image

绿格为起点,红格为终点,灰格为障碍,允许进行八向移动,即使用对角线距离,规定代价D为1,代价D2为1.4。

第一步:计算起点周围可用格子的f = g + h。

如果某个格子到终点之间有障碍物,也无需理会,直接使用距离公式就好,对于h我们只需得到一个大概的值,这也是h值被称为估计距离的原因;而对于障碍,根本无需计算它。

结果如下:
image

第二步:从所有已知f值的格子(上图已标黄)中,取f值最小的并且尚未在第二步中被取过的格子,以它为新的起点,计算它周围可用格子的f = g + h。

有两条规则是:

  1. 假设本次取的是格子a,它周围的某个可用格子为b,那么新计算出来的b的g值必须为ab之间的实际代价再加上a的g值。
    因为这个操作是模拟从a走向b的,当然要求其总和。

  2. 假设本次取的是格子a,它周围存在已经被计算过的格子b,那么必须比较新计算出的g值与格子b的旧g值的大小,格子b的g值要取其中最小值。
    这个操作会更新旧的实际代价。

要注意的是,每次新计算的格子都会被计入已知f值的格子中,这也是A*算法是启发式算法的原因,它会优先搜索最有可能产生最短路径的网格。

如下:
image

第三步:若第二步中的某次计算时,有已计算格子的四周和终点相邻,则终止算法;否则继续执行第二步。

如下:
image
image
若干次后...
image
大概如此。

至于如何记录通路,可以让“被计算的格子”指向“最新一次更新它”的格子,这样一来,只要知道和终点相邻的那个格子,就可以反推回起点,形成通路。

优缺点分析

优点十分明显,A*算法能够适应不同的需求,十分灵活;而且作为一种启发式的算法,它的性能较好,是一种直接的搜索算法。

A*算法的缺点是实时性比较差,随着节点数的增多,搜索效率逐渐变低。
我的优化思路是,如果起点和终点之间相隔很远,中间的格子数过多,我们可以尝试降低采样频率,将相邻的每四个格子视为一个大格子,其中有两个或三个小格子可行走就视为大格子可行走,否则大格子即为障碍,这样一来,或许能提高时间效率。
此思路尚未实践过,究竟能否成功优化,适用于什么规模的地图寻路,还有待考证。

参考资料

GameProgramming/AStar
A*寻路算法详细解读
A-Star(A*)寻路算法原理与实现
启发式(heuristic)算法

posted @ 2022-08-06 17:17  AshScops  阅读(298)  评论(0编辑  收藏  举报