在复杂的 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 }
============================================================================================================