我正在制作一个简单的基于图块的2D游戏,该游戏使用A *(“ A星”)寻路算法。我一切正常,但是搜索存在性能问题。简而言之,当我单击不可逾越的图块时,该算法显然会遍历整个地图以找到通往不可逾越的图块的路线-即使我站在它旁边。
我该如何规避?我想我可以减少对屏幕区域的寻路,但是也许我在这里遗漏了一些明显的东西?
我正在制作一个简单的基于图块的2D游戏,该游戏使用A *(“ A星”)寻路算法。我一切正常,但是搜索存在性能问题。简而言之,当我单击不可逾越的图块时,该算法显然会遍历整个地图以找到通往不可逾越的图块的路线-即使我站在它旁边。
我该如何规避?我想我可以减少对屏幕区域的寻路,但是也许我在这里遗漏了一些明显的东西?
Answers:
有关避免完全导致路径失败的搜索的一些想法:
有效完成A *搜索的最便宜的方法之一是完全不进行搜索。如果所有代理商都无法真正进入该区域,请在负载(或管道中)中为每个区域填充唯一的岛屿ID。当寻路检查,如果岛ID的路径来源的匹配岛ID的目的地。如果它们不匹配,则没有点搜索-这两个点位于不同的,未连接的岛上。这仅在所有代理都有真正无法通过的节点时才有用。
我限制了可以搜索的最大节点数的上限。这可以帮助无法通行的搜索永远运行,但这确实意味着一些很长的可通过搜索可能会丢失。这个数字需要调整,并不能真正解决问题,但可以减轻长搜索的费用。
如果您发现所花费的时间太长,则可以使用以下技术:
让搜索在单独的线程中运行,或者在每个帧中运行一点,这样游戏就不会停止等待搜索。在等待搜索结束时显示人物抓头或踩脚的动画,或适当的动画。为了有效地做到这一点,我将搜索的状态保留为单独的对象,并允许存在多个状态。当请求路径时,获取一个空闲状态对象并将其添加到活动状态对象的队列中。在您的寻路更新中,将活动项从队列的最前面拉出并运行A *,直到A.完成或B.运行了一些迭代限制。如果完成,则将状态对象放回空闲状态对象列表。如果尚未完成,请将其放在“有效搜索”的末尾,然后移至下一个。
确保使用正确的数据结构。这是我的StateObject的工作方式。出于性能原因,我所有的节点都预先分配了一个有限的数量,例如1024或2048。我使用一个节点池来加快节点的分配速度,它还允许我在数据结构中存储索引而不是指针,这些数据是u16(如果我有255个最大节点,则是u8)(在某些游戏中是这样)。对于我的寻路,我将优先级队列用于打开列表,存储指向Node对象的指针。它实现为二进制堆,并且我将浮点值排序为整数,因为它们始终为正,并且我的平台的浮点比较慢。我为封闭的地图使用哈希表来跟踪访问过的节点。它存储NodeID(而不是Node)以节省缓存大小。
首次访问节点并计算到目标的距离时,请将其缓存在存储在状态对象中的节点中。如果您重新访问该节点,请使用缓存的结果,而不是再次进行计算。就我而言,这有助于不必在重新访问的节点上做平方根。您可能会发现还有其他值可以预先计算和缓存。
您可以研究的其他领域:使用双向寻路从任一端进行搜索。我没有这样做,但是正如其他人指出的那样,这可能有所帮助,但并非没有警告。我要尝试的另一件事是分层路径查找或群集路径查找。还有就是HavokAI文档中一个有趣的描述这里描述的群集概念,这是不是说明*实现了HPA不同位置。
祝您好运,让我们知道您的发现。
AStar是一个完整的计划算法,这意味着如果存在到该节点的路径,则AStar可以保证找到它。因此,它必须先检查出起始节点之外的每个路径,然后才能确定目标节点不可达。当节点过多时,这是非常不希望的。
减轻这种情况的方法:
如果您先验地知道某个节点不可访问(例如,它没有邻居或标记为UnPassable
),则No Path
无需调用AStar即可返回。
限制终止前AStar将扩展的节点数。检查打开的设置。如果太大,终止并返回No Path
。但是,这将限制AStar的完整性。因此它只能规划最大长度的路径。
限制AStar查找路径的时间。如果时间用完,请退出并返回No Path
。与以前的策略一样,这会限制完整性,但是会随着计算机的速度扩展。请注意,对于许多游戏来说,这是不希望的,因为计算机速度更快或速度较慢的玩家对游戏的体验会有所不同。
如果目标周围只有6个图块可访问,而原点有1002个图块可访问,则搜索将在6(双)次迭代时停止。
一旦一个搜索找到了另一个被访问的节点,您也可以将搜索范围限制到另一个被访问的节点,并更快地完成。
假设问题是目的地不可达。而且导航网格不是动态的。最简单的方法是导航图稀疏(足够稀疏,以至于相对较快的完整运行),并且仅在可能的情况下才使用详细图。
使用具有不同特征的多种算法
A *具有一些优良的特性。特别是,它总是找到最短的路径(如果存在)。不幸的是,您还发现了一些不良特性。在这种情况下,它必须在不存在任何解决方案之前穷举搜索所有可能的路径。
您在A *中发现的“缺陷”是它不了解拓扑。您可能有一个2D世界,但并不知道。就其所知,在您的世界的最远角落是一个楼梯,将它带到世界的尽头。
考虑创建第二种了解拓扑的算法。首先,您可能需要每10或100个空间用“节点”填充世界,然后维护这些节点之间的连通性图。该算法将通过在起点和终点附近找到可访问的节点,然后尝试在图上的它们之间找到一条路径(如果存在)的方法来寻路。
一种简单的方法是将每个图块分配给一个节点。可以轻松地看出,您只需要为每个图块分配一个节点即可(您永远无法访问图中未连接的两个节点)。然后,将图边缘定义为两个具有不同节点的图块相邻的任何位置。
该图有一个缺点:找不到最佳路径。它只是找到一条道路。但是,现在表明A *可以找到最佳路径。
它还提供了一种启发式方法,可以帮助您提高对A *功能的低估,因为您现在对自己的情况有了更多的了解。在发现您需要退后一步以继续前进之前,您不太可能需要充分探索一个死胡同。
如果您的地图是静态的,则可以让每个单独的部分都有自己的代码,并在运行A *之前先进行检查。这可以在创建地图时完成,甚至可以在地图中进行编码。
不可逾越的图块应带有一个标志,当移至类似图块时,您可以选择不运行A *或在其旁边选择一个图块。
如果您的动态地图经常更改,那么您将很不走运。您必须以自己的方式来决定算法是否在完成之前停止,或者对部分进行检查以经常关闭。
如何使A *更快地得出节点不可通过的结论?
分析您的Node.IsPassable()
功能,找出最慢的部分,然后加快速度。
在确定节点是否可通过时,将最可能出现的情况放在顶部,以便在大多数情况下函数立即返回而无需费心检查更难理解的可能性。
但这是为了更快地检查单个节点。您可以剖析以查看在查询节点上花费了多少时间,但是听起来您的问题是正在检查太多节点。
当我单击不可逾越的图块时,该算法显然会遍历整个地图以找到通往不可逾越的图块的路线
如果目标图块本身无法通过,则该算法根本不应该检查任何图块。甚至在开始进行寻路之前,它应查询目标图块以检查是否可行,如果不能,则返回无路径结果。
如果您的意思是目的地本身是可通过的,但被不可逾越的图块环绕,因此没有路径,那么A *检查整个地图是正常的。怎么会知道没有路呢?
如果是后者,则可以通过进行双向搜索来加快速度-这样,从目的地开始的搜索可以迅速找到没有路径,并停止搜索。参见此示例,用墙壁围住目标,比较双向与单向。
如果仅您的地图上没有连续的大面积不可达图块,那么这将起作用。寻路只会搜索封闭的不可达区域,而不是搜索整个可达地图。
当我单击不可逾越的图块时,该算法显然会遍历整个地图以找到通往不可逾越的图块的路线-即使我站在它旁边。
其他答案也不错,但我必须指出显而易见的一点-您根本不应将寻路工具用于无法逾越的图块。
这应该是算法的早期退出:
if not IsPassable(A) or not IsPasable(B) then
return('NoWayExists');
要检查图中两个节点之间的最长距离:
(假设所有边缘的权重相同)
v
。v
,我们称其为d
。u
。u
,我们将其称为w
。u
和之间的距离w
是图中的最长距离。证明:
D1 D2
(v)---------------------------r_1-----------------------------(u)
|
R | (note it might be that r1=r2)
D3 | D4
(x)---------------------------r_2-----------------------------(y)
y
和x
更大!D2 + R < D3
D2 < R + D3
v
和之间的距离x
大于v
和u
?u
在第一阶段就不会被选中。归功于教授。什洛米·鲁宾斯坦
如果使用加权边,则可以通过运行Dijkstra而不是BFS查找最远的顶点,从而在多项式时间内完成相同的操作。
请注意,我假设它是一个连通图。我还假设它是无向的。
A *对于一个简单的基于2D瓦片的游戏而言并没有真正的用处,因为如果我正确理解,假设这些生物朝4个方向移动,那么BFS将获得相同的结果。即使生物可以沿8个方向移动,偏爱靠近目标的节点的懒惰BFS仍将获得相同的结果。A *是Dijkstra的一种修改,与使用BFS相比,其计算量大得多。
BFS = O(| V |)应该是O(| V | + | E |),但实际上不是自上而下的地图。A * = O(| V | log | V |)
如果我们的地图只有32 x 32瓦片,那么BFS最多将花费1024,而真正的A *可能会花费您高达10,000。这是0.5秒和5秒之间的时差,如果考虑到缓存,则可能会更大。因此,请确保您的A *表现得像一个懒惰的BFS,它喜欢更接近所需目标的图块。
A *对于导航地图很有用,因为边缘的成本在决策过程中很重要。在简单的基于开销的平铺游戏中,边缘的成本可能不是重要的考虑因素。如果是这样(如果不同的图块成本不同),则可以运行BFS的修改版,该版本将推迟并惩罚通过图块的路径,这些路径会使字符变慢。
所以是的,在很多情况下,BFS> A *。
log|V|
在A *的复杂性确实来自于保持打开集,或条纹的大小,并为网格地图是非常小的-有关日志(的sqrt(| V |))使用的符号。log | V | 仅显示在超连接图中。这是最简单的最坏情况复杂性应用得出错误结论的示例。