在上一篇博客中,我们一起学习了随机迷宫算法,在本篇博客中,我们将一起了解一下寻路算法中常用的A*算法。

  通常情况下,迷宫寻路算法可以使用深度优先或者广度优先算法,但是由于效率的原因,不会直接使用这些算法,在路径搜索算法中最常见的就是A*寻路算法。使用A*算法的魅力之处在于它不仅能找到地图中从A到B的一条路径,还能保证找到的是一条最短路径,它是一种常见的启发式搜索算法,类似于Dijkstra算法一样的最短路径查找算法,很多游戏应用中的路径搜索基本都是采用这种算法或者是A*算法的变种。

  下面我们来了解一下A*算法相关的理论知识:

  

  如图,我们需要在迷宫中找到A点到B点的一条最短的可以通过的路径,A和B直接被一面墙堵住了。在上一篇博客中我们说到了,地图是有二维数组组成的,墙表示不能通过的地方,用1表示,A*算法所要做的就是从A找到一条最短的通向B的路径。当然,不能从墙上飞过去,也不能瞬移到B。只能每次移动一个格子,一步一步地移动到B目标位置。问题在于,每次移动一格的时候,有上下左右四个方向,这里我们限制物体斜向移动,如何选择下一个移动方向呢?按照我们的想法,不就是找一条离目标最近的路吗?那我们可以在这四个方向中,找一个最接近目标点的位置,当然,还要考虑障碍因素,基于这个思想,A*算法采用了以下的搜索步骤来实现:

  1.首先把起始位置点加入到一个称为“open List”的列表,在寻路的过程中,目前,我们可以认为open List这个列表会存放许多待测试的点,这些点是通往目标点的关键,以后会逐渐往里面添加更多的测试点,同时,为了效率考虑,通常这个列表是个已经排序的列表。

  2.如果open List列表不为空,则重复以下工作:

  (1)找出open List中通往目标点代价最小的点作为当前点;

  (2)把当前点放入一个称为close List的列表;

  (3)对当前点周围的4个点每个进行处理(这里是限制了斜向的移动),如果该点是可以通过并且该点不在close List列表中,则处理如下;

  (4)如果该点正好是目标点,则把当前点作为该点的父节点,并退出循环,设置已经找到路径标记;

  (5)如果该点也不在open List中,则计算该节点到目标节点的代价,把当前点作为该点的父节点,并把该节点添加到open List中;

  (6)如果该点已经在open List中了,则比较该点和当前点通往目标点的代价,如果当前点的代价更小,则把当前点作为该点的父节点,同时,重新计算该点通往目标点的代价,并把open List重新排序;

  3.完成以上循环后,如果已经找到路径,则从目标点开始,依次查找每个节点的父节点,直到找到开始点,这样就形成了一条路径。

  以上,就是A*算法的全部步骤,按照这个步骤,就可以得到一条正确的路径。这里有一个关键的地方,就是如何计算每个点通往目标点的代价,之所以称为A*算法为启发式搜索,就是因为通过评估这个代价值来搜索最近的路径,对于任意一个点的代价值,在A*算法中通常使用下列的公式计算:

代价F=G+H

  在这里,F表示通往目标点的代价,G表示从起始点移动到该点的距离,H则表示从该点到目标点的距离,比如图中,可以看到小狗的附近格子的代价值,其中左上角的数字代表F值,左下角的数字代表G值,右下角的数字代表H值。拿小狗上方的格子来举例,G=1,表示从小狗的位置到该点的距离为1个格子,H=6,表示从小狗到骨头的距离是6个格子,则F=G+H=7。在此处,距离的算法是采用曼哈顿距离,它计算从当前格子到目的格子之间水平和垂直的方格的数量总和,例如在平面上,坐标(x1,y1)的点和坐标(x2,y2)的点的曼哈顿距离为:

|x1-x2|+|y1-y2|

  当然,距离的算法也可以采用其他的方法,实际在游戏中,这个移动的代价除了要考虑距离因素外,还要考虑当前格子的游戏属性。比如有的格子表示水路、草地、陆地,这些有可能影响人物移动的速度,实际计算的时候还要把这些考虑在内。

  另一个需要注意的就是,在计算这个距离的时候是毋须考虑障碍因素的,因为在以上A*算法步骤中会剔除掉障碍。

  这样,按照前面所说的A*算法的步骤,第一次循环open List的时候,把A点作为当前点,同时把A周围的四个点放入到open List中。第二次循环的时候把A右边的点作为当前点,该点的父节点就是A,这是处理当前点的时候,只需要把当前点的上下两个点放入open List中,因为左边的A已经在close List中,而右边的是墙,所以直接被忽略。

  A*的算法具体代码如下:

  1. //地图工具
  2. var _MapUtil = win.MapUtil =
  3. {
  4. //定义点对象
  5. Point:function(x,y)
  6. {
  7. this.x = x;
  8. this.y = y;
  9. this.parent = null;
  10. this.f = 0;
  11. this.g = 0;
  12. this.h=0;
  13. //当前点状态,0:表示在openlist 1:表示closelist,-1表示还没处理
  14. this.state=-1;
  15. //flag表明该点是否可通过
  16. this.flag = 0;
  17. },
  18. //产生随机迷宫
  19. primMaze:function(r,c)
  20. {
  21. //初始化数组
  22. function init(r,c)
  23. {
  24. var a = new Array(2*r+1);
  25. //全部置1
  26. for(var i=0,len=a.length;i<len;i++)
  27. {
  28. var cols = 2*c+1;
  29. a[i]= new Array(cols);
  30. ArrayUtil.fillWith(a[i],1);
  31. }
  32. //中间格子为0
  33. for(var i=0;i<r;i++)
  34. for(var j=0;j<c;j++)
  35. {
  36. a[2*i+1][2*j+1] = 0;
  37. }
  38. return a;
  39. }
  40. //处理数组,产生最终的数组
  41. function process(arr)
  42. {
  43. //acc存放已访问队列,noacc存放没有访问队列
  44. var acc = [],noacc = [];
  45. var r = arr.length>>1,c=arr[0].length>>1;
  46. var count = r*c;
  47. for(var i=0;i<count;i++){noacc[i]=0;}
  48. //定义空单元上下左右偏移
  49. var offs=[-c,c,-1,1],offR=[-1,1,0,0],offC=[0,0,-1,1];
  50. //随机从noacc取出一个位置
  51. var pos = MathUtil.randInt(count);
  52. noacc[pos]=1;
  53. acc.push(pos);
  54. while(acc.length<count)
  55. {
  56. var ls = -1,offPos = -1;
  57. offPos = -1;
  58. //找出pos位置在二维数组中的坐标
  59. var pr = pos/c|0,pc=pos%c,co=0,o=0;
  60. //随机取上下左右四个单元
  61. while(++co<5)
  62. {
  63. o = MathUtil.randInt(0,5);
  64. ls =offs[o]+pos;
  65. var tpr = pr+offR[o];
  66. var tpc = pc+offC[o];
  67. if(tpr>=0&&tpc>=0&&tpr<=r-1&&tpc<=c-1&&noacc[ls]==0){ offPos = o;break;}
  68. }
  69. if(offPos<0)
  70. {
  71.  
  72. pos = acc[MathUtil.randInt(acc.length)];
  73. }
  74. else
  75. {
  76. pr = 2*pr+1;
  77. pc = 2*pc+1;
  78. //相邻空单元中间的位置置0
  79. arr[pr+offR[offPos]][pc+offC[offPos]]=0;
  80. pos = ls;
  81. noacc[pos] = 1;
  82. acc.push(pos);
  83. }
  84. }
  85. }
  86. var a = init(r,c);
  87. process(a);
  88. return a;
  89. },
  90. //把普通二维数组(全部由1,0表示)的转换成a*所需要的点数组
  91. convertArrToAS:function(arr)
  92. {
  93. var r = arr.length,c=arr[0].length;
  94. var a = new Array(r);
  95. for(var i=0;i<r;i++)
  96. {
  97. a[i] = new Array(c);
  98. for(var j=0;j<c;j++)
  99. {
  100. var pos = new MapUtil.Point(i,j);
  101. pos.flag = arr[i][j];
  102. a[i][j]=pos;
  103. }
  104. }
  105. return a;
  106. },
  107. //A*算法,pathArr表示最后返回的路径
  108. findPathA:function(pathArr,start,end,row,col)
  109. {
  110. //添加数据到排序数组中
  111. function addArrSort(descSortedArr,element,compare)
  112. {
  113. var left = 0;
  114. var right = descSortedArr.length-1;
  115. var idx = -1;
  116. var mid = (left+right)>>1;
  117. while(left<=right)
  118. {
  119. var mid = (left+right)>>1;
  120. if(compare(descSortedArr[mid],element)==1)
  121. {
  122. left = mid+1;
  123. }
  124. else if(compare(descSortedArr[mid],element)==-1)
  125. {
  126. right = mid-1;
  127. }
  128. else
  129. {
  130. break;
  131. }
  132. }
  133. for(var i = descSortedArr.length-1;i>=left;i--)
  134. {
  135. descSortedArr[i+1] = descSortedArr[i];
  136. }
  137. descSortedArr[left] = element;
  138. return idx;
  139. }
  140. //判断两个点是否相同
  141. function pEqual(p1,p2)
  142. {
  143. return p1.x==p2.x&&p1.y==p2.y;
  144. }
  145. //获取两个点距离,采用曼哈顿方法
  146. function posDist(pos,pos1)
  147. {
  148. return (Math.abs(pos1.x-pos.x)+Math.abs(pos1.y-pos.y));
  149. }
  150. function between(val,min,max)
  151. {
  152. return (val>=min&&val<=max)
  153. }
  154. //比较两个点f值大小
  155. function compPointF(pt1,pt2)
  156. {
  157. return pt1.f-pt2.f;
  158. }
  159. //处理当前节点
  160. function processCurrpoint(arr,openList,row,col,currPoint,destPoint)
  161. {
  162. //get up,down,left,right direct
  163. var ltx = currPoint.x-1;
  164. var lty = currPoint.y-1;
  165. for(var i=0;i<3;i++)
  166. for(var j=0;j<3;j++)
  167. {
  168. var cx = ltx+i;
  169. var cy = lty+j;
  170. if((cx==currPoint.x||cy==currPoint.y)&&between(ltx,0,row-1)&&between(lty,0,col-1))
  171. {
  172. var tp = arr[cx][cy];
  173. if(tp.flag == 0 && tp.state!=1)
  174. {
  175. if(pEqual(tp,destPoint))
  176. {
  177. tp.parent = currPoint;
  178. return true;
  179. }
  180. if(tp.state == -1)
  181. {
  182. tp.parent = currPoint;
  183. tp.g= 1+currPoint.g;
  184. tp.h= posDist(tp,destPoint);
  185. tp.f = tp.h+tp.f;
  186. tp.state = 0
  187. addArrSort(openList,tp,compPointF);
  188. }
  189. else
  190. {
  191. var g = 1+currPoint.g
  192. if(g<tp.g)
  193. {
  194. tp.parent = currPoint;
  195. tp.g = g;
  196. tp.f = tp.g+tp.h;
  197. openList.quickSort(compPointF);
  198. }
  199. }
  200. }
  201. }
  202. }
  203. return false;
  204. }
  205. //定义openList
  206. var openList = [];
  207. //定义closeList
  208. var closeList = [];
  209. start = pathArr[start[0]][start[1]];
  210. end = pathArr[end[0]][end[1]];
  211. //添加开始节点到openList;
  212. addArrSort(openList,start,compPointF);
  213. var finded = false;
  214. var tcount = 0;
  215. while((openList.length>0))
  216. {
  217. var currPoint = openList.pop();
  218. currPoint.state = 1;
  219. closeList.push(currPoint);
  220. finded = processCurrpoint(pathArr,openList,row,col,currPoint,end);
  221. if(finded)
  222. {
  223. break;
  224. }
  225. }
  226. if(finded)
  227. {
  228. var farr = [];
  229. var tp = end.parent;
  230. farr.push(end);
  231. while(tp!=null)
  232. {
  233. farr.push(tp);
  234. tp = tp.parent;
  235. }
  236. return farr;
  237. }
  238. else
  239. {
  240. return null;
  241. }
  242. }
  243. }

  运用上面的代码,我们可以实现一个简单的迷宫寻路DEMO,用户在迷宫中点击任意的地点,蓝色的球体就会自动寻路移动到该点,如图:

  此DEMO的源码地址

  A*算法不仅可以应用在游戏当中,同样也可以应用到其他领域,比如车辆定位和行车自动导航,当然,这得需要另外的地理信息数据支持。

作者:马三小伙儿
出处:http://www.cnblogs.com/msxh/p/5674417.html 
请尊重别人的劳动成果,让分享成为一种美德,欢迎转载。另外,文章在表述和代码方面如有不妥之处,欢迎批评指正。留下你的脚印,欢迎评论!

【小白学游戏常用算法】二、A*启发式搜索算法的更多相关文章

  1. 小白学 Python 数据分析(19):Matplotlib(四)常用图表(下)

    人生苦短,我用 Python 前文传送门: 小白学 Python 数据分析(1):数据分析基础 小白学 Python 数据分析(2):Pandas (一)概述 小白学 Python 数据分析(3):P ...

  2. 小白学 Python 数据分析(17):Matplotlib(二)基础操作

    人生苦短,我用 Python 前文传送门: 小白学 Python 数据分析(1):数据分析基础 小白学 Python 数据分析(2):Pandas (一)概述 小白学 Python 数据分析(3):P ...

  3. 小白学 Python 数据分析(18):Matplotlib(三)常用图表(上)

    人生苦短,我用 Python 前文传送门: 小白学 Python 数据分析(1):数据分析基础 小白学 Python 数据分析(2):Pandas (一)概述 小白学 Python 数据分析(3):P ...

  4. 小白学 Python 爬虫(3):前置准备(二)Linux基础入门

    人生苦短,我用 Python 前文传送门: 小白学 Python 爬虫(1):开篇 小白学 Python 爬虫(2):前置准备(一)基本类库的安装 Linux 基础 CentOS 官网: https: ...

  5. 小白学 Python 爬虫(12):urllib 基础使用(二)

    人生苦短,我用 Python 前文传送门: 小白学 Python 爬虫(1):开篇 小白学 Python 爬虫(2):前置准备(一)基本类库的安装 小白学 Python 爬虫(3):前置准备(二)Li ...

  6. 小白学 Python 爬虫(34):爬虫框架 Scrapy 入门基础(二)

    人生苦短,我用 Python 前文传送门: 小白学 Python 爬虫(1):开篇 小白学 Python 爬虫(2):前置准备(一)基本类库的安装 小白学 Python 爬虫(3):前置准备(二)Li ...

  7. 小白学 Python 数据分析(13):Pandas (十二)数据表拼接

    人生苦短,我用 Python 前文传送门: 小白学 Python 数据分析(1):数据分析基础 小白学 Python 数据分析(2):Pandas (一)概述 小白学 Python 数据分析(3):P ...

  8. 小白学 Python 数据分析(3):Pandas (二)数据结构 Series

    在家为国家做贡献太无聊,不如跟我一起学点 Python 顺便问一下,你们都喜欢什么什么样的文章封面图,老用这一张感觉有点丑 人生苦短,我用 Python 前文传送门: 小白学 Python 数据分析( ...

  9. 游戏编程算法与技巧 Game Programming Algorithms and Techniques (Sanjay Madhav 著)

    http://gamealgorithms.net 第1章 游戏编程概述 (已看) 第2章 2D图形 (已看) 第3章 游戏中的线性代数 (已看) 第4章 3D图形 (已看) 第5章 游戏输入 (已看 ...

随机推荐

  1. Liunx下查看服务器硬件信息

    一.如何查看服务器的CPU 今天安装了9台Linux服务器,型号完全不一样(有DELL. HP和IBM服务器),又懒得去对清单,如何在Linux下cpu的个数和核数呢?另外,nginx的cpu工作模式 ...

  2. 挣值管理(PV、EV、AC、SV、CV、SPI、CPI) 记忆

    挣值管理法中的PV.EV.AC.SV.CV.SPI.CPI这些英文简写相信把大家都搞得晕头转向的.在挣值管理法中,需要记忆理解的有三个参数:PV.AC.EV.     PV:计划值,在即定时间点前计划 ...

  3. XML编码utf-8有中文无法解析或乱码 C#

    XML的encoding="UTF-8" ,含有中文的话(部分)会出现乱码. 网上还是很多这类问题跟解决办法的. 表现为用ie或者infopath之类的xml软件打不开这个xml, ...

  4. 面试题目——《CC150》排序与查找

    面试题11.1:给定两个排序后的数组A和B,其中A的末端有足够的缓冲空间容纳B.编写一个方法,将B合并入A并排序. package cc150.sort_search; public class Me ...

  5. 深入理解javascript原型和闭包(10)——this

    接着上一节讲的话,应该轮到“执行上下文栈”了,但是这里不得不插入一节,把this说一下.因为this很重要,js的面试题如果不出几个与this有关的,那出题者都不合格. 其实,this的取值,分四种情 ...

  6. [NHibernate]组件之依赖对象

    目录 写在前面 文档与系列文章 组件之依赖对象 一个例子 总结 写在前面 周一至周四一直在成都出差,也一直没有更新博客了,一回到家第一件事就是扒一扒最近博客园更新的文章,然后把想看的收藏了,大概有20 ...

  7. mysql 存储过程在批处理数据中的应用

    最近批处理数据的时候,突然想到:为什么不使用存储过程进行数据批处理? 为什么要进行批处理? 自答:减少数据库连接次数,提高效率. 存储过程批处理数据的优点:一次编译,永久执行. 这次的批处理逻辑较简单 ...

  8. Android 基础篇(二)

    ADB进程 adb指令 adb install xxx.apk adb uninstall 包名 adb devices adb start-server adb kill-server adb sh ...

  9. 上个项目的一些反思 I

    最近一直在反思之前的项目,发现了很多问题.比如数据安全... 虽然项目需求是只展示最新的数据,所以几乎没用什么本地存储.除了通讯录和用户的Token. 用户通讯录另表,今天反思下用户的Token的存储 ...

  10. JavaScript——基本的瀑布流布局及ajax动态新增数据

    本文用纯js代码手写一个瀑布流网页效果,初步实现一个基本的瀑布流布局,以及滚动到底部后模拟ajax数据加载新图片功能. 缺点: 1. 程序不是响应式,不能实时调整页面宽度: 2. 程序中当新增ajax ...