【qboy】原创 2013年2月2日
好久没回到这里来写了,回家过年之前再写一篇吧。这是在2012年11月到12月之间做的一个游戏中所采用的算法。跟大家分享一下。
一、A*算法的简介
在大学时,在一个人工智能的选修课,我第一次接触了A*算法,也采用这个算法实现课堂上一个作业8数码问题。
简单的说A*算法的核心就是F=G+H;G为到第i步经过的步数,H为到达目的地预计还需要通过多少步,即是一个可能值(或者排除一切障碍的最优值,视问题情况而定),F即是两者相加,对当前情况下的所有F值进行排序,得到F的最优的一步进行第i+1步,直到H值为0。通过这个公式我们可以看出A*算法照顾了过去,也预测着未来。
二、问题背景
我们游戏的背景是在一个有限的海洋中(可以定义为一个二维数组),敌方船向我方船进行逼近,直到撞到我方船为止。在海洋中,有一些零散的岛屿或者其他是在海洋中的障碍物,有些敌方船会以直线的方式逼近(即不考虑障碍物,撞到障碍会死亡),而另一些船AI较高会绕开障碍物向我方逼近。
如下图所示,五角形是我方船位置,即目标位置,菱形和五边形是敌方船,菱形是直线逼近,无智能,五边形是有AI,每艘船可以沿着八个方向进行行走,黄色方块是障碍物。那么菱形会右下撞向障碍物,五边形以实线方向逼近我方船,而不是走最近虚线。当然游戏是回合制,一次只能走特定的步数。
三、实现步骤
为了解决以上问题,我们采用了A*算法(由于游戏是IOS上,所以很多都是ObjC,不过此方法主要还是采用C语言来实现)。
1、我们设定了一个即二维数组(世界环境),描述各船和障碍物所在的位置。
2、定义在寻路过程中的数据结构:
typedef struct ShipPointAStar
{
ShipPoint point;//所在位置
int GValue;//已经过的步数,G
int HValue;//H值
int FValue;//=G+H
struct ShipPointAStar *prePoint;//前一步,这是为了逆着找到要走的点
}ShipPointAStar;
3、定义H函数和G函数:
//A star 算法中的H
// 输入:curPoint为当前所在点,destPoint为目标点
//输出:H值
//原理是:假设没有障碍的情况下两点之间的最短距离数
CG_INLINE int H(ShipPoint curPoint,ShipPoint destPoint)
{
ShipPoint prePoint = curPoint;
int numStep = 0;//预测的步数
while (!EqualShipPoint(prePoint, destPoint)) {//当前点是否与目标点重合
prePoint = getNextShipPoint(prePoint, destPoint);//获取下一步
numStep++;
}
return numStep;
}
G的求法:
定义:
G(i)=G(i-1)+1;
G(0)=0;
根据数学知识可以推算出G(i)=i;其中i为分解步数。
4、构造可选区域的,由于船最多往8个方向移动,则把当前步下的所有可能方向,并计算出相应的H值和G值,求出F值。
5、对所有的可能情况(未走过的点)进行F值排序,取出F值最小的点进行下一步展开,在这采用的是冒泡排序所以就不贴出来了,有机会再改成快速排序。
当出现点与目标点重合时查找结束,或者当所有的能展开的点都已排除,则查找失败(不可达)。
当找到路径时我们逆着把当前点下一步点返回,这已经不在我们讨论范围了。
以下是算法的程序段:
//通过A*算法 得到移动位置
//输入:srcPoint源点, destPoint目标点,arrmap地点二维数组,我们定义1是不可走区域,mapW是二维数组的行数,mapH是二维数组的列数 maxFValue是扩展功能,即最大F值,当超过时不再寻路,0时没有限制。
//输出:船的下个行动点
CG_INLINE ShipPoint getNextShipPointByAStarAndMaxFValue(ShipPoint srcPoint,ShipPoint destPoint,int **arrmap,int mapW,int mapH,int maxFValue)
{
ShipPoint nextPoint = ShipPointCopy(srcPoint);
ShipPointAStar star;
star.GValue = 0;
star.HValue = H(nextPoint, destPoint);
star.FValue = star.GValue+star.HValue;
star.point=nextPoint;
star.prePoint = NULL;
ShipPointAStar* stararr =malloc(sizeof(ShipPointAStar)*mapW*mapH);//我个人觉得最多只有地图中的一半个节点会进入数组
int num = 0;
stararr[num] = star;
num ++;
int pi =0;
while (!EqualShipPoint(stararr[pi].point, destPoint)) //已到达目标
{
nextPoint = stararr[pi].point;
if (maxFValue > 0 && stararr[pi].FValue>maxFValue) {
break;
}
int minX = nextPoint.ShipX > 0 ? nextPoint.ShipX - 1 : 0;
int maxX = nextPoint.ShipX < mapW - 1 ? nextPoint.ShipX + 1 : mapW-1;
int minY = nextPoint.ShipY > 0 ? nextPoint.ShipY - 1 : 0;
int maxY = nextPoint.ShipY < mapH - 1 ? nextPoint.ShipY + 1 : mapH-1;
for (int i = minY ; i <= maxY; i++) {
for (int j = minX; j <= maxX; j++) {
if (arrmap[i][j] == 1) //不可走
{
continue;
}
else
{
ShipPoint p =ShipPointMake(j, i);
bool blExist = NO;
for (int k = 0; k<num; k++) {
if (EqualShipPoint(p, stararr[k].point)) {
blExist = YES;
break;
}
}
if (!blExist) {
ShipPointAStar s;
s.GValue = stararr[pi].GValue+1;
s.HValue = H(p, destPoint);
s.FValue = s.GValue+s.HValue;
s.point=p;
s.prePoint = &stararr[pi];
stararr[num++] = s;
}
}
}
}
SortShipPointAStar(stararr, num);
pi++;
if (pi>=num) {
break;
}
}
for (int i = 0; i<num; i++) {
ShipPoint p = stararr[i].point;
FCLOG(@"X = %i Y = %i,GValue = %i,FValue = %i",p.ShipX,p.ShipY,stararr[i].GValue,stararr[i].FValue);
}
FCLOG(@"Arr Num = %i",num);
ShipPoint rstPoint= ShipPointNull();
if (EqualShipPoint(stararr[pi].point, destPoint)) {
FCLOG(@"循路成功");
//往回走路径
if (stararr[pi].prePoint) {
ShipPoint p = stararr[pi].point;
FCLOG(@"X = %i Y = %i,GValue = %i,FValue = %i",p.ShipX,p.ShipY,stararr[pi].GValue,stararr[pi].FValue);
ShipPointAStar *preP = stararr[pi].prePoint;
if (preP->prePoint!=NULL) {
while (preP->prePoint->prePoint!=NULL) {
p = preP->point;
CCLOG(@"X = %i Y = %i,GValue = %i,FValue = %i",p.ShipX,p.ShipY,preP->GValue,preP->FValue);
preP = preP ->prePoint;
}
p = preP->point;
FCLOG(@"X = %i Y = %i,GValue = %i,FValue = %i",p.ShipX,p.ShipY,preP->GValue,preP->FValue);
rstPoint = preP->point;
}
else
{
rstPoint = p;
}
}
}
else
{
rstPoint = ShipPointNull();
FCLOG(@"循路失败");
}
free(stararr);
return rstPoint;
}
四、缺陷问题
通过以上分析,我们把船的寻路给找到了。但是我们在实际发现了以下两个问题:
1、由于可走路径比较多(8个方向),我们寻找出的路径只是好几条较优路径中的一条。如下图中两个艘船就有三条路径。
2、由于在搜索时采用的是先上后下,先左后右的搜索顺序,所以在上图中敌方船的行走就不是中间那一条,而是上面那条,这样会引出船的方向可能并不是用户(玩家)所希望的,船的方向和位置会决定后续回会中的走法。
解决以上问题的办法:
一开始我想到的办法是将船的方向不要设定为8个方向,而是只有4个方向,上下左右。但是如果两艘船的位置是在斜线方向,则两者也有可能会出现多条路径,况且这也符合实际情况。。
当写到这儿的时候,我突然之间想到一个办法,不知道可行不,先写出来再说,再设定每个方向的权重,比如,通过两个点之间距离来决定权重,而不是步数。例如上图中,设定每个方块宽度为1,最上一条和最下面一条的距离就应该是,而中间的一条的路径是2。这样我们就可以用距离来计算G,H,F值,而不是之前的步数了。