个人Demo开发--A星寻路模块

在换行进入游戏行业,并且在一家公司稳定工作一年多之后,感觉自己的学习似乎陷入了停滞,在思考了一段时间,并且和道友们交流过后,还是决定先动起来。在这一年多的工作时间里,每每想要学习点什么,都经常会想得太多而无法下手,所以最后终于决定,开始动起来,开始写自己想写的游戏,但是我知道,自己能力还不足,这也是为什么叫demo开发的原因,我希望能通过多次迭代开发或者重构,最终创作出一个自己满意的demo。

废话说的有点多,接下来进入正题,目前我的demo里已经完成了以下几个部分,资源加载模块(基于QF框架),简易的消息机制,基于消息机制的回合制流程,简单的地图生成模块(相当简陋),寻路模块,而今天呢,我决定先来讨论寻路模块

之所以这么决定,是因为,这个模块是我最近完成,并且基于我的地图设想进行了一定的优化,所以印象深刻,那么接下来,就开始正式说说A星寻路这个大名鼎鼎的寻路算法。

A星寻路分析

如果我们直接刨析A星寻路的原理的话,其实非常的简单,其灵魂,实际上就是他的公式:F = G + H,G是指当前节点距离父节点的距离,H是指当前节点到终点的距离,而F是指总共的消耗值,A星寻路的过程,就是不断地寻找自生周围F值最小的点,然后把其设置为当前节点,当找到终点时,方可停止。

单次寻路拆解

这里引用一下B站唐老师的视频解说图

图中a点(黄色)为起始点,d点(蓝色)为终点,红色方块为阻挡

a点的周围,一共有8个点,分别为a1~a8,筛选掉处于阻挡模块的点,那么可选的点还剩下7个

分别计算每个点到终点F值,F = G + H,那么我们需要计算出G和H的值

首先来计算G的值,上下左右的点的G值其实非常直接,为1,斜角的点,我们需要在这做一点小处理,为了方便计算,我们可以直接规定斜角值为1.4,实际上就是对2开根号

接下来计算H的值,H值的计算为了方便,也做了一定的简化计算处理,规则就是,直接计算当前点与终点的横坐标与纵坐标的差值和,并且在计算过程中,无视阻挡,比如a2,H=4(x)+1(y)

那么经过计算,我们能得出,F值最小的点,应该是正上方(a2)和正右方(a4)的两个点,我们可以先取正右方(a5)的点来继续进行下一步,至于为什么不取上方点(a2),后面我们再解释(如果推演一下,也能发现,a2后续的点F值反而不是最小的)

那么以a5为起点,再次重复进行找寻周围点的步骤,但是在这里,我们需要注意一个小细节,已经找过的点,我们不应该再记录了,那么第二次搜寻得到的新的点如图

通过计算,我们可以得到,b1是F值最小的点,那么我们再以b1为起点,再次重复步骤

然后再次得到c2为最近点,那么再重复找寻周围点,最后发现终点,就是c2的周围点,d点

过程步骤总结

当我们模拟一次完整的寻路过程,不难发现在寻路的过程中,有着重复的步骤,那么我们可以把其中复用的部分提炼,以便于后续进行代码的编写

首先,先对寻路的过程进行一个大致总结:

选定起点与终点→判断起点终点是否合法(是否超出地图范围,是否为阻挡)→找寻周围点→从周围点中找出F值最小点(在找寻过程中,需要判断是否合法,不合法则选取第二小的点)→判断是否为终点→(不是终点)设为起点→找寻周围点

由此则完成了一个正常循环,这就是上述拆解中能够得到的核心步骤

代码分析

经过之前的分析,我们可以大致构想出我们需要的代码框架

首先,我们需要节点类,节点类大致需要几个属性,F(消耗),G(起点距离),H(终点距离),x,y,还有一个parent这个属性在我们回溯路径时非常重要

根据我们过程中分析,为了便于我们筛选,我们还需要两个列表,一个用于存放已经遍历过的点,叫做开启列表(openList),一个用于存放被设置成起点的点,叫做关闭列表(closeList),这两个列表同样非常关键

其次,我们需要把整体的寻路过程,写在一个方法里,并且带有返回值为寻路路径,可以便于外部调用,这个方法需要传入起点与终点,给寻路过程使用

在方法的内部,我们进行真正的寻路步骤

在开始寻路之前,需要把坐标点转换成实际的地图节点,然后对节点进行必要的检查

进行初始化操作,并且把起点放入关闭列表

获取起点周围的点,并且找出其中的最小点,这一步步骤可能会有重复,可以考虑用循环或者递归来做重复的操作,考虑到递归的本质是调用方法,可能导致额外的GC(这只是个人的考虑,还没有经过验证,不一定准确),所以我这里使用的是循环实现

把暂时的起点周围符合条件的点(范围合法且不在openList和closeList中)放入openList,在加入开启列表之前,有一个非常重要的操作,就是把起点设置为所有点的parent

从openlist中选出最小的点作为起点,从openList中移除,并且把他加入到closeList中

而判断是否跳出循环的条件也很简单,就是判断当前起点是否为终点。当找到终点时,我们则需要回溯得到完整路径时,有一个值得注意的关键点,我们不可以直接把整个closeList作为路径,而是应该通过终点的parent点,一个个回溯得到完整路径,至于为什么,我们在后面再进行分析

总结与分析

至此,完整的寻路逻辑其实已经基本完成了,仔细回顾一下,步骤其实并不多

1.获得起点与终点,并且判断节点合法性

2.列表以及起点信息初始化,把起点放入closeList

3.获取起点周围点(合法点,且不在openList和closeList中),并且加入openList

4.获取openList中F值最小节点,设置为起点,并且加入closeList

5.判断当前起点是否为终点,如果是终点则回溯得到路径并且跳出循环,如果不是终点则重复步骤3,4

笔者在学习完A星寻路后,提出了两个疑问,并且在后续的思考中获得了答案,那么我就来探讨一下这两个疑问

第一个,为什么每次选择新起点需要从openList中选择,而不是从周围点中获取,其实这个问题如果做过一定的推演,不难得到答案,当地图中没有阻挡或者不会形成死路时,新起点的周围点的F值,应该会一直小于之前的点,那么从周围点选取不太会出现问题,但是当当前寻路路径走到死路时,不难发现,我们已经无法获取到周围点了,因为,周围的点要么是阻挡,要么已经在处于openList或者closeList中了,会导致寻路出现异常,他无法从之前点中找寻一条新的可能路径,所以,我们选取新点时,需要从openList中选择,由此我们也可以推断出无法到达终点的情况,当所有的点都已经放入了closeList,openList已经为空,并且closeList中不存在终点时,说明从起点无法到达终点

第二个,为什么回溯路径不能直接使用closeList,而是需要通过终点记录的parent一个个追溯得到完整路径,其实如果能够理解第一条中分析的情况,那么这一条就能很容易明白其中缘由,当我们的寻路过程中存在错误路径,也即寻路出现死路需要从之前某个点重新寻路时,closeList中记录的点就会存在死路中的节点,如果直接采用,那么给出的路径必然是错误的,通过提前记录的唯一parent节点,则能够正确的找出完整的路径,并且是从终点开始回溯,确保了路径的正确性

A星寻路的学习,到此就告一段落了,在没有真正实践了解之前,它始终保持着神秘的面纱,但当你真正去面对,深入了解与学习之后,能发现它没有想象中的那么困难,而在学习的道路中亦是如此,愿未来的你我,顶峰相见

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值