最近在研究3D寻路算法,想自己实现下,虽然用的BK引擎,但是还是要把这相关的技术和算法自己全盘掌握才是对自己技术的一个提升。 A算法使用于静态的寻路算法 D算法适用于动态的寻路算法 本文源码下载地址:http://download.csdn.net/detail/sun2043430/5907609(第一版)
http://download.csdn.net/detail/sun2043430/5909315(第二版)
https://github.com/sun2043430/A_Star_Algorithm (github下载地址,推荐到这里下载,这里是最新版)
网上关于A*算法的文章、代码和示例已经相当多了,有很多文章写的都很好,还有很多国外的网页用JS动态演示了A*算法寻路的过程。我找到的一些资源如下:
点评:生动的例子,讲解的非常浅显,易上手的示例。(入门级)
2 http://theory.stanford.edu/~amitp/GameProgramming/
点评:长篇大作。非常详细地介绍了A*算法的原理,演化过程,启发式,算法实现,A*实现中各种数据结构的优劣,A*算法变种,实际使用A*算法会遇到的问题。(深入级)
3 http://www.ccg.leeds.ac.uk/people/j.macgill/xaStar/
点评:使用java脚本制作的动态演示A*寻路算法的网页。演示了经典A*算法和Fudge A*算法 的动态寻路过程。(很有参考价值)
我用MFC模拟了经典A*算法的动态寻路过程,基本上模仿了这个网页。
感谢以上文章的作者和云风的书!
A*算法
A*算法是Dijkstra算法和贪婪算法的综合,Dijkstra算法的缺点在于从起点全方位360地向外做广度优先搜索,导致遍历节点太多,速度较慢,优点是能够保证找到最优路径。贪婪算法总是选择看起来最优的路线前进,优点是速度很快,缺点是有可能掉入陷阱,而走冤枉路。而A*算法采用启发式的方式,综合了二者的优点,且依然能够保证找到最优路径。
以一个最简单的例子来说明一下A*算法的启发式寻路过程,顺便介绍一些A*算法中的概念。
以上图为例,红色为起点,绿色是终点。我们假设每一个方块只能直接到达其上下左右四个方块,不可以走斜线。每一个方块有3个值f,g,h(左上角是f值,左下角是g值,右下角是h值),其中:
- g是指从起点走到这个方块所经历的步数(这个是精确的值,是一步一步走过来时,累加得到的值);
- h是指从当前方块到终点方块的启发式步数(一般简单的就是假设该方块到终点之间没有障碍物,可到达的最小步数,这是理想值);
- f是g+h得到的值。
首先,从起点出发,可以往上下右3个反向走,所以上下右3个方块的g值为1。但是上下两个方块到终点的启发式步数h是7(横向6步,竖向1步),右边方块的启发式步数h是5。将g和h相加得到f值。
那么接下来,我们就选择f值最小的方块(右边的方块)来继续寻路过程……具体算法我们在下面给出。
A*算法具体过程
1 开两个空队列OPEN,CLOSE
2 将起点的g值设为0,并计算出对应的h,f值,将起点放入OPEN队列
3 从OPEN队列中取出f值最小的节点放入临时队列TempQueue(一开始就是起点这一个点;另外f值最小的可能不止一个,如有多个则都取出)(取出的意思是OPEN队列中就不再保存这些节点了)
4 如果TempQueue队列为空,也就意味着OPEN队列为空,说明已经遍历完整个地图,此时应该退出寻路过程【退出条件1:地图遍历完,走不到终点】
5 将TempQueue中的所有节点加入到CLOSE队列中
6 遍历TempQueue中的每一个节点Node
处理Node的每一个邻居节点Neighbor
如果Neighbor既不在OPEN队列也不在CLOSE队列中,计算出Neighbor节点的g,h,f值(g值等于Node的g值加一个单位),设置Neighbor的父节点为Node,并将Neighbor加入到OPEN队列
如果Neighbor在CLOSE队列中则不处理(在动态的游戏中可能需要看情况做处理,简单情况下无需处理)
如果Neighbor在OPEN队列中,则用Neighbor之前的f值和现在计算出来的f值进行比较。如果现在计算出来的f值更小,则更新Neighbor的f值,并重新设置Neighbor的父节点为Node
如果Neighbor就是终点,则寻路过程结束,从终点根据父节点的指向到CLOSE队列中去找父节点,可倒推出完整的最短路径【退出条件2:走到终点】
7 回到步骤3继续处理
A*算法关键代码
[cpp] view plain copy
- void CAStar::FindPath()
- {
- m_bFind = false;
- vector<NODE> minVec;
- while (!m_bFind)
- {
- minVec.clear();
- GetMinFromOpen(minVec);
- if (minVec.empty())
- break;
- m_close.insert(m_close.end(), minVec.begin(), minVec.end());
- for (vector<NODE>::iterator it = minVec.begin(); it != minVec.end(); it++)
- DoNeighbors(*it);
- if (m_bFind)
- GetFinalShortestPath();
- if (m_CallBack) m_CallBack(m_pArg);
- }
- }
DEMO程序的使用
DEMO程序运行界面如下
在右边3个单选按钮中选择一个,然后可在左边设置起点、终点或者障碍物。
点击开始寻路,则开始动态演示寻路过程。
其中:
红色方块为起点
绿色方块为终点
蓝色方块为OPEN队列中的点
黄色方块为CLOSE队列中的点
紫色方块为最短路径
计算过f,g,h值的节点,其f值在左上角;g值在左下角;h值在右下角。
请原谅本人糟糕的MFC编程。画面预显示做的很不好,不知道该在哪个消息中预绘制图形,而且因为没有使用双缓冲绘图,所以画面闪烁厉害。另外,寻路过程没有使用多线程,所以寻路过程中画面会卡住不能移动,这些方面希望在下一版中能够改进。另外我打算在下一个版本中加入可斜向移动的功能。
本文地址:http://blog.csdn.net/sunnianzhong/article/details/9899359
Update:(2013.8.12) 斜向移动功能
上传了第二版的代码,可在这里下载:http://download.csdn.net/detail/sun2043430/5909315
1 增加了斜向移动的功能,对应的每一步移动的权值进行了修改,平行或竖直移动一格的权值是5,斜向移动的权值是7。斜向移动时需要判断是否会穿过障碍物方块。如下的情况是不允许的。
1 | 2 |
3 | 4 |
只要1或者4位置是障碍物,那么从2到3的斜向移动是不允许的。
2 采用内存DC缓冲方式,画面先画到内存DC,再一次性 BitBlt 贴到控件上,使得画面不再闪烁。
3 寻路过程放到一个独立的线程中去运算,使得主界面不再被卡住。
4 增加寻路过程中的暂停、继续功能。更方便查看中间状态。