问题
由于游戏中寻路出了个小问题–玩家寻路到一个死角后在那边不停的来回跑,就是无法越过障碍物, 就研究了下A*寻路算法以解决这个问题;
研究了几天,自己写了个demo这里给出总结;
原理
paper: http://www.policyalmanac.org/games/aStarTutorial.htm
A*算法给出的是权值最优的路径而不是最短路径;
权值有F = G + H来表示;
启发式函数如下:
F(p) = G(p) + H(p)
- F:从起点经过当前节点p到达目标节点预计总消耗;
- G:从起点到达当前节点p时需要消耗的值;(可以有上一步G(p-1)+g_p(当前步的消耗)得到)
- H:为从当前节点p到达目标节点需要消耗的值(预估权值);
H值估算方法
这里的估算方法类似距离*单位消耗, 计算过程优先使用整数, 浮点数的运算速度慢;
//曼哈顿估价法
private function manhattan(node:Node):Number
{
return Math.abs(node.x - _endNode.x) * _straightCost + Math.abs(node.y + _endNode.y) * _straightCost;
}
//几何估价法
private function euclidian(node:Node):Number
{
var dx:Number=node.x - _endNode.x;
var dy:Number=node.y - _endNode.y;
return Math.sqrt(dx * dx + dy * dy) * _straightCost;
}
//对角线估价法
private function diagonal(node:Node):Number
{
var dx:Number=Math.abs(node.x - _endNode.x);
var dy:Number=Math.abs(node.y - _endNode.y);
var diag:Number=Math.min(dx, dy);
var straight:Number=dx + dy;
return _diagCost * diag + _straightCost * (straight - 2 * diag);
}
G值估算方法
从起点到当前节点的消耗, 可以用叠加过程来得到, 即G(p)=G(p-1)+g_p; g_p为从p-1走到p的消耗, 上下左右为1则对角线为1.4 (sqrt(2)得到). 实际计算用整数,所以会使用10, 14来计算.
openlist与closelist
- 每次查找openlist中F值最小的节点n, 从openlist中删除, 将n放入closelist中.
- 遍历n的8个单步移动方向, 无法到达的节点, 将那些不在closelist的节点放入到openlist中,且所有节点的parent节点指向n;(这里要注意n地址的固定不变的处理, 不然当发生变动时n的子节点可能会失去parent的指向)
- 在将node放入openlist前, 找一下openlist里是否已经有该节点位置的node, 如果有,则比较openlist里的oldnode的G值, 如果oldG小于newG, 则不改变openlist里的数据, oldnode的parent仍然指向原先指向的parent, 而如果oldG大于newG,则说明新的路径(从n到oldnode)的F值将更小,所以将openlist里的oldnode的parent从新指向当前节点n;
设计
节点设计
class point
{
public:
int x;
int y;
point(int X=-1,int Y=-1):x(X),y(Y){};
bool operator==(const point& pos)
{
return (x == pos.x) && (y == pos.y);
}
};
struct node
{
point pos;
node* parent;
bool isvisited;
short GVal;
short HVal;
node(int x=-1,int y=-1):pos(x,y),parent(NULL),isvisited(false),GVal(0),HVal(0){};
bool operator==(const node& node)
{
return (pos == node.pos) && (pos.x >= 0) && (pos.y >= 0);
}
};
所有的parent都是指向closelist里的成员,而closelist里的值是不会变的,closelist做成一维数组表示二维,利用(X,Y)定位,openlist也同样处理,这样当openlist里的isvisited为true则表明被访问过了,就可以对比G值并更新parent;list中的所有没有访问过的或删除的内存全部清零(isvisited=0表示没有访问过);
头文件.h
#ifndef STRATEGYFACTORY_H
#define STRATEGYFACTORY_H
/***************
A* searching road
***************/
#include <stdio.h>
#include <string.h>
using namespace std;
class point
{
public:
int x;
int y;
point(int X=-1,int Y=-1):x(X),y(Y){};
bool operator==(const point& pos)
{
return (x == pos.x) && (y == pos.y);
}
};
struct node
{
point pos;
node* parent;
bool isvisited;
short GVal;
short HVal;
node(int x=-1,int y=-1):pos(x,y),parent(NULL),isvisited(false),GVal(0),HVal(0){};
bool operator==(const node& node)
{
return (pos == node.pos) && (pos.x >= 0) && (pos.y >= 0);
}
};
// 全局变量的定义
static const int MAX_SEARCH_NUM = 1024;
static const point UP(0,0);
static const int MAX_GRID_NUM = 15;
static const point DOWN(MAX_GRID_NUM,MAX_GRID_NUM);
static const char MAP_BLOCK = 'X';
static const char MAP_NONBLOCK = '.';
static const char MAP_LOAD = 'O';
char Map[MAX_GRID_NUM][MAX_GRID_NUM];
enum SearchDir // 8个搜索方向
{
Dir_LEFT = 0,
Dir_LEFT_DOWN,
Dir_DOWN,
Dir_RIGHT_DOWN,
Dir_RIGHT,
Dir_RIGHT_UP,
Dir_UP,
Dir_UP_LEFT,
};
// A*搜索类
class AStar
{
private:
node openlist[MAX_SEARCH_NUM]; //the next step grid (使用优先队列会有更好的查找性能)
node closelist[MAX_SEARCH_NUM];//the grid that already visited
int OpenCount; // 标识openlist使用节点情况
point mStart;
point mEnd;
inline int GetIndex(const point pos) const {return pos.x*(DOWN.y - UP.y)+pos.y;}
void GetMinFNode(node& cur);
bool GetNextStepNode(node& cur,node& nextnode, int dir);
bool IsInCloselist(node& cur);
bool IsInOpenlist(node& cur);
void CalHVal(node& nextnode);
void CalNodeVal(node& nextnode, int dir);
void UpdateGH(node& cur);
public:
AStar():OpenCount(0)
{
memset(closelist,0,MAX_SEARCH_NUM*sizeof(node));
memset(openlist,0,MAX_SEARCH_NUM*sizeof(node));
}
void SetStartPos(point spos) {mStart = spos;}
void SetEndPos(point epos) {mEnd = epos;}
bool ASSearch(point SPt, point Ept); // 寻路核心函数
bool CalLoad(); // 寻路完后通过父节点从终点查找到起点
};
#endif // STRATEGYFACTORY_H
源文件.cpp
#include <iostream>
#include "Test.h"
#include "cmath"
#include <cstdlib>
#include <assert.h>
bool AStar::ASSearch(point SPt, point Ept)
{
SetStartPos(SPt);
SetEndPos(Ept);
node S(mStart.x,mStart.y);
S.isvisited = true;
openlist[GetIndex(S.pos)] = S;
OpenCount++;
while(OpenCount)
{
node cur;
GetMinFNode(cur);// 最小F值节点
assert(cur.isvisited);
closelist[GetIndex(cur.pos)] = cur;// 压入当前节点到closelist
memset(&openlist[GetIndex(cur.pos)],0,sizeof(node)); // 从openlist中移除该节点
--OpenCount;
if(cur.pos == mEnd)
{
closelist[GetIndex(cur.pos)] = cur;
return true;
}
// 将相邻节点计算GH后压入
for(int i = Dir_LEFT; i <= Dir_UP_LEFT; ++i)
{
node nextnode ;
// 已经在closelist中则跳过
if(!GetNextStepNode(cur,nextnode,i) || IsInCloselist(nextnode))
continue;
// 计算H值
CalHVal(nextnode);
CalNodeVal(nextnode,i); // 计算Node的相关属性值
// 访问过了则更新最优GH(在closelist中)
if(IsInOpenlist(nextnode))
UpdateGH(nextnode);
else
{
openlist[GetIndex(nextnode.pos)] = nextnode;
++OpenCount;
}
}
}
return false;
}
void AStar::CalHVal(node& nextnode)
{
nextnode.HVal = abs(nextnode.pos.x - mEnd.x) + abs(nextnode.pos.y - mEnd.y);
nextnode.HVal *= 10;
}
bool AStar::GetNextStepNode(node& cur,node& nextnode, int dir)
{
point nxt;
switch(dir)
{
case Dir_LEFT:
nxt.x = cur.pos.x-1;
nxt.y = cur.pos.y;
nextnode.GVal = cur.GVal +10;
break;
case Dir_LEFT_DOWN:
nxt.x = cur.pos.x-1;
nxt.y = cur.pos.y-1;
nextnode.GVal = cur.GVal +14;
break;
case Dir_DOWN:
nxt.x = cur.pos.x;
nxt.y = cur.pos.y-1;
nextnode.GVal = cur.GVal +10;
break;
case Dir_RIGHT_DOWN:
nxt.x = cur.pos.x+1;
nxt.y = cur.pos.y-1;
nextnode.GVal = cur.GVal +14;
break;
case Dir_RIGHT:
nxt.x = cur.pos.x+1;
nxt.y = cur.pos.y;
nextnode.GVal = cur.GVal +10;
break;
case Dir_RIGHT_UP:
nxt.x = cur.pos.x+1;
nxt.y = cur.pos.y+1;
nextnode.GVal = cur.GVal +14;
break;
case Dir_UP:
nxt.x = cur.pos.x;
nxt.y = cur.pos.y+1;
nextnode.GVal = cur.GVal +10;
break;
case Dir_UP_LEFT:
nxt.x = cur.pos.x-1;
nxt.y = cur.pos.y+1;
nextnode.GVal = cur.GVal +14;
break;
}
if(nxt.x<UP.x || nxt.y<UP.y||nxt.x>=DOWN.x||nxt.y>=DOWN.y)
return false;
if(Map[nxt.x][nxt.y] == MAP_BLOCK)
return false;
nextnode.pos = nxt;
nextnode.parent= &closelist[GetIndex(cur.pos)]; //指向closelist中的对应位置
return true;
}
bool AStar::IsInCloselist(node& cur)
{
return closelist[GetIndex(cur.pos)].isvisited? true : false;
}
bool AStar::IsInOpenlist(node& cur)
{
return openlist[GetIndex(cur.pos)].isvisited? true : false;
}
void AStar::GetMinFNode(node& cur)
{
if(!OpenCount)
return;
int F = 0x7FFF;
node* re = NULL;
for(int i =0; i<MAX_SEARCH_NUM;++i)
{
re = &openlist[i];
if(re->isvisited && F>re->GVal+re->HVal)
{
cur = *re;
F = re->GVal + re->HVal;
}
}
return;
}
void AStar::UpdateGH(node& cur)
{
node& node = openlist[GetIndex(cur.pos)];
if(node.isvisited && node.GVal > cur.GVal)
node = cur;
}
void AStar::CalNodeVal(node& nextnode, int dir)
{
nextnode.isvisited = true;
}
bool AStar::CalLoad()
{
node* endnode = &closelist[GetIndex(mEnd)];
if(!endnode->isvisited)
{
cout<<"not found the end in CloseList!"<<endl;
return false;
}
while(endnode->parent!=NULL)
{
Map[endnode->pos.x][endnode->pos.y] = MAP_LOAD;
endnode = endnode->parent;
}
Map[mStart.x][mStart.y] = MAP_LOAD;
Map[mEnd.x][mEnd.y] = MAP_LOAD;
return true;
}
void InitMap()
{
srand(1002); //可以改变随机数种子来改变地图情况
for(int i = UP.x; i < DOWN.x; ++i)
{
for(int j = UP.y; j < DOWN.y; ++j)
Map[i][j] = rand()&1?MAP_NONBLOCK:MAP_BLOCK; //将地图格子随机设为阻碍或非阻碍
}
}
// 打印地图情况
void ShowMap()
{
const char SPACE[8] = " ";
cout<<SPACE;
for(int i = UP.x; i < DOWN.x; ++i)
cout<<i<<SPACE;
cout<<endl;
for(int i = UP.x; i < DOWN.x; ++i)
{
cout<<i<<SPACE;
for(int j = UP.y; j < DOWN.y; ++j)
cout<<Map[i][j]<<SPACE;
cout<<endl<<endl;
}
cout<<endl<<"======================================================="
<<endl;
}
void GetStartPos(point& S)
{
for(int i = 2; i >= 0; --i)
for(int j = 2; j >= 0; --j)
if(Map[i][j] != MAP_BLOCK)
{
S.x = i;
S.y = j;
return;
}
}
void GetEndPos(point& E)
{
for(int i = MAX_GRID_NUM-1; i >= MAX_GRID_NUM-3; --i)
for(int j = MAX_GRID_NUM-1; j >= MAX_GRID_NUM-3; --j)
if(Map[i][j] != MAP_BLOCK)
{
E.x = i;
E.y = j;
return;
}
}
int main (void)
{
InitMap();
ShowMap();
AStar astr;
point s,e;
GetStartPos(s);
GetEndPos(e);
if(astr.ASSearch(s,e) && astr.CalLoad())
ShowMap();
else
cout<<"not found load"<<endl;
}
演示
地图
其中的.都是可以走的格子,X都是障碍格子;
寻路
0表示寻得的路径, 这里也是唯最优路径.
2741

被折叠的 条评论
为什么被折叠?



