Colin-Liao 个人原创,欢迎转载,转载请注明地址。Colin-Liao的专栏地址http://blog.youkuaiyun.com/focusdeveloper
最近在研究A*算法,自己也写了一个关于A*寻路算法,在这里分享一下自己学习使用A*算法的感受和例子,希望大家多多指教。
A*算法是一种启发性的算法,通过估计计算出下一个将要到达的格子来获得最短的路径以到达目标。A*算法有一个核心公式:F = G + H;
这个公式相信了解过A*算法的人都应该知道,首先将地图划分为一个网格,那么G表示精灵在当前格子到周围八个格子中任意一个格子(此时估算的格子)的距离,H表示当前估算格子到目标格子的距离,这样通过估算出八个格子中F的最小值来获得较为理想的解。
我的程序同样有一个open数组和一个close数组,不过我这个open列表中只存放一个item(一个格子),另外还有个path数组,将每次估算出来的理想的格子存放到path中,当估算到目标格子后,便可以轻松从path数组中解析出一条理想的路径。
下面先将代码贴出来:
#include <iostream>
#include "cocos2d.h"
USING_NS_CC;
class Astaritem:public CCObject {
CC_SYNTHESIZE(int, m_col, Col);//列
CC_SYNTHESIZE(int, m_row, Row);//行
CC_SYNTHESIZE(float, m_g, G);//当前位置到估算格子的距离
CC_SYNTHESIZE(float, m_h, H);//估算格子到目标格子的距离
CC_SYNTHESIZE(float, m_f, F);//估计函数 f = g+h;
public:
CREATE_FUNC(Astaritem);
bool init();
~Astaritem();
};
这个是Astaritem的头文件,里面定义了地图网格中的列和行,当前位置到估算格子的距离G,估算格子到目标格子的距离H以及总估算值F。
#include <iostream>
#include "cocos2d.h"
USING_NS_CC;
class Astaritem;
class Astar :public CCObject {
CC_SYNTHESIZE(int, cur_col, Cur_col);//当前行
CC_SYNTHESIZE(int, cur_row, Cur_row);//当前列
CC_SYNTHESIZE(int, aim_col, Aim_col);//目标行
CC_SYNTHESIZE(int, aim_row, Aim_row);//目标列
CC_SYNTHESIZE(int, loadTrend, LoadTrend)//路径起始走向0表示横向 1表示纵向
public:
CREATE_FUNC(Astar);
~Astar();
bool init();
void setPath(int curCol,int curRow,int aimCol,int aimRow);
Astaritem* getPathItem(int index);
int getPathCount();
bool isCloseItem(int col,int row);//判断是否已经在close数组中的值
void removeAllArray();
private:
float getH(int col,int row);
float getG(int col,int row);
void opentoClose();
void setBeastItemFromAround();
void setMinItem(Astaritem* item);
CCArray* open;
CCArray* close;
CCArray* path;
};
这个是Astar的头文件。
现在开始A*算法的实现源文件:
void Astar::setPath(int curCol,int curRow,int aimCol,int aimRow)
{
this->cur_col = curCol;
this->cur_row = curRow;
this->aim_col = aimCol;
this->aim_row = aimRow;
if (AnalysisINI::SharedAnalysisiINI()->getState(this->aim_col, this->aim_row) == 3 ) {
return;
}
if ((this->cur_col == this->aim_col)&& (this-> cur_row == this->aim_row)) {
return;
}
Astaritem* item1 = Astaritem::create();
item1->setCol(this->cur_col);
item1->setRow(this->cur_row);
item1->setH(this->getH(item1->getCol(), item1->getRow()));
item1->setG(0);
item1->setF(item1->getH()+item1->getG());
this->open->addObject(item1);
while (abs(this->aim_col - ((Astaritem *)this->open->lastObject())->getCol()) > 0 || abs(this->aim_row - ((Astaritem *)this->open->lastObject())->getRow()) > 0) {
this->opentoclose();
this->setBeastItemFromAround();
if (this->open->count()<=0) {
break;
}
}
}
首先设置精灵当前的行和列的坐标以及目标格子坐标。AnalysisINI::SharedAnalysisiINI()->getState(this->aim_col, this->aim_row),这行代码是我自己写的一个类用来判断目标点是否能够到达,是否在屏幕之外或者是否有障碍,若有障碍那么函数返回,不能生成路径数组。之后用初始化当前位置来创建一个item并且设置H以及G,开始时g为0,并将其加入到open数组中。
float Astar::getH(int col,int row)
{
int _col = 3*abs(this->aim_col - col);
int _row = 2*abs(this->aim_row - row);
long b = _col*_col + _row*_row;
float a = sqrtf(b);
return a;
}
float Astar::getG(int col,int row)
{
Astaritem* item = (Astaritem*)this->close->lastObject();
int _col = 3*abs(item->getCol() - col);
int _row = 2*abs(item->getRow() - row);
return sqrtl(_col*_col+_row+_row);
}
然后实现一个循环,跳出条件为估算的格子与目标格子相等的时候就跳出循环,即到达终点目标位置。为保证安全,当open中没有item时强制break跳出循环。
void Astar::fromopentoclose()
{
if (this->open->count() > 0) {
Astaritem* item = (Astaritem*)this->open->lastObject();
this->close->addObject(item);
this->path->addObject(item);
this->open->removeLastObject();
}
}
将加入到open数组中的item取出来放入到close数组中和path数组中,那么路径数组中从第一个点开始,放入close数组中是为了在下次估算格子时不重复估算已经加入到close数组中的格子,最后将open数组清空。
void Astar::setBeastItemFromAround()
{
Astaritem* item = (Astaritem*)this->close->lastObject();
int col;
int row;
if (item->getCol() == this->aim_col && item->getRow() == this->aim_row) {
return;
}
//判断其走向为横向还是纵向
int _colDistance = abs(item->getCol()-this->aim_col);
int _rowDidstance = abs(item->getRow() -this->aim_row);
if (_colDistance > _rowDidstance) {
this->loadTrend = 0;
}else
{
this->loadTrend = 1;
}
col = item->getCol()-1;
row = item->getRow();
Astaritem* newItem1 = Astaritem::create();
newItem1->setCol(col);
newItem1->setRow(row);
newItem1->setH(this->getH(newItem1->getCol(), newItem1->getRow()));
newItem1->setG(this->getG(newItem1->getCol(), newItem1->getRow()));
newItem1->setF(newItem1->getH()+newItem1->getG());
this->setMinItem(newItem1);
col = item->getCol();
row = item->getRow()-1;
Astaritem* newItem2 = Astaritem::create();
newItem2->setCol(col);
newItem2->setRow(row);
newItem2->setH(this->getH(newItem2->getCol(), newItem2->getRow()));
newItem2->setG(this->getG(newItem2->getCol(), newItem2->getRow()));
newItem2->setF(newItem2->getH()+newItem2->getG());
this->setMinItem(newItem2);
col = item->getCol()-1;
row = item->getRow()-1;
Astaritem* newItem3 = Astaritem::create();
newItem3->setCol(col);
newItem3->setRow(row);
newItem3->setH(this->getH(newItem3->getCol(), newItem3->getRow()));
newItem3->setG(this->getG(newItem3->getCol(), newItem3->getRow()));
newItem3->setF(newItem3->getH()+newItem3->getG());
this->setMinItem(newItem3);
col = item->getCol()+1;
row = item->getRow();
Astaritem* newItem4 = Astaritem::create();
newItem4->setCol(col);
newItem4->setRow(row);
newItem4->setH(this->getH(newItem4->getCol(), newItem4->getRow()));
newItem4->setG(this->getG(newItem4->getCol(), newItem4->getRow()));
newItem4->setF(newItem4->getH()+newItem4->getG());
this->setMinItem(newItem4);
col = item->getCol();
row = item->getRow()+1;
Astaritem* newItem5 = Astaritem::create();
newItem5->setCol(col);
newItem5->setRow(row);
newItem5->setH(this->getH(newItem5->getCol(), newItem5->getRow()));
newItem5->setG(this->getG(newItem5->getCol(), newItem5->getRow()));
newItem5->setF(newItem5->getH()+newItem5->getG());
this->setMinItem(newItem5);
col = item->getCol()+1;
row = item->getRow()+1;
Astaritem* newItem6 = Astaritem::create();
newItem6->setCol(col);
newItem6->setRow(row);
newItem6->setH(this->getH(newItem6->getCol(), newItem6->getRow()));
newItem6->setG(this->getG(newItem6->getCol(), newItem6->getRow()));
newItem6->setF(newItem6->getH()+newItem6->getG());
this->setMinItem(newItem6);
col = item->getCol()-1;
row = item->getRow()+1;
Astaritem* newItem7 = Astaritem::create();
newItem7->setCol(col);
newItem7->setRow(row);
newItem7->setH(this->getH(newItem7->getCol(), newItem7->getRow()));
newItem7->setG(this->getG(newItem7->getCol(), newItem7->getRow()));
newItem7->setF(newItem7->getH()+newItem7->getG());
this->setMinItem(newItem7);
col = item->getCol()+1;
row = item->getRow()-1;
Astaritem* newItem8 = Astaritem::create();
newItem8->setCol(col);
newItem8->setRow(row);
newItem8->setH(this->getH(newItem8->getCol(), newItem8->getRow()));
newItem8->setG(this->getG(newItem8->getCol(), newItem8->getRow()));
newItem8->setF(newItem8->getH()+newItem8->getG());
this->setMinItem(newItem8);
}
判断close列表中的最后一个item是否和目标item相同,相同则表示已经到达终点。然后我们开始从八个方向寻找最优的解。
这里设置其基本走向是什么意思呢,这也是本文与基础A*算法有差别的地方,之前我完全未改动的A*算法中总是在大障碍或者比较奇怪的障碍的时候找不到最优解或者是寻不到正确的路径。那么本文设置这个走向在道路上没有障碍的时候是自动满足的,那么在出现障碍的时候先判断最优F,然后再在目标格子的这个走向上来进一步得到最优解。之后会贴出代码。
void Astar::setMinItem(Astaritem* item)
{
//传过来的item是否是障碍的地方有需要处理
if (item->getCol() < 0 ||item->getCol() > 116) {
return;
}
if (item->getRow() < 0 || item->getRow() > 127) {
return;
}
if (AnalysisINI::SharedAnalysisiINI()->getState(item->getCol(), item->getRow()) == 3)
{
return;
}
if (this->isCloseItem(item->getCol(), item->getRow())) {
return;
}
//若为横向和纵向都用各自的方法处理
if (this->loadTrend == 0) {
if (this->open->count() == 0) {
this->open->addObject(item);
}else{
Astaritem* lastItem = (Astaritem*)this->open->lastObject();
if (lastItem->getF() < item->getF()) {
if (abs(lastItem->getCol() - aim_col)<= abs(item->getCol()-aim_col)) {
}else
{
this->open->replaceObjectAtIndex(this->open->count()-1, item);
this->cur_col = item->getCol();
this->cur_row = item->getRow();
}
}else if (lastItem->getF() > item->getF())
{
if (abs(lastItem->getCol() - aim_col) < abs(item->getCol()-aim_col)) {
}else
{
this->open->replaceObjectAtIndex(this->open->count()-1, item);
this->cur_col = item->getCol();
this->cur_row = item->getRow();
}
}
}
}else
{
if (this->open->count() == 0) {
this->open->addObject(item);
}else{
Astaritem* lastItem = (Astaritem*)this->open->lastObject();
if (lastItem->getF() < item->getF()) {
if (abs(lastItem->getRow() - aim_row)<= abs(item->getRow()-aim_row)) {
}else
{
this->open->replaceObjectAtIndex(this->open->count()-1, item);
this->cur_col = item->getCol();
this->cur_row = item->getRow();
}
}else if (lastItem->getF() > item->getF())
{
if (abs(lastItem->getRow() - aim_row) < abs(item->getRow()-aim_row)) {
}else
{
this->open->replaceObjectAtIndex(this->open->count()-1, item);
this->cur_col = item->getCol();
this->cur_row = item->getRow();
}
}
}
}
}
这个函数也是本文的核心,也是本文算法的核心。首先判断估算的item是否已经在自己预设的网格之外,若已经超出网格则返回。若估算的item是障碍或者已经加入到了close数组中,那么也不再继续估算。
首先在之前的函数中预设的走向标示来判断走向,得到走向之后,这八个估算格子都将是这个走向,首先open数组是为空的,我们加入第一个估算item,接着传入第二个item,先比较他们的F值,取得最小的F值的item,基本A*算法到这里就结束了。
在里面继续判断他们到走向坐标的横轴距离或者纵轴距离,我们总是取离走向横坐标或者纵坐标距离最小的那个item,看起来我们对F的判断似乎没有多大的用处,但是当没有障碍的时候就不需要判断走向了,因为他自动满足了,直接可以判断F,当存在极大障碍或者奇怪的障碍的时候,用这个方法能轻松找到最优解。
最终我们会在八个方向都估算完之后在open数组中得到一个最优解。把这个最优解通过上面的函数加入到close和path数组中,就这样知道跳出循环那么便得到了一个最优的路径数组。通过解析这个路径数组便可以让你的人物轻松的饶过障碍或者行走。
这个是第一次分享和贴出自己的代码,以后还会陆续的分享在开发过程中获得经验与心德。如有错误,希望大家多多指正。