本文由 伯乐在线 - stonesun 翻译,黄利民 校稿。未经许可,禁止转载!
英文出处:Amit Patel。欢迎加入翻译组。
本系列:
- 关于寻路算法的一些思考(1):A* 算法介绍
- 关于寻路算法的一些思考(2):Heuristics 函数
- 关于寻路算法的一些思考(3):A* 算法的实现
- 关于寻路算法的一些思考(4):A* 算法的变体
- 关于寻路算法的一些思考(5):处理移动中的障碍物
- 关于寻路算法的一些思考(6):预先计算好的路径的所用空间
- 关于寻路算法的一些思考(7):地图表示
- 关于寻路算法的一些思考(8):长期和短期目标
- 关于寻路算法的一些思考(9):寻路者的移动成本
- 关于寻路算法的一些思考(10):最短路径的用户体验
- 关于寻路算法的一些思考(11):寻路算法的其他应用
- 关于寻路算法的一些思考(12):AI 技术
有时候,影响计算寻路路径的不是时间,而是计算路径所需的上百个单元格所占的空间。寻路是需要内存来运行寻路算法,还需要额外内存来存储寻到的路径。运行寻路算法(A*,开集或闭集)所需的临时空间经常会比存储这些寻到的路径所需的空间更大。通过在同一时间内只进行一条路径计算来限制游戏中的计算量,可以将你需要的临时空间降到最少。另外,对开集闭集的数据结构的选择也会对减少你所需的临时内存产生很大的影响。在本章中将会转而关注通过生成的路径减少使用的空间。
位置vs方向
一条路径可以是一堆位置或者一堆方向,位置需要更多的空间,但是它的优势在于它很容易决定一条路径上的一个任意的位置点或者方向而不用遍历这条路径。当存储方向的时候,只需要这个方向就可以很容易的决定;而位置只能通过遵循某个方向经由整条路径才能决定,在传统的栅格化地图中,位置可能使用两个16位的整数来存储,这样的话,每一步存储都需要32字节。因为它有更少的方向所以需要的空间就更少。如果一个单元格只能在四个方向上移动,每一步只需要2字节;如果单元格可以在六个或者八个方向上移动,每一步就会需要3字节。这些存储在存储位置上的路径点在路径中是非常重要的。Hannu Kankaanpaa建议你可以通过存储绝对的方向(比如”向北”)而不是存储这些相对方向(比如”右转60度”)来进一步的减少存储所需的空间。一些相对方向可能让一些单元格难以理解。比如说如果你的单元格在想北方移动,那它下一步就不大可能向南移动。在一个六方向的游戏中,你只有五个有意义的方向。在一些地图中你可能只有3个方向(直走,左转60度,右转60度)有意义。但是在一些其他的地图中右转120度可能才是一个有效的移动(比如通过Z字形路线爬一座陡峭的山)。
路径压缩
一旦一个路径被找到,它将会通过某种方式被压缩。我们可以使用一个通用的压缩算法,但是我们不会在这篇文章中讨论这个算法。一个针对具体路径的压缩算法可以用来缩短基于位置的路径或者基于方向的路径。在做决定之前,考虑你游戏中的具体的典型路径来决定哪一种压缩算法最适合你所寻到的路径。同时 也要考虑在你游戏中实施(或者调试)的可行性,代码的体积还有这个压缩算法是否真的很重要。如果你有 个300单元格的限制,在同一时间只有50个单元格在移动,并且路径很短(只有100步),那么所需要的内存可能最多只有50k,那么你就不需要再考虑使用路径的压缩算法了。
位置点存储
在一张地图中如果障碍是寻路的主要影响因子而不是地形,那么就可以将路径分成很多条线段,如果是这种情况的话,那么一条路径只需要包括这些线段集合的各个终点位置(有时也被称为路径点)。运动就是由检查这条路径的下一个终点位置并沿着直线向终点移动组成。
方向存储
当方向被存储的时候,它可能是在一排中多次出现的方向,你可以利用那种常见的模式使用较少的空间去存储那条路径。
一种最好的存储路径方式是同时存储这个方向和指明单元格将在这个方向上移动多少次的数字。不同于位置存储的优化,当这个方向在这一排中并没有多次使用的时候这种优化可能会变得很糟。当然,对于很多直线路径的位置存储这种方式很有效,由于线可以不与的行走方向之一对齐,这种情况并不适用方向存储的压缩。当有多种可选方向时,你可以选择清除“一直直走”作为一个可行的方向。Hannu Kankaanpaa指出在一个八方向图中,你可以清除直走,后退还有135度左,右转(假设你的地图允许这样),然后你可以仅仅使用2字节去存储每个方向。
另一种存储路径的方法是使用可变长度的编码。这是指使用一个单字节去存储大部分的一般性步骤比如说:直走。使用数字1去标记转向,在跟上一个数字1使用一些字节去表示转向,在一个4向图中,你只可以左转或者右转,所以你可能需要使用10来表示左转11来表示右转。
可变长度编码更为通用,可能比游程编码工作起来更好,但是对于长直型的路径就不如混合编码了。这个(北向,六步直走,左转,直走三步,右转,直走5步,左转,直走六步)的序列被使用长编码的[(North,6),(WEST,3),(NORTH,5),(WEST,2)]所代替。如果每个方向占用2字节,美短距离占用8字节,这条路径需要40字节去存储。如果使用可变长度编码,你需要使用1个字节去存储各个步骤2个字节去存储每次转向-[NORTH 0 0 0 0 0 0 10 0 0 0 11 0 0 0 0 0 10 0 0]总共需要24字节。如果初始化的方向和每次转向代表一步,你可以每次转向省下一字节,这样你只需要20字节就可以存储这条路径。但是使用可变长度编码在遇到较长的路径可能需要使用更多的空间。如果使用游程编码这个序列(north,直走200步)是[(NORTH,200)]只需要10个字节,同样的序列如果使用可变长度编码就变成[NORTH 0 0…],总共需要202字节。
计算路径点
路径点是指一条路径上的所有点。寻路完成后处理步骤时,可以折叠多个步骤到一个单一的路径点钟,通常存储的是路径改变方向的点,或者像城市的主要位置点,而不是存储一路走来的每一步。然后使用算法在路径点之间沿着路径运动。
限制路径长度
考虑到地图条件或者指令可能会发生变化,存储一条长路径可能意义并不大,因为余下的路径点可能根本就不会被使用到。每个单元格可以在路径开始的时候存储一些合适的步骤数,然后在路径快要走完时在重新计算心的路径。这种方法可以控制每个单元格的数据量。
总结
路径在游戏中可能占用很多空间,尤其是当路径很长,并且这个路径上有很多游戏单元的时候。路径压缩、路径点还有信标(beacon)都会在一定程度上减少在一小块数据里存储很多行路步骤的空间。在一条直线路径上加入需要存储路径点的话,只需要存储末尾点就可以了,信标是依靠在地图上特意标明的地方之间事先计算好的路径上使用。如果路径仍然需要占用很大的空间,就要限制路径的长度了,在经典的实时路径计算中是这样做的:为了节省空间,消息可以被忽略并且延迟计算。