在复杂的 3D 游戏环境中如何能使非玩家控制角色准确实现自动寻路功能成为了 3D 游戏开 发技术中一大研究热点。其中 A*算法得到了大量的运用,A*算法较之传统的路径规划算法,实时性更高、灵活性更强,寻路 结果更加接近人工选择的路径结果. A*寻路算法并不是找到最优路径,只是找到相对近的路径,因为找最优要把所有可行 路径都找出来进行对比,消耗性能太大,寻路效果只要相对近路径就行了。

1.A* 算法的原理(如显示不全 刷新重试)

我们假设在推箱子游戏中人要从站里的地方移动到右侧的箱子目的地,但是这两点之间被一堵墙隔开。

我们下一步要做的便是查找最短路径。既然是 AI 算法, A* 算法和人寻找路径的做法十分类似,当我们离目标较远时,我 们的目标方向是朝向目的点直线移动,但是在近距离上因为各种障碍需要绕行(走弯路)!而且已走过的地方就无须再次 尝试。

为了简化问题,我们把要搜寻的区域划分成了正方形的格子。这是寻路的第一步,简化搜索区域,就像推箱子游戏一样。 这样就把我们的搜索区域简化为了 2 维数组。数组的每一项代表一个格子,它的状态就是可走 (walkalbe) 和不可走 (unwalkable) 。通过计算出从起点到终点需要走过哪些方格,就找到了路径。一旦路径找到了,人物便从一个方格的中心 移动到另一个方格的中心,直至到达目的地。

简化搜索区域以后,如何定义小人当前走要走的格子离终点是近是远呢?我们需要两个指标来表示:

  • G 表示从起点移动到网格上指定方格的移动距离 (暂时不考虑沿斜向移动,只考虑上下左右移动)。
  • H 表示从指定的方格移动到终点的预计移动距离,只计算直线距离 (H 有很多计算方法, 这里我们设定只可以上 下左右移动,即该点与终点的直线距离)。

令 F = G + H ,F 即表示从起点经过此点预计到终点的总移动距离接下来我们从起点开始,按照以下寻路步骤,直至找到目标。

2.寻路步骤

1. 从起点开始, 把它作为待处理的方格存入一个预测可达的节点列表,简称 openList, 即把起点放入“预测可达节点列表”, 可达节点列表 openList 就是一个等待检查方格的列表。

2. 寻找 openList 中 F 值最小的点 min(一开始只有起点)周围可以到达的方格(可到达的意思是其不是障碍物,也不存 在关闭列表中的方格,即不是已走过的方格)。计算 min 周围可到达的方格的 F 值。将还没在 openList 中点放入其中, 并 设置它们的"父方格"为点 min,表示他们的上一步是经过 min 到达的。如果 min 下一步某个可到达的方格已经在 openList 列表那么并且经 min 点它的 F 值更优,则修改 F 值并把其"父方格"设为点 min。

3. 把 2 中的点 min 从"开启列表"中删除并存入"关闭列表"closeList 中, closeList 中存放的都是不需要再次检查的方格。如 果 2 中点 min 不是终点并且开启列表的数量大于零,那么继续从第 2 步开始。如果是终点执行第 4 步,如果 openList 列 表数量为零,那么就是找不到有效路径。

4.如果第 3 步中 min 是终点,则结束查找,直接追溯父节点到起点的路径即为所选路径

具体寻路步步骤如下所示:

3.算法实现

Astar.h

 1 #pragma once
2
3 #include <list>
4
5 const int kCost1 = 10; //直移一格消耗
6 const int kCost2 = 14; //斜移一格消耗
7
8 typedef struct _Point
9 {
10 int x,y; //点坐标,这里为了方便按照 C++的数组来计算,x 代表横排,y 代表竖列
11 int F,G,H; //F=G+H
12 struct _Point *parent; //parent 的坐标
13 }Point;
14
15 /*分配一个节点(格子)*/
16 Point* AllocPoint(int x, int y);
17
18 /*初始化地图*/
19 void InitAstarMaze(int *_maze, int _lines, int _colums);
20
21 /*通过 A* 算法寻找路径*/
22 std::list<Point *> GetPath(Point *startPoint, Point *endPoint);
23
24 /*清理资源,结束后必须调用*/
25 void ClearAstarMaze();

Astar.cpp

  1 #include <math.h>
2 #include "Astar.h"
3 #include <iostream>
4 #include <vector>
5
6 static int *maze; //迷宫对应的二维数组,使用一级指针表示
7 static int cols; //二维数组对应的列数
8 static int lines; //二维数组对应的行数
9 static std::list<Point *> openList; //开放列表
10 static std::list<Point *> closeList; //关闭列表
11
12 /*搜索从起点到终点的最佳路径*/
13 static Point* findPath(Point *startPoint,Point *endPoint) ;
14 /*从开启列表中返回 F 值最小的节点*/
15 static Point *getLeastFpoint();
16 /*获取当前点周围可达的节点*/
17 static std::vector<Point *> getSurroundPoints(const Point *point);
18 /*判断某点是否可以用于下一步判断 */
19 static bool isCanreach(const Point *point,const Point *target);
20 /*判断开放/关闭列表中是否包含某点*/
21 static Point *isInList(const std::list<Point *> &list,const Point *point);
22 //计算 FGH 值
23 static int calcG(Point *temp_start,Point *point);
24 static int calcH(Point *point,Point *end);
25 static int calcF(Point *point);
26
27 /*分配一个节点(格子)*/
28 Point* AllocPoint(int x, int y)
29 {
30 Point *temp = new Point;
31 memset(temp, 0, sizeof(Point)); //初始值清零
32 temp->x = x;
33 temp->y = y;
34 return temp;
35 }
36
37 /*初始化 A*搜索的地图*/
38 void InitAstarMaze(int *_maze, int _lines, int _colums)
39 {
40 maze = _maze;
41 lines = _lines;
42 cols = _colums;
43 }
44
45 /*通过 A* 算法寻找路径*/
46 std::list<Point *> GetPath(Point *startPoint, Point *endPoint)
47 {
48 Point *result=findPath(startPoint, endPoint);
49 std::list<Point *> path;
50
51 //返回路径,如果没找到路径,返回空链表
52 while(result)
53 {
54 path.push_front(result);
55 result=result->parent;
56 }
57 return path;
58 }
59
60 /*搜索从起点到终点的最佳路径*/
61 static Point* findPath(Point *startPoint,Point *endPoint)
62 {
63 openList.push_back(AllocPoint(startPoint->x, startPoint->y)); //置入起点,拷贝开辟一个节点,内外隔离
64 while(!openList.empty())
65 {
66 //第一步,从开放列表中取最小 F 的节点
67 Point *curPoint = getLeastFpoint(); //找到 F 值最小的点
68
69 //第二步,把当前节点放到关闭列表中
70 openList.remove(curPoint);
71 closeList.push_back(curPoint);
72
73 //第三步,找到当前节点周围可达的节点,并计算 F 值
74 std::vector<Point *> surroundPoints = getSurroundPoints(curPoint);
75 std::vector<Point *>::const_iterator iter;
76
77 for(iter=surroundPoints.begin();iter!=surroundPoints.end(); iter++)
78 {
79 Point *target = *iter;
80
81 //对某一个格子,如果它不在开放列表中,加入到开启列表,设置当前格为其父节点,计算 F G H
82 Point *exist = isInList(openList, target);
83 if(!exist)
84 {
85 target->parent=curPoint;
86 target->G=calcG(curPoint,target);
87 target->H=calcH(target,endPoint);
88 target->F=calcF(target);
89 openList.push_back(target);
90 }
91 else
92 {
93 int tempG = calcG(curPoint, target);
94 if(tempG<target->G)
95 {
96 exist->parent = curPoint;
97 exist->G=tempG;
98 exist->F=calcF(target);
99 }
100 delete target;
101 }
102 }//end for
103
104 surroundPoints.clear();
105 Point *resPoint = isInList(openList, endPoint);
106 if(resPoint)
107 {
108 return resPoint;
109 }
110 }
111 return NULL;
112 }
113
114 /*从开启列表中返回 F 值最小的节点*/
115 static Point *getLeastFpoint()
116 {
117 if(!openList.empty())
118 {
119 Point *resPoint = openList.front();
120 std::list<Point*>::const_iterator itor;
121
122 for(itor = openList.begin(); itor!= openList.end(); itor++)
123 {
124 if((*itor)->F < resPoint->F)
125 {
126 resPoint = *itor;
127 }
128 }
129 return resPoint;
130 }
131 return NULL;
132 }
133
134 /*获取当前点周围可达的节点*/
135 static std::vector<Point *> getSurroundPoints(const Point *point)
136 {
137 std::vector<Point *> surroundPoints;
138 for(int x=point->x-1; x<=point->x+1; x++)
139 {
140 for(int y=point->y-1; y<=point->y+1; y++)
141 {
142 Point *temp = AllocPoint(x, y);
143 if(isCanreach(point, temp))
144 {
145 surroundPoints.push_back(temp);
146 }
147 else
148 {
149 delete temp;
150 }
151 }
152 }
153 return surroundPoints;
154 }
155
156 /*判断某点是否可以用于下一步判断 */
157 static bool isCanreach(const Point *point,const Point *target)
158 {
159 if(target->x<0||target->x>(lines-1)
160 ||target->y<0||target->y>(cols-1)
161 ||maze[target->x *cols + target->y]==1
162 ||maze[target->x *cols + target->y]==2
163 ||(target->x==point->x && target->y==point->y)
164 ||isInList(closeList, target))
165 {
166 return false;
167 }
168 if(abs(point->x-target->x)+abs(point->y-target->y)==1)
169 {
170 return true;
171 }
172 else
173 {
174 return false;
175 }
176 }
177 static Point* isInList(const std::list<Point *> &list,const Point *point)
178 {
179 //判断某个节点是否在列表中,这里不能比较指针,因为每次加入列表是新开辟的节点,只能比较坐标
180 std::list<Point *>::const_iterator itor;
181 for(itor = list.begin(); itor!=list.end();itor++)
182 {
183 if((*itor)->x==point->x&&(*itor)->y==point->y)
184 {
185 return *itor;
186 }
187 }
188 return NULL;
189 }
190
191 /*计算节点的 G 值*/
192 static int calcG(Point *temp_start, Point *point)
193 {
194 int extraG=(abs(point->x-temp_start->x)+abs(point->y-temp_start->y))==1?kCost1:kCost2;
195
196 //如果是初始节点,则其父节点是空
197 int parentG=(point->parent==NULL? NULL:point->parent->G);
198
199 return parentG+extraG;
200 }
201
202 static int calcH(Point *point,Point *end)
203 {
204 //用简单的欧几里得距离计算 H,可以用多中方式实现
205 return (int)sqrt((double)(end->x-point->x)*
206 (double)(end->x-point->x)+(double)(end->y-point->y)*
207 (double)(end->y-point->y))*kCost1;
208 }
209
210 /*计算节点的 F 值*/
211 static int calcF(Point *point)
212 {
213 return point->G+ point->H;
214 }
215
216 /*清理资源,结束后必须调用*/
217 void ClearAstarMaze()
218 {
219 maze = NULL;
220 lines = 0;
221 cols = 0;
222 std::list<Point *>::iterator itor;
223
224 //清除 openList 中的元素
225 for(itor = openList.begin(); itor!=openList.end();)
226 {
227 delete *itor;
228 itor = openList.erase(itor);//获取到下一个节点
229 }
230
231 //清理 closeList 中的元素
232 for(itor = closeList.begin(); itor!=closeList.end();)
233 {
234 delete *itor;
235 itor = closeList.erase(itor);
236 }
237 }

main.cpp

 1 #include "Astar.h"
2 #include <list>
3 #include <iostream>
4 #include <windows.h>
5
6 using namespace std;
7
8 //定义地图数组,二维数组在内存顺序存储的
9 int map[13][13] = {
10 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
11 { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, },
12 { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, },
13 { 0, 1, 0, 1, 0, 1, 2, 1, 0, 1, 0, 1, 0, },
14 { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, },
15 { 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, },
16 { 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, },
17 { 2, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 2, },
18 { 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, },
19 { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, },
20 { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, },
21 { 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, },
22 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }
23 };
24
25 void AStarTest()
26 {
27 InitAstarMaze(&map[0][0], 13, 13);
28
29 //设置起始和结束点
30 Point* start = AllocPoint(12, 4);
31 Point* end = AllocPoint(0, 0);
32
33 //A*算法找寻路径
34 list<Point *> path = GetPath(start, end);
35 cout<<"寻路结果:"<<endl;
36 list<Point *>::const_iterator iter;
37
38 for(iter=path.begin(); iter!=path.end(); iter++)
39 {
40 Point *cur = *iter;
41 cout<<'('<<cur->x<<','<<cur->y<<')'<<endl;
42 Sleep(800);
43 }
44
45 ClearAstarMaze();
46 }
47
48 int main(void)
49 {
50 AStarTest();
51 system("pause");
52 return 0;
53 }

============================================================================================================

数据结构与算法——图(游戏中的自动寻路-A*算法)的更多相关文章

  1. 游戏中的自动寻路-A*算法(第一版优化——走斜线篇)

    一.简述以及地图 G 表示从起点移动到网格上指定方格的移动距离 (暂时不考虑沿斜向移动,只考虑上下左右移动). H 表示从指定的方格移动到终点的预计移动距离,只计算直线距离,走直角篇走的是直角路线. ...

  2. 如何在Cocos2D游戏中实现A*寻路算法(一)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...

  3. 如何在Cocos2D游戏中实现A*寻路算法(八)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...

  4. 如何在Cocos2D游戏中实现A*寻路算法(六)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...

  5. 如何在Cocos2D游戏中实现A*寻路算法(四)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...

  6. python数据结构与算法——图的广度优先和深度优先的算法

    根据维基百科的伪代码实现: 广度优先BFS: 使用队列,集合 标记初始结点已被发现,放入队列 每次循环从队列弹出一个结点 将该节点的所有相连结点放入队列,并标记已被发现 通过队列,将迷宫路口所有的门打 ...

  7. 如何在Cocos2D游戏中实现A*寻路算法(二)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...

  8. 如何在Cocos2D游戏中实现A*寻路算法(七)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...

  9. 如何在Cocos2D游戏中实现A*寻路算法(五)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...

随机推荐

  1. ntpd配置时间同步服务器

    修改同步服务器的配置文件/etc/ntp.conf ,删除所有的内容,添加 restrict default nomodify server 127.127.1.0 # local clock fud ...

  2. (二)廖师兄springboot微信点餐虚拟机说明文档

    虚拟机 VirtualBox-5.1.22  系统 CentOS7.3账号 root密码 123456 软件:jdk 1.8.0_111nginx 1.11.7mysql 5.7.17redis 3. ...

  3. 深入浅出!springboot从入门到精通,实战开发全套教程!

    前言 之前一直有粉丝想让我出一套springboot实战开发的教程,我这边总结了很久资料和经验,在最近总算把这套教程的大纲和内容初步总结完毕了,这份教程从springboot的入门到精通全部涵盖在内, ...

  4. Fruity Parametric EQ 2使用说明(二)——FL Studio插件教程

    Fruity Parametric EQ 2均衡器,是一款我们在FL Studio制作音乐时经常会用到的插件,它是EQ中的战斗鸡,它不仅有一个高级的 7 波段参数均衡器,还具有声谱分析能力.我们在对很 ...

  5. 类虚拟机软件CrossOver是什么?它的优势在哪里?

    虚拟机软件对于很多人来说已经不是一个陌生的词汇了.我们可以通过软件来模拟具有完整硬件系统功能的计算机系统.比如我们可以在Mac OS系统上模拟Windows 7 的系统,以此来安装我们想要使用的应用程 ...

  6. ubuntu安装php的 redis扩展

    wget https://github.com/phpredis/phpredis/archive/2.2.4.tar.gztar -zxvf 2.2.4.tar.gz cd phpredis-2.2 ...

  7. 执行文件异常报错:ImportError: attempted relative import with no known parent package

    这个问题困扰了我很久了,网上的解决方法都很一致,找来找去都是一样的解决方法,在导入包的文件和执行文件加入 1 print('__file__={0:<35} | __name__={1:< ...

  8. 配置jdb

    目录 注:1)查看当前Linux系统是否已经安装java 1.把jdk文件的压缩包拖入虚拟机 2.找到刚刚拖拽的文件 3.在usr/local下创建jdk的文件夹 4.解压jdk的文件,并存放在刚刚创 ...

  9. Apache Hudi初学者指南

    在深入研究Hudi机制之前,让我们首先了解Hudi正在解决的问题. 客户在使用数据湖时通常会问一个问题:当源记录被更新时,如何更新数据湖?这是一个很难解决的问题,因为一旦你写了CSV或Parquet文 ...

  10. Windows生产力工具推荐

    相信大部分同学还是Windows用户,作为一个长期Windows/MacOS双系统长期用户,Windows在用的好,工作效率也很高,下面就推荐几款Windows下面的生产力工具. utools 用过M ...