客户端地图内寻路总结与优化

Posted geek1116

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了客户端地图内寻路总结与优化相关的知识,希望对你有一定的参考价值。

首先关于客户端的坐标体系:技术图片

 

菱形框是客户端使用的单位方格,也就是游戏里雷达显示的坐标。客户端中采用的等距视角,使用菱形方格能与平面的场景地图模拟出3D效果。红色矩形框则是客户端和服务端公用的坐标格。

 

寻路方法入口: bool StartFindPath(CPos start, CPos end, vector<Cvector2f>& path, int IgnoreSteps, int nRatio, bool bAnyDir, int nMaxStep)

 

(下面讲解具体的寻路实现时涉及到A*寻路和Dijkstra最短路算法的知识点,这里就不对这两者做详细介绍了。)

 

start和end寻路的起点和终点,均为像素点。path为引用参数,用于存储寻路结果。IgnoreSteps为忽略距离终点的格数,即离终点还有几格时即可停下。nMaxSteps是最大寻路步数,用于限制使用A*算法寻路时的超时时间。

寻路成功返回true,将线路的“拐点集”(即相邻两点间是可以直线连通没有障碍的,且任意非相邻的两点是不能直线连通的)存储在path中。否则返回false。

 

StartFindPath的寻路过程:

  1.先用LinePath()检验起点与终点是否可以直线连通:

    (1)LinePath需保证起点在场景内;终点参数在场景外则直接返回起点坐标。

    (2)然后由起点和重点坐标得出直线方向,以最小单位(即上面说的菱形格)遍历从起点至终点的所有格判断是否有高障碍(高障碍一般为地形,人物角色和怪物一般为低障碍),成功返回true;否则返回遇到的第一个障碍格。

  2.若直线寻路没有成功,接下来检查输入参数的起点和终点是否均不超过场景。

  3.进入A*寻路算法FindPath():

    (1)检查如果起点和终点相同则直接返回-1。

    (2)将起点添加进open list中。

    (3)循环执行下面操作直至open list为空找到终点节点 超出最大寻路步数

      I. 在open list中找到并移除总花费值最小的节点(总花费=G+H;G为从起点到该节点的花费;H为从该点到终点的估计花费。H这里采用的是“曼哈顿距离”来估计从该节点到终点的花费,公式为横纵坐标差值的和:H=abs(start.x-end.x)+abs(start.y-end.y) )。

      II. 在该处理节点的八个相邻方向上遍历,对于到达的新节点若没存入过open list则直接加入;否则检查由该处理节点到新节点的G是否更小,如果更小就更新其花费和前置节点;并重新调整open list的顺序。

 

实现该A*算法的过程中三个重要的数据结构:

    (1)     XPS_Node:存储位置节点的结构,包含了节点位置、G花费、H花费和到达该节点的前置节点等内容。

    (2)     XPS_Node** m_pOpenList:用数组实现的、以总花费做权值比较的最小堆。

    (3)     XPS_Node* m_aBacket:用于记录加入过open list的XPS_Node哈希表;用x、y坐标值计算哈希值;解决哈希冲突的方式是将哈希值相同的XPS_Node以链表结构存储在同一数组下标上。

 

  4.如果A*寻路没有成功,则进入下面的Dijkstra最短路算法:

    (1)     加载对应地图数据目录下的roadPoint.csv和pathlink.csv(这两个文件是在地图上预设好的各坐标顶点和相邻顶点间的距离),构建出Dijkstra算法需要的图。

    (2)     找到距离start和end最近的且能够与之直线连通的顶点。将该两个顶点作为Dijkstra图中的起点和终点。

    (3)     循环N次执行以下操作:

      I. 找出到达花费最小的节点node,并作标记 使下次查找时忽略该点。

      II. 对于所有与node节点相邻的顶点,检查由node到其相邻节点的到达花费是否更小( cost[node] + link[node][i] < cost[i] ),如果是则更新其到达花费,并将其前置节点置为node。

  5.若Dijkstra寻路未成功(没有csv文件或找不到与起/终直连的顶点等),则进行大范围A*寻路(这一步往往耗时就很严重了玩家会有明显的寻路延迟)。

  (如下示,虚线为起始点寻找能够直线连通的顶点;蓝色实线为Dijkstra得出的路径)

技术图片

 

 

对寻路代码做的优化:

 

  1.对Dijkstra算法使用堆优化:

原先实现中,每次寻找最小花费的节点步骤中,是通过遍历一次所有节点得出的。可以用个最小堆来存储节点的到达花费,那么每次寻找最小花费节点的复杂度由O(N)降为O(logN)。 (我这里实现最小堆是直接用的std的优先队列priority_queue)。

  2.在使用Dijkstra算法寻路时,第一步是遍历图中的所有顶点中,找到能够与起点、终点位置直线连通的顶点。因为需要直线连通这一条件,因此在起/终点周围存在较多障碍格的时候很容易失效,而进入下面非常耗时的大范围A*寻路。因此将这一步改为:当直线连通失效时,则寻找与起/终点直线距离最近的若干顶点使用A*算法(超时步数限制在较小范围),来确定Dijkstra的图中起点和终点。

 

下面给出巨**谷地图(还是把具体地图名遮上吧 :D)上的一些测试结果:

起点

终点

原始耗时

优化后

(79,42)

(570,1097)

151ms

26ms

(17,1104)

(538,13)

114ms

32ms

(246,47)

(625,688)

97ms

23ms

(577,808)

(295,584)

31ms

29ms

(36,105)

(419,910)

39ms

39ms

(625,373)

(6,203)

28ms

27ms

 

 

上述优化有些限制,就是需要寻路地图要要有相应的csv地图,并且有数量可观的结点,否则起不到较好的优化效果。而且第一步的Dijkstra算法的堆优化的实际作用并不大,毕竟数据量有限。

 

以上是关于客户端地图内寻路总结与优化的主要内容,如果未能解决你的问题,请参考以下文章

一款已上市MMO手游地图同步方案总结

3D寻路系统NavMesh-服务端篇

3D寻路系统NavMesh-服务端篇

3D寻路系统NavMesh-服务端篇

3D寻路系统NavMesh-服务端篇

编程算法寻路A*优化