目录
使用路径点(Way Point)作为节点
洪水填充算法创建路径点
使用导航网格(Navigation Mesh)作为节点
预计算
寻路的改进
参考
在了解路径规划之前必须先了解基本的寻路算法。
可参考A*寻路算法:A*寻路算法-KillerAery-博客园
使用路径点(Way Point)作为节点
大部分讨论A*算法使用的节点是网格点(也就是简单的二维网格),但是这种内存开销往往比较大。
实际上A*寻路算法,对于图也是适用的,实现只要稍微改一下。
因此我们可以把地图看作一个图而不是一个网格,使用预先设好的路径点而不是网格来作为寻路节点,则可以减少大量节点数量。
使用路径点的好处:
洪水填充算法创建路径点
倘若一个地图过大,开发人员手动预设好路径点+路径连接的工作就比较繁琐,而且很容易有错漏。
这时可以使用洪水填充算法来自动生成路径点,并为它们链接。
算法步骤:
1.以任意一点为起始点,往周围八个方向扩展点(不能通行的位置则不扩展)
2.已经扩展的点(在图中被标记成红色)不需要再次扩展,而扩展出来新的点继续扩展
3.直到所有的点都被扩展过,此时能得到一张导航图
自动生成的导航图可以调整扩展的距离,手游账号交易平台从而得到合适的节点和边的数量。
使用导航网格(Navigation Mesh)作为节点
导航网格将地图划分成若干个凸多边形,每个凸多边形就是一个节点。
使用导航网格更加可以大大减少节点数量,从而减少搜寻所需的计算量,同时也使路径更加自然。
然而该如何建立地图的导航网格,一般有两种方法:
导航网格是目前3D游戏的主流实现,例如《魔兽世界》就是典型使用导航网的游戏,Unity引擎也内置了基于导航网格的寻路系统。
如果你对如何将一个区域划分成多个凸多边形作为导航网格感兴趣,可以参考空间划分的数据结构(网格/四叉树/八叉树/BSP树/k-d树/BVH/自定义划分)-KillerAery-博客园里面的BSP树部分,也许会给你一些启发。
预计算
主要方式是通过预先计算好的数据,然后运行时使用这些数据减少运算量。
可以根据自己的项目权衡运行速度和内存空间来选择预计算。
路径查询表
借助预先计算好的路径查询表,可以以O(|v|)的时间复杂度极快完成寻路,但是占用空间为O(|v|²)。
(|v|为顶点数量)
实现:对每个顶点使用Dijkstra算法,求出该顶点到各顶点的路径,再通过对路径回溯得到前一个经过的点。
路径成本查询表
有时候,游戏AI需要考虑路径的成本来决定行为,
则可以预先计算好路径成本查询表,以O(1)的时间复杂度获取路径成本,但是占用空间为O(|v|²)。
实现:类似路径查询表,只不过记录的是路径成本开销,而不是路径点。
扩展障碍碰撞几何体
在寻路中,一个令游戏AI程序员头疼的问题是碰撞模型往往是一个几何形状而不是一个点。
这意味着在寻路时检测是否碰到障碍,得用几何形状与几何形状相交判断,而非几何形状包含点判断(毋庸置疑前者开销庞大)。
一个解决方案是根据碰撞模型的形状扩展障碍几何体,此时碰撞模型可以简化成一个点,这样可以将问题由几何形状与几何形状相交问题转换成几何形状包含点问题。
这里主要由两种扩展思路:
这些扩展障碍几何形状的计算完全可以放到预计算(离线计算),不过要注意:
可视点寻径
寻路的改进
平均帧运算
有时候,大量物体使用A*寻路时,CPU消耗比较大。
我们可以不必一帧运算一次寻路,而是在N帧内运算一次寻路。
(虽然有所缓慢,但是就几帧的东西,一般实际玩家的体验不会有大影响)
所以我们可以通过每帧只搜索一定深度=深度限制/N(N取决于自己定义多少帧内完成一次寻路)。
路径平滑
基于网格的寻路算法结果得到的路径往往是不平滑的。
很容易看出来,寻路算法的路径太过死板,只能上下左右+斜45度方向走。
这里提供两种平滑方式:
它检查相邻的边是否可以无障碍通过,若可以则删除中间的点,不可以则继续往下迭代。
它的复杂度是O(n),得到的路径是粗略的平滑,还是稍微有些死板。
它每次推进一位都要遍历剩下所有的点,看是否能无障碍通过,推进完所有点后则得到精准平滑路径。
它的复杂度是O(n²),得到的路径是精确的平滑。
双向搜索
与从开始点向目标点搜索不同的是,你也可以并行地进行两个搜索:
一个从开始点向目标点,另一个从目标点向开始点。当它们相遇时,你将得到一条路径。
双向搜索的思想是:单向搜索过程生成了一棵在地图上散开的大树,而双向搜索则生成了两颗散开的小树。
一棵大树比两棵小树所需搜索的节点更多,所以使用双向搜索性能更好。
不过实验表明,在A*算法往往得到的不会是一棵像BFS算法那样散开的树。
因此无论你的路径有多长,A*算法只尝试搜索地图上小范围的区域,而不进行散开的搜索。
若地图是复杂交错多死路的(例如迷宫,很多往前的路实际上并不通往终点),A*算法便会容易产生散开的树,这时双向搜索会更有用。
路径拼接
游戏世界往往很多动态的障碍,当这些障碍挡在计算好的路径上时,我们常常需要重新计算整个路径。但是这种简单粗暴的重新计算有些耗时,一个解决方法是用路径拼接替代重新计算路径。
首先我们需要设置拼接路径的频率K:
例如每K步检测K步范围内是否有障碍,若有障碍则该K步为阻塞路段。
接着,与重新计算整个路径不同,我们可以重新计算从阻塞路段首位置到阻塞路段尾的路径:
假设p[N]..P[N+K]为当前阻塞的路段。为p[N]到P[N+K]重新计算一条新的路径,并把这条新路径拼接(Splice)到旧路径:把p[N]..p[N+K]用新的路径值代替。
一个潜在的问题是新的路径也许不太理想,下图显示了这种情况(褐色为障碍物):
最初正常计算出的路径为红色路径(1 ->2- ->3 ->4)。
如果我们到达2并且发现从2到达3的路径被封锁了,路径拼接技术会把(2 ->3)用(2 ->5 ->3)取代,结果是寻路体沿着路径(1 ->2 ->5 ->3 ->4)运动。
我们可以看到这条路径不是这么好,因为蓝色路径(1 ->2 ->5 ->4)是另一条更理想的路径。
一个简单的解决方法是,设置一个阈值最大拼接路径长度M:
如果实际拼接的路径长度大于M,算法则使用重新计算路径来代替路径拼接技术。
M不影响CPU时间,而影响了响应时间和路径质量的折衷:
路径拼接确实比重计算路径要快,但它可能算出不怎么理想的路径:
节点评估
在A*寻路算法里,一个节点的预测函数最直观的莫过于欧几里得距离。
然而对于复杂的游戏世界来说,特别是对于需要复杂决策的AI来说,节点的预测值可不仅仅就距离一个影响因素:
因此,我们可以自定义寻路的预测函数,以调整成为适应复杂游戏世界的AI寻路。