预先计算的寻路是否仍然有意义?


12

语境

老卢卡斯艺术公司(ScummVM时代)使用预先计算的寻路来点击图形冒险游戏。这是该技术的粗略概述。

第1步

每个房间的地板都被划分为所谓的“步行箱”,它们几乎等同于导航网格中的节点,但仅限于梯形形状。例如:

 ______ _____ _________ _____
\   A  |  B  |    C    |  D   \
 \_____|     |         |_______\
       |_____|         |
             |_________|

第2步

离线算法(例如Dijkstra或A *)将计算每对节点之间的最短路径,并将路径的第一步存储在2D矩阵中,并在每个维度中由使用的起始节点和结束节点进行索引。例如,使用上面的步行框:

      ___ ___ ___ ___
     | A | B | C | D | <- Start Node
  ___|___|___|___|___|
 | A | A | A | B | C |  ---
 |___|___|___|___|___|     |
 | B | B | B | B | C |     |
 |___|___|___|___|___|     |-- Next node in shortest path
 | C | B | C | C | C |     |   from Start to End
 |___|___|___|___|___|     | 
 | D | B | C | D | D |  ---
 |___|___|___|___|___| 
   ^
   |
End Node

您可能会猜到,内存需求随着节点数量的增加而迅速增加(N ^ 2)。由于short通常足够大以将每个条目存储在矩阵中,因此具有300个节点的复杂映射会导致额外存储:

300^2 * sizeof(short) = 176 kilobytes

第三步

另一方面,计算两个节点之间的最短路径非常快捷且琐碎,只需对矩阵进行一系列查找即可。就像是:

// Find shortest path from Start to End
Path = {Start}
Current = Start
WHILE Current != End
    Current = LookUp[Current, End]
    Path.Add(Current)
ENDWHILE

应用此简单算法以查找从C到A的最短路径将返回:

1) Path = { C }, Current = C
2) Path = { C, B }, Current = B
3) Path = { C, B, A }, Current = A, Exit

我怀疑,借助当今功能强大的硬件,再加上对每个级别执行此操作所需要的内存,现在仅通过在运行时执行A *就能抵消这项技术曾经带来的任何好处。

我还听说如今的内存查找甚至可能比常规计算要慢,这就是为什么创建正弦和余弦查找表不再流行的原因。

但是我必须承认,尽管我对低级硬件效率的问题还不是很了解,所以我借此机会向更熟悉该主题的人提出意见。

在我的引擎上,我还需要能够在运行时动态地向图中添加和删除节点(请参见参考资料),因此预先计算的路由只会使事情变得更加复杂,因此我将其报废(更不用说我的运行时A *解决方案已经在完美运行)。尽管如此,我还是想知道...

归根结底,这种技术在当今任何情况下仍然有用吗?


2
我认为,如果您的CPU预算紧张,那仍然很重要。但是一旦您想要动态路径,它就不再有用。顺便说一句,我研究了从哪里获得A *算法的,您可以使用minheap和其他技巧进一步优化它。我已经做了一些改进C#中的A *的迭代,您可以在这里看到:roy-t.nl/index.php/2011/09/24/…可能有用。
罗伊·T。

1
谢谢,我已将其添加为书签,并在开始优化应用程序时会对其进行研究。我几乎使用了Eric Lippert的解决方案,但做了一些小的修改,因为它是如此干净而且易于遵循...而且对于我的所有测试用例,它几乎都是“即时”运行的,因此我什至都没有去优化它。
David Gouveia

1
顺便说一句,如果您决定进行预计算,则可能需要查看Floyd-Warshall算法。与重复使用Dijkstra / A *相比,它可以更有效地构建“下一步”矩阵。
2011年

@amitp感谢您的提示,了解这些替代方法总是很高兴的!尽管由于在大多数情况下预计算将在脱机状态下进行,所以提高效率不会带来太多好处。除非你真的没有耐心。:-)
David Gouveia

同意,尽管Floyd-Warshall的实现也比Dijkstra的算法简单得多,所以如果您还没有实现Dijkstra的算法,那么值得一看:)
amitp 2012年

Answers:


3

在我的引擎上,我还需要能够在运行时动态地向图中添加和删除节点的功能(请参阅此信息),因此预先计算的路由只会使事情变得更复杂,因此我将其报废(更不用说我的运行时A *解决方案已经在完美运行)。尽管如此,我还是想知道...

归根结底,这种技术在当今任何情况下仍然有用吗?

使用这种技术我看不到任何好处。

我缺乏图的灵活性(您可以具有不同的LOD,它们不必具有任何特定的形状,等等)。此外,您引擎的任何用户都将知道什么是图形以及如何使用图形。因此,如果他们想添加额外的功能,他们将不得不学习如何使用对他们来说完全陌生的情况来实现其扩展。

正如您提到的那样,它看起来会可怕地扩展。还值得注意的是,如果图表适合现金,并且您将所有路径发现都背对背运行,则可以真正减少IO时间。看来您的实现很快就会变得太大而无法容纳任何缓存。

我还听说如今的内存查找甚至可能比常规计算要慢,这就是为什么创建正弦和余弦查找表不再流行的原因。

除非您可以在缓存中放入所有程序及其所需的内存,否则在限制处理器之前,您将遇到瓶颈,无法将内容拉入和拉出内存。

我怀疑,借助当今功能强大的硬件,再加上对每个级别执行此操作所需要的内存,现在仅通过在运行时执行A *来抵消这项技术曾经拥有的任何好处。

还应意识到许多游戏都有单独的循环来更新AI。我相信他建立我的项目的方式是:有一个60hz的用户输入更新循环,AI仅为20hz,游戏绘制速度尽可能快。

另外,我也做了一些GBA编程,只是为了娱乐,一点也不转移到使用现代设备上。对于GBA而言,一切都是为了最大程度地减少处理器的工作量(因为这很可悲)。您还必须意识到,大多数高级语言C#和Java(不是那么多的C ++或C)都可以为您进行大量的优化。至于优化代码,除了尽可能少地访问内存以及在对内存进行尽可能多的计算之后再引入新的内存(将其从缓存中移出并确保您是只做一次。

编辑:也是为了回答你的标题,是的。预计算经常使用的路径是一个好主意,可以在游戏循环之外的任何位置使用A *完成。例如,从您的基础到RTS中的资源,这样收集就不必每次想要离开或返回时都重新计算。


关于您的编辑,我并不是真正在谈论对常用路径的预计算,而只是在谈论对每个可能的路径进行预计算的技术。我对您的大多数答案是否反对使用预先计算的路径查找也有些困惑,但是最后您说这将是一个绝妙的主意。那么,这在CPU受限的环境(例如GBA)中是否有用?
David Gouveia

1
不好意思,我试图指出,从上下文中删除您的标题的答案是肯定的。而有关您问题中描述的特定算法的答案为否。因此,总之,预先计算所有可能的路径是一个坏主意,但是预先计算一些非常常用的路径可能是个好主意。
ClassicThunder

2
@ClassicThunder:这种预先计算来自几个地标的所有路径的技术通常称为ALT具有地标和三角不等式的A-starcs.princeton.edu/courses/archive/spr06/cos423/Handouts/GW05。 pdf
Pieter Geerkens
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.