A*寻路算法

作者:追风剑情 发布于:2016-8-13 11:36 分类:Algorithms

一、定义地图文件格式

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5.  
  6. namespace MapEditor.Map
  7. {
  8. /// <summary>
  9. /// 地图文件
  10. /// </summary>
  11. public class MapFile
  12. {
  13. public MapHeader header = new MapHeader();
  14. public Grid[] grids;
  15. }
  16.  
  17. /// <summary>
  18. /// 地图文件头信息
  19. /// </summary>
  20. public class MapHeader
  21. {
  22. public int version = 1;
  23. public int type = 1;
  24. public string name = "未命名";
  25. public int width = 1080;
  26. public int height = 960;
  27. public int tileWidth = 64;
  28. public int tileHeight = 32;
  29. //2.5D行数和列数
  30. public int rows = 10;
  31. public int cols = 10;
  32. //2D行数和列数
  33. public int rows2d = 10;
  34. public int cols2d = 10;
  35. public string background = "";
  36. }
  37.  
  38. /// <summary>
  39. /// 网格
  40. /// </summary>
  41. public class Grid
  42. {
  43. //网格索引
  44. public int index = 0;
  45. //网格像素坐标
  46. public int px = 0;
  47. public int py = 0;
  48. //网格坐标
  49. public int gx = 0;
  50. public int gy = 0;
  51. //权值
  52. public int weight = 0;
  53. //贴图名称
  54. public string tile = "";
  55.  
  56. #region 以下字段用于A*寻路
  57. //上一个节点索引
  58. public int prev = 0;
  59. //起点到当前点的移动代价值
  60. public int g = 0;
  61. //当前点到终点的移动代价值
  62. public int h = 0;
  63. //起点到终点的移动代价值
  64. public int f = 0;
  65. #endregion
  66. }
  67. }

 

二、写个像素坐标与2.5D网格坐标相互转换的工具类

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Drawing;
  6.  
  7. namespace MapEditor.Map
  8. {
  9. /// <summary>
  10. /// 2.5D地图坐标转换工具类
  11. /// </summary>
  12. public class CoordinateUtil
  13. {
  14. /// <summary>
  15. /// 像素坐标转网格坐标
  16. /// </summary>
  17. /// <param name="tileWidth">网格宽度</param>
  18. /// <param name="tileHeight">网格高度</param>
  19. /// <param name="px">像素x坐标</param>
  20. /// <param name="py">像素y坐标</param>
  21. /// <returns></returns>
  22. public static Point GetCellPoint(int tileWidth, int tileHeight, int px, int py)
  23. {
  24. int xtile = 0; //网格的x坐标
  25. int ytile = 0; //网格的y坐标
  26. int cx, cy, rx, ry;
  27. cx = (int)(px / tileWidth) * tileWidth + tileWidth/2; //计算出当前X所在的以tileWidth为宽的矩形的中心的X坐标
  28. cy = (int)(py / tileHeight) * tileHeight + tileHeight/2;//计算出当前Y所在的以tileHeight为高的矩形的中心的Y坐标
  29. rx = (px - cx) * tileHeight/2;
  30. ry = (py - cy) * tileWidth/2;
  31. if (Math.Abs(rx)+Math.Abs(ry) <= tileWidth * tileHeight/4)//<=上三角面积
  32. {
  33. xtile = (int)(px / tileWidth);
  34. ytile = (int)(py / tileHeight) * 2;
  35. }
  36. else
  37. {//偶行
  38. px = px - tileWidth/2;
  39. xtile = (int)(px / tileWidth) + 1;
  40. py = py - tileHeight/2;
  41. ytile = (int)(py / tileHeight) * 2 + 1;
  42. }
  43.  
  44. return new Point(xtile - (ytile&1), ytile);
  45. }
  46.  
  47. /// <summary>
  48. /// 网格坐标转像素坐标
  49. /// </summary>
  50. /// <param name="tileWidth">网格宽度</param>
  51. /// <param name="tileHeight">网格高度</param>
  52. /// <param name="tx">网格x坐标</param>
  53. /// <param name="ty">网格y坐标</param>
  54. /// <returns></returns>
  55. public static Point GetPixelPoint(int tileWidth, int tileHeight, int tx, int ty)
  56. {
  57. //偶数行tile中心
  58. int tileCenter = (tx * tileWidth) + tileWidth/2;
  59. // x象素 如果为奇数行加半个宽
  60. int xPixel = tileCenter + (ty&1) * tileWidth/2;
  61. // y象素
  62. int yPixel = (ty + 1) * tileHeight/2;
  63. return new Point(xPixel, yPixel);
  64. }
  65.  
  66. /// <summary>
  67. /// 根据网格索引取得网格坐标
  68. /// </summary>
  69. /// <param name="gindex">网格索引</param>
  70. /// <param name="cols">网格列数</param>
  71. /// <param name="rows">网格行数</param>
  72. public static Point GetICellPoint( int gindex, int cols )
  73. {
  74. int xtile = gindex % cols;
  75. int ytile = gindex / cols;
  76. return new Point(xtile, ytile);
  77. }
  78. /// <summary>
  79. /// 根据网格索引取得象素坐标
  80. /// </summary>
  81. /// <param name="gindex">网格索引</param>
  82. /// <param name="cols">网格列数</param>
  83. /// <param name="rows">网格行数</param>
  84. /// <param name="tileWidth">网格宽度</param>
  85. /// <param name="tileHeight">网格高度</param>
  86. /// <returns></returns>
  87. public static Point GetIPixelPoint( int gindex, int cols, int rows, int tileWidth, int tileHeight )
  88. {
  89. Point cellPoint = GetICellPoint(gindex, cols);
  90. Point pixelPoint = GetPixelPoint(tileWidth, tileHeight, cellPoint.X, cellPoint.Y);
  91. return pixelPoint;
  92. }
  93. /// <summary>
  94. /// 根据网格坐标取得网格索引
  95. /// </summary>
  96. /// <param name="gx">网格x坐标</param>
  97. /// <param name="gy">网格y坐标</param>
  98. /// <param name="cols">网格列数</param>
  99. /// <returns></returns>
  100. public static int GetCellIndex( int gx, int gy, int cols )
  101. {
  102. int gindex = gy * cols + gx;
  103. return gindex;
  104. }
  105.  
  106. /// <summary>
  107. /// 根据像素坐标取得网格索引
  108. /// </summary>
  109. public static int GetIndexPixel(int px, int py, int tileWidth, int tileHeight, int cols)
  110. {
  111. Point gp = GetCellPoint(tileWidth, tileHeight, px, py);
  112. int index = GetCellIndex(gp.X, gp.Y, cols);
  113. return index;
  114. }
  115. }
  116. }

 

三、A*算法实现

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Drawing;
  6.  
  7. namespace MapEditor.Map
  8. {
  9. /// <summary>
  10. /// A*寻路
  11. /// </summary>
  12. public class AStar
  13. {
  14. //最大搜索步数
  15. public static int MAX_FIND_STEP = 5000;
  16. //开启列表
  17. HeapSort openList = new HeapSort();
  18. //关闭列表
  19. Dictionary<int, bool> closeList = new Dictionary<int, bool>();
  20.  
  21. public List<Grid> Find(MapFile map, Grid start, Grid end)
  22. {
  23. Console.WriteLine("target index="+end.index);
  24.  
  25. if (start.index == end.index)
  26. {
  27. start.prev = -1;
  28. return BackTrack(map, start);
  29. }
  30.  
  31. List<Grid> path = null;
  32. int step = 0;
  33.  
  34. openList.Clear();
  35. closeList.Clear();
  36. start.prev = -1;
  37. start.g = 0;
  38. Grid curGrid = start;
  39. openList.Push(curGrid);
  40.  
  41. while (curGrid != null)
  42. {
  43. //(1)从开启列表中取出f值最小的节点
  44. curGrid = openList.Shift();
  45. Console.WriteLine("Shift index={0}, f={1}", curGrid.index, curGrid.f);
  46. //(2)当前节点的周围节点加入到开启列表
  47. Grid target = PushAroundOpenList(map, curGrid, end);
  48. if (null != target) {//是否搜索到目标格
  49. path = BackTrack(map, target);
  50. break;
  51. }
  52. //(3)当前节点加入到关闭列表
  53. if (!closeList.ContainsKey(curGrid.index))
  54. closeList.Add(curGrid.index, true);
  55.  
  56. step++;
  57. if (step > MAX_FIND_STEP)
  58. {
  59. Console.WriteLine("停止搜索,超过最大搜索步数 MAX_FIND_STEP=" + MAX_FIND_STEP);
  60. break;
  61. }
  62. }
  63.  
  64. return path;
  65. }
  66.  
  67. //把周围的网格加入开启列表
  68. Grid PushAroundOpenList(MapFile map, Grid cur, Grid end)
  69. {
  70. //计算出周围8向网格的索引号
  71. int cols = map.header.cols;
  72. int rows = map.header.rows;
  73. int maxIndex = cols * rows - 1;//地图网格最大索引号
  74.  
  75. int index_u = cur.index - cols*2;//上
  76. int index_d = cur.index + cols*2;//下
  77. int index_l = cur.index - 1;//左
  78. int index_r = cur.index + 1;//右
  79. int index_lu=0, index_ld=0, index_ru=0, index_rd=0;
  80. if ((cur.gy & 1) > 0)//奇行
  81. {
  82. index_lu = cur.index - cols;//左上
  83. index_ld = cur.index + cols;//左下
  84. index_ru = cur.index - cols + 1;//右上
  85. index_rd = cur.index + cols + 1;//右下
  86. }
  87. else
  88. {
  89. index_lu = cur.index - cols - 1;//左上
  90. index_ld = cur.index + cols - 1;//左下
  91. index_ru = cur.index - cols;//右上
  92. index_rd = cur.index + cols;//右下
  93. }
  94.  
  95. //判断周围节点中是否包含终点
  96. int[] around = new int[8] { index_u, index_d, index_l, index_r, index_lu, index_ld, index_ru, index_rd };
  97. for (int i = 0; i < around.Length; i++)
  98. {
  99. if (around[i] == end.index)
  100. {
  101. end.prev = cur.index;
  102. return end;
  103. }
  104. }
  105.  
  106. //周围网格加入开启列表
  107. if (!closeList.ContainsKey(index_u) && index_u >= 0 && map.grids[index_u].weight == 0)
  108. CheckInOpenList(cur, map.grids[index_u], end);
  109. if (!closeList.ContainsKey(index_d) && index_d <= maxIndex && map.grids[index_d].weight == 0)
  110. CheckInOpenList(cur, map.grids[index_d], end);
  111. if (!closeList.ContainsKey(index_l) && index_l >= 0 && map.grids[index_l].weight == 0)
  112. CheckInOpenList(cur, map.grids[index_l], end);
  113. if (!closeList.ContainsKey(index_r) && index_r <= maxIndex && map.grids[index_r].weight == 0)
  114. CheckInOpenList(cur, map.grids[index_r], end);
  115.  
  116. if (!closeList.ContainsKey(index_lu) && index_lu >= 0 && map.grids[index_lu].weight == 0)
  117. CheckInOpenList(cur, map.grids[index_lu], end);
  118. if (!closeList.ContainsKey(index_ld) && index_ld >= 0 && index_ld <= maxIndex && map.grids[index_ld].weight == 0)
  119. CheckInOpenList(cur, map.grids[index_ld], end);
  120. if (!closeList.ContainsKey(index_ru) && index_ru >= 0 && map.grids[index_ru].weight == 0)
  121. CheckInOpenList(cur, map.grids[index_ru], end);
  122. if (!closeList.ContainsKey(index_rd) && index_rd >= 0 && index_rd <= maxIndex && map.grids[index_rd].weight == 0)
  123. CheckInOpenList(cur, map.grids[index_rd], end);
  124.  
  125. return null;
  126. }
  127.  
  128. void CheckInOpenList(Grid cur, Grid ag, Grid end, int g=1)
  129. {
  130. if (openList.Contains(ag.index))
  131. {
  132. if (cur.g + g < ag.g)
  133. {
  134. ag.g = cur.g + g;
  135. ag.prev = cur.index;
  136. }
  137. }
  138. else
  139. {
  140. ag.prev = cur.index;
  141. ag.g = cur.g + g;
  142. ag.h = ChebyshevDistance(ag.px, ag.py, end.px, end.py);
  143. ag.f = ag.g + ag.h;
  144. openList.Push(ag);
  145. }
  146. }
  147.  
  148. //路径回溯
  149. List<Grid> BackTrack(MapFile map, Grid end)
  150. {
  151. List<Grid> list = new List<Grid>();
  152. Grid cur = end;
  153. while(cur.prev != -1){
  154. Console.Write("{0}(px:{1},py:{2})->", cur.index, cur.px, cur.py);
  155. list.Add(cur);
  156. cur = map.grids[cur.prev];
  157. }
  158. list.Add(cur);
  159.  
  160. if (list.Count > 2)
  161. SmoothPath(map, list);
  162.  
  163. return list;
  164. }
  165.  
  166. #region 距离评估函数
  167. //曼哈顿距离
  168. int ManhattanDistance(int x1, int y1, int x2, int y2)
  169. {
  170. return Math.Abs(x2 - x1) + Math.Abs(y2 - y1);
  171. }
  172.  
  173. //欧氏几何平面距离(欧几里得距离)
  174. int EuclideanDistance(int x1, int y1, int x2, int y2)
  175. {
  176. return (int)Math.Sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
  177. }
  178.  
  179. int EuclideanSquareDistance(int x1, int y1, int x2, int y2)
  180. {
  181. return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
  182. }
  183.  
  184. //切比雪夫距离
  185. int ChebyshevDistance(int x1, int y1, int x2, int y2)
  186. {
  187. return Math.Max(Math.Abs(x2-x1), Math.Abs(y2-y1));
  188. }
  189. #endregion
  190.  
  191. #region 路径平滑处理
  192. void SmoothPath(MapFile map, List<Grid> path)
  193. {
  194. RemoveStraightLine(map, path);
  195. RemoveTurn(map, path);
  196. }
  197.  
  198. //移除同线的节点
  199. void RemoveStraightLine(MapFile map, List<Grid> path)
  200. {
  201. int i = 0;
  202. int count;
  203. while (i < (count= path.Count) - 2)
  204. {
  205. for (int j = i + 1, k = i + 2; k < count; j++, k++)
  206. {
  207. if (path[j].px - path[j - 1].px != path[k].px - path[j].px ||
  208. path[j].py - path[j - 1].py != path[k].py - path[j].py)
  209. {
  210. int n = j - i - 1;
  211. if(n > 0)
  212. path.RemoveRange(i + 1, n);
  213. break;
  214. }
  215. }
  216. i++;
  217. }
  218. }
  219.  
  220. //移除多余拐点
  221. void RemoveTurn(MapFile map, List<Grid> path)
  222. {
  223. int i = 0;
  224. int j = path.Count - 1;
  225. while (j > i + 1)
  226. {
  227. for (; j > 0; j--)
  228. {
  229. if(j <= i + 1)
  230. break;
  231.  
  232. if (VisualDetection(map, path[i], path[j]))
  233. {
  234. int n = j - i - 1;//j - (i + 1);
  235. for (int k = 0; k < n; k++)
  236. Console.WriteLine("移除拐点 {0}", path[i+1+k].index);
  237. path.RemoveRange(i + 1, n);
  238. break;
  239. }
  240. }
  241.  
  242. i++;
  243. j = path.Count - 1;
  244. }
  245. }
  246.  
  247. //可视探测
  248. bool VisualDetection(MapFile map, Grid g1, Grid g2)
  249. {
  250. int d = map.header.tileHeight / 2;//探测距离增量
  251. int dline = (int)CalLineDistance(g1.px, g1.py, g2.px, g2.py);
  252. int dtimes = dline / d;//两拐点间探测次数
  253. int deltaX = (g2.px - g1.px) / dtimes;
  254. int deltaY = (g2.py - g1.py) / dtimes;
  255. int dx = g1.px, dy = g1.py;//当前探测点坐标
  256.  
  257. Console.WriteLine("开始探测: ");
  258. while (dtimes > 0)
  259. {
  260. dx += deltaX;
  261. dy += deltaY;
  262. int index = CoordinateUtil.GetIndexPixel(dx, dy, map.header.tileWidth, map.header.tileHeight, map.header.cols);
  263. Grid g = map.grids[index];
  264. Console.Write(index + "->");
  265. if (g.weight > 0)
  266. return false;
  267. dtimes--;
  268. }
  269. return true;
  270. }
  271.  
  272. //计算两点间距离
  273. double CalLineDistance(int x1, int y1, int x2, int y2)
  274. {
  275. return Math.Sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
  276. }
  277. #endregion
  278. }
  279.  
  280. /// <summary>
  281. /// 二叉堆
  282. /// </summary>
  283. class HeapSort
  284. {
  285. List<Grid> R;
  286. //声明个字典用来快速检查元素是否已在开启列表中。
  287. Dictionary<int, bool> openList = new Dictionary<int, bool>();
  288.  
  289. public HeapSort()
  290. {
  291. R = new List<Grid>();
  292. //堆排序不会用到0号元素,这里设个占位符。
  293. R.Add(null);
  294. }
  295.  
  296. public void Clear()
  297. {
  298. openList.Clear();
  299. R.Clear();
  300. R.Add(null);
  301. }
  302.  
  303. public bool Contains(int index)
  304. {
  305. return openList.ContainsKey(index);
  306. }
  307.  
  308. //筛选方法
  309. private void SiftLess(List<Grid> R, int low, int high)
  310. {
  311. int i = low, j = 2 * i;
  312. Grid tmp = R[i];
  313. while (j <= high)
  314. {
  315. if (j < high && R[j].f > R[j + 1].f)
  316. {
  317. j++;
  318. }
  319. if (tmp.f > R[j].f)
  320. {
  321. R[i] = R[j];
  322. i = j;
  323. j = 2 * i;
  324. }
  325. else
  326. {
  327. break;
  328. }
  329. }
  330. R[i] = tmp;
  331. }
  332.  
  333. //添加元素
  334. public void Push(Grid value)
  335. {
  336. if (openList.ContainsKey(value.index))
  337. return;
  338.  
  339. openList.Add(value.index, true);
  340. R.Add(value);
  341. int high = R.Count - 1;
  342. int i = high / 2;//最后一个元素的父节点索引
  343. int j = high;//最后一个元素索引
  344. Grid tmp;
  345.  
  346. while (i >= 1)
  347. {
  348. if (R[i].f > R[j].f)
  349. {
  350. tmp = R[i];
  351. R[i] = R[j];
  352. R[j] = tmp;
  353. j = i;
  354. i /= 2;
  355. }
  356. else
  357. {
  358. break;
  359. }
  360. }
  361. Console.WriteLine("Push[1]="+R[1].f);
  362. }
  363.  
  364. //移除根元素
  365. public Grid Shift()
  366. {
  367. int high = R.Count - 1;
  368.  
  369. if (high < 1)
  370. return null;
  371.  
  372. Grid r1;
  373. if (high <= 1)
  374. {
  375. r1 = R[1];
  376. R.RemoveAt(1);
  377. openList.Remove(r1.index);
  378. return r1;
  379. }
  380.  
  381. r1 = R[1];
  382. R[1] = R[high];
  383. openList.Remove(r1.index);
  384. R.RemoveAt(high);
  385. SiftLess(R, 1, high - 1);
  386.  
  387. return r1;
  388. }
  389. }
  390. }

 

运行测试

111111.png

 

Demo工程

360云盘分享

https://yunpan.cn/c6IHhimkdh6Xw  访问密码 5039

 

标签: Algorithms

Powered by emlog  蜀ICP备18021003号-1   sitemap

川公网安备 51019002001593号