在复杂的 3D 游戏环境中如何能使非玩家控制角色准确实现自动寻路功能成为了 3D 游戏开 发技术中一大研究热点。其中 A*算法得到了大量的运用,A*算法较之传统的路径规划算法,实时性更高、灵活性更强,寻路 结果更加接近人工选择的路径结果. A*寻路算法并不是找到最优路径,只是找到相对近的路径,因为找最优要把所有可行 路径都找出来进行对比,消耗性能太大,寻路效果只要相对近路径就行了。
我们下一步要做的便是查找最短路径。既然是 AI 算法, A* 算法和人寻找路径的做法十分类似,当我们离目标较远时,我 们的目标方向是朝向目的点直线移动,但是在近距离上因为各种障碍需要绕行(走弯路)!而且已走过的地方就无须再次 尝试。
为了简化问题,我们把要搜寻的区域划分成了正方形的格子。这是寻路的第一步,简化搜索区域,就像推箱子游戏一样。 这样就把我们的搜索区域简化为了 2 维数组。数组的每一项代表一个格子,它的状态就是可走 (walkalbe) 和不可走 (unwalkable) 。通过计算出从起点到终点需要走过哪些方格,就找到了路径。一旦路径找到了,人物便从一个方格的中心 移动到另一个方格的中心,直至到达目的地。
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 列 表数量为零,那么就是找不到有效路径。
- 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 }
- 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 }
============================================================================================================