C++ 数据结构算法 学习笔记(26) - 图及其企业级应用
AI游戏中的自动寻路-A*算法
随着3D游戏的日趋流行,在复杂的3D游戏环境中如何能使非玩家控制角色准确实现自动寻路功能成为了3D游戏开 发技术中一大研究热点。其中A算法得到了大量的运用,A算法较之传统的路径规划算法,实时性更高、灵活性更强,寻路 结果更加接近人工选择的路径结果. A*寻路算法并不是找到最优路径,只是找到相对近的路径,因为找最优要把所有可行 路径都找出来进行对比,消耗性能太大,寻路效果只要相对近路径就行了。
A*算法的原理
我们假设在推箱子游戏中人要从站里的地方移动到右侧的箱子目的地,但是这两点之间被一堵墙隔开。
我们下一步要做的便是查找最短路径。既然是AI算法,A*算法和人寻找路径的做法十分类似,当我们离目标较远时,我们的目标方向是朝向目的点直线移动,但是在近距离上因为各种障碍需要绕行(走弯路)!而且已走过的地方就无须再次尝试。
为了简化问题,我们把要搜寻的区域划分成了正方形的格子。这是寻路的第一步,简化搜索区域,就像推箱子游戏一样。 这样就把我们的搜索区域简化为了2维数组。数组的每一项代表一个格子,它的状态就是可走(walkalbe)和不可走 (unwalkable)。通过计算出从起点到终点需要走过哪些方格,就找到了路径。一旦路径找到了,人物便从一个方格的中心 移动到另一个方格的中心,直至到达目的地。 简化搜索区域以后,如何定义小人当前走要走的格子离终点是近是远呢?我们需要两个指标来表示
- G表示从起点移动到网格上指定方格的移动距离(暂时不考虑沿斜向移动,只考虑上下左右移动)。
- H表示从指定的方格移动到终点的预计移动距离,只计算直线距离(H有很多计算方法,这里我们设定只可以上下左右移动,即该点与终点的直线距离)。
- F=G+H ,F即表示从起点经过此点预计到终点的总移动距离
接下来我们从起点开始,按照以下寻路步骤,直至找到目标。
寻路步骤
- 从起点开始, 把它作为待处理的方格存入一个预测可达的节点列表,简称openList, 即把起点放入“预测可达节点列表”, 可达节点列表openList就是一个等待检查方格的列表。
- 寻找openList中F值最小的点min(一开始只有起点)周围可以到达的方格(可到达的意思是其不是障碍物,也不存 在关闭列表中的方格,即不是已走过的方格)。计算min周围可到达的方格的F值。将还没在openList中点放入其中, 并 设置它们的"父方格"为点min,表示他们的上一步是经过min到达的。如果min下一步某个可到达的方格已经在openList 列表那么并且经min点它的F值更优,则修改F值并把其"父方格"设为点min。
- 把2中的点min从"开启列表"中删除并存入"关闭列表"closeList中,closeList中存放的都是不需要再次检查的方格。如 果2中点min不是终点并且开启列表的数量大于零,那么继续从第2步开始。如果是终点执行第4步,如果openList列 表数量为零,那么就是找不到有效路径。
- .如果第3步中min 是终点,则结束查找,直接追溯父节点到起点的路径即为所选路径。
具体寻路步骤图片如下所示:
代码实现
Astar.h
#pragma once
#include <iostream>
#include <list>
#include <string>
using namespace std;
const int kCost1 = 10;
const int kCost2 = 14;
typedef struct _Point
{
int x, y;
int F, G, H;
struct _Point* parent;
}Point;
Point* AllocPoint(int x, int y);
void InitAstarMaze(int* _maze, int _lines, int _colums);
std::list<Point*> GetPath(Point* starPoint, Point* endPoint);
void ClearAstarMaze();
Astar.cpp
#include <math.h>
#include <vector>
#include "Astar.h"
static int* maze;
static int cols;
static int lines;
static list<Point *> openList;
static list<Point*> closeList;
static Point* findPath(Point* startPoint, Point* endPoint);
static Point* getLeastFPoint();
static vector<Point*> getSurrondPoints(const Point* point);
static bool isCanreach(const Point* point, const Point* target);
static Point* isInList(const list<Point*>& list, const Point* target);
static int calcG(Point* temp_start, Point* point);
static int calcH(Point* point, Point* end);
static int calcF(Point* point);
Point* AllocPoint(int x, int y)
{
Point* temp = new Point;
memset(temp, 0, sizeof(Point));
temp->x = x;
temp->y = y;
return temp;
}
void InitAstarMaze(int* _maze, int _lines, int _colums)
{
maze = _maze;
lines = _lines;
cols = _colums;
}
list<Point*> GetPath(Point* startPoint, Point* endPoint)
{
Point* result = findPath(startPoint, endPoint);
list<Point*> path;
while (result)
{
path.push_front(result);
result = result->parent;
}
return path;
}
static Point* findPath(Point* startPoint, Point* endPoint)
{
openList.push_back(AllocPoint(startPoint->x, startPoint->y));
while (!openList.empty())
{
//First Step: From the openlist get the least value of F value;
Point* curPoint = getLeastFPoint();
//Second Step: Put the curPoint into the closeList
openList.remove(curPoint);
closeList.push_back(curPoint);
//Third step: Find the curPoint surrond can reach node. Calculate their F value;
vector<Point*> surrondPoints = getSurrondPoints(curPoint);
vector<Point*>::const_iterator iter;
for (iter = surrondPoints.begin(); iter != surrondPoints.end(); iter++)
{
Point* target = *iter;
//For the target box, if its not inside the open list, put it in. And set the current point to its parent node.
Point* exist = isInList(openList, target);
if (!exist)
{
target->parent = curPoint;
target->G = calcG(curPoint, target);
target->H = calcH(target, endPoint);
target->F = calcF(target);
openList.push_back(target);
}
else
{
int tempG = calcG(curPoint, target);
if (tempG < target->G)
{
exist->parent = curPoint;
exist->G = tempG;
exist->F = calcF(target);
}
delete target;
}
}
surrondPoints.clear();
Point* resPoint = isInList(openList, endPoint);
if (resPoint)
{
return resPoint;
}
}
return NULL;
}
static Point* getLeastFPoint()
{
if (!openList.empty())
{
Point* resPoint = openList.front();
list<Point*>::const_iterator itor = openList.begin();
for (; itor != openList.end(); itor++)
{
if ((*itor)->F < resPoint->F)
{
resPoint = *itor;
}
}
return resPoint;
}
return NULL;
}
static vector<Point*> getSurrondPoints(const Point* point)
{
vector<Point*> surrondPoints;
for (int x = point->x - 1; x <= point->x + 1; x++)
{
for (int y = point->y - 1; y <= point->y + 1; y++)
{
Point* temp = AllocPoint(x, y);
if (isCanreach(point, temp))
{
surrondPoints.push_back(temp);
}
else
{
delete temp;
}
}
}
return surrondPoints;
}
static bool isCanreach(const Point* point, const Point* target)
{
if (target->x<0 || target->x>(lines - 1) || target->y <0 || target->y>(cols - 1) ||
maze[target->x*cols + target->y] == 1|| maze[target->x * cols + target->y] == 2
||(target->x==point->x && target->y == point->y) || isInList(closeList,target))
{
return false;
}
if (abs(point->x - target->x) + abs(point->y - target->y) == 1)
{
return true;
}
else
{
return false;
}
}
static Point* isInList(const list<Point*>& lists, const Point* target)
{
list <Point *>::const_iterator itor;
for (itor = lists.begin(); itor != lists.end(); itor++)
{
if ((*itor)->x == target->x && (*itor)->y == target->y)
{
return *itor;
}
}
return NULL;
}
static int calcG(Point* temp_start, Point* point)
{
int extraG = (abs(point->x - temp_start->x) + abs(point->y - temp_start->y)) == 1 ? kCost1 : kCost2;
int parentG = (point->parent == NULL ? NULL : point->parent->G);
return parentG + extraG;
}
static int calcH(Point* point, Point* end)
{
return (int)sqrt((double)(end->x - point->x) * (double)(end->x - point->x) + (double)(end->y - point->y) * (double)(end->y - point->y));
}
static int calcF(Point* point)
{
return point->G + point->H;
}
void ClearAstarMaze()
{
maze = NULL;
lines = 0;
cols = 0;
list<Point*>::iterator itor;
for (itor = openList.begin(); itor != openList.end();)
{
delete* itor;
itor = openList.erase(itor); // Get the next node, therefore no need itor++
}
for (itor = closeList.begin(); itor != closeList.end();)
{
delete* itor;
itor = closeList.erase(itor); // Get the next node, therefore no need itor++
}
}
main.cpp
#include <iostream>
#include"Astar.h"
#include "Windows.h"
using namespace std;
int map[13][13] = {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,},
{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,},
{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,},
{0, 1, 0, 1, 0, 1, 2, 1, 0, 1, 0, 1, 0,},
{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,},
{0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0,},
{0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0,},
{2, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 2,},
{0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,},
{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,},
{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,},
{0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0,},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,}
};
void AStarTest()
{
InitAstarMaze(&map[0][0], 13, 13);
Point* start = AllocPoint(12, 4);
Point* end = AllocPoint(0, 0);
list<Point*>path = GetPath(start, end);
cout << "The result for the find path is:" << endl;
list<Point*>::const_iterator iter;
for (iter = path.begin(); iter != path.end();iter++)
{
Point* cur = *iter;
cout << '(' << cur->x << ',' << cur->y << ')' << endl;
Sleep(800);
}
ClearAstarMaze();
}
int main()
{
AStarTest();
system("pause");
return 0;
}