最近学习游戏中的A*算法,有点心得,写出来共享
同时也希望这方面的前辈指点一下
我是根据这两篇文章和其中的代码改写的,用C#语言,然后增加了惯性方向
http://blog.vckbase.com/panic/archive/2005/03/20/3778.html
http://blog.vckbase.com/panic/archive/2005/03/28/4144.html
源代码 http://www.blitzcoder.com/cgi-bin/showcase/showcase_showentry.pl?id=turtle177604062002002208&comments=no
一、问题说明
用A*寻路算法时,总会有一直贴着墙走的情况出现
这和你搜索周围点的顺序有关
上面文章中的例程中几行代码解释一下
parentXval = openX[openList[1]]; //得到开放列表中F值最低的节点X坐标
parentYval = openY[openList[1]]; //得到开放列表中F值最低的节点Y坐标
接着将( parentXval, parentYval)放入关闭列表
然后对( parentXval, parentYval)周围的节点进行查询
这时候用到循环
for (b = parentYval-1; b <= parentYval+1; b++){
for (a = parentXval-1; a <= parentXval+1; a++){
//do something
}
}
这个循环是固定规律的循环,这就导致寻路过程中,在几个F值相同的情况下,默认总会选择某一个方向的节点。本程序中,最后加入的节点优先选择。
我们定义方向为
7 0 1
6 2
5 4 3
那么上面的循环就是优先选择3方向的节点,其次4、5......
***另外说明一下,我的地图中,无论向哪个方向走,消耗都是一样。也就是从中心点到2和到3的距离相同
这样的循环,找到的路径显得有些古板,在能走4方向的情况下绝对不会走5的。有时候你会看到选择的节点总是贴着蜿蜒障碍物前进,根本不进入宽阔的道路中
二、如何解决这个问题呢??
构思1、改变靠近墙壁的节点的H值估价,让靠近墙壁的节点滞后选择
这样做应该会多几个判断和赋值在循环中,cpu消耗多些;而更重要的是找到的路径虽然不是紧挨墙壁,但仍会在距离墙壁一定距离内和墙壁起伏不平
构思2.增加惯性方向
比如当前我在走2方向,下一个F值最小的节点就优先选择2方向,尽管3方向的F值也同样的小
这样会尽量保持直线行走。我选择这个方法。(如果这个构思2结合构思1就可以很好的远离障碍)
三、实现构思2
1、得到当前行进方向
于是我想增加一个惯性方向判定
如何增加这个方向呢?
多加入个数组么?direction[width+1,height+1]???
好家伙,这个东西是比耗费内存的,运算也不少
还是做判断吧
把刚放入关闭列表的节点坐标和其父节点坐标比较就可以很容易的判断出来方向了
这个判断不再循环中,cpu消耗比较小
public int calc_direction(ref int startX,ref int startY, ref int targetX, ref int targetY)
{
switch(targetX-startX)
{
case 1:
switch(targetY-startY)
{
case 1:
return 3;
case 0:
return 2;
case -1:
return 1;
default:
return -1;
}
case 0:
switch(targetY-startY)
{
case 1:
return 4;
case 0:
return -1;
case -1:
return 0;
default:
return -1;
}
case -1:
switch(targetY-startY)
{
case 1:
return 5;
case 0:
return 6;
case -1:
return 7;
default:
return -1;
}
default:
return -1;
}//end switch(targetX-startX)
}//end calc_direction
2、使用当前方向
使用当前方向只需要对当前方向的节点放到最后在查询就可以实现了。因为程序中的二叉堆的算法总是优先使用最后插入的节点
3、具体循环写法和优化
那么那个循环如何写呢
在循环中再对坐标使用一个方向判断的函数么?
由于在循环中,那样会加大很多运算量的,另外对方向也不直观,可读性不好
那么我们这样做吧。当前方向为run_direction(用 calc_direction得到),设置一个循环变量find_direction从run_direction+1开始绕一圈,最后回到run_direction做最后一次比较。方向8和方向0是相同的,1和9相同....
比如当前方向为3,那么我们就从方向4到11进行一次循环
有了find_direction,如何得到相关坐标呢?
如果用switch的方法计算,仍然很耗费cpu
我定义了这样的一个数组directionArray[,],用空间置换时间吧(这点空间很小,完全值得)。存储每个方向的坐标变化情况。
7 0 1
6 2
5 4 3
0,0点在地图的左上角情况下。初始点坐标为(parentXval,parentYval),那么0方向的坐标为parentXval+0,parentYval-1
于是directionArray[0,0]=0,directionArray[0,1]=-1
同理延续,我们得到一个总共15个方向的数组
int[,] directionArray = new int[,] {{0,-1},{1,-1},{1,0},{1,1},{0,1},{-1,1},{-1,0},{-1,-1},{0,-1},{1,-1},{1,0},{1,1},{0,1},{-1,1},{-1,0},{-1,-1}};
这样(parentXval,parentYval)点某方向d节点(a,b)的计算为
a=parentXval + directionArray[find_direction,0];
b=parentYval + directionArray[find_direction,1];
好了。我们的循环部分就这样写了
parent_parentXval = parentX[parentXval,parentYval];
parent_parentYval = parentY[parentXval,parentYval];
run_direction = calc_direction(ref parent_parentXval,ref parent_parentYval,ref parentXval,ref parentYval);
max_direction = run_direction+8;
for(find_direction=run_direction+1;find_direction<=max_direction;find_direction++)
{
a=parentXval + directionArray[find_direction,0];
b=parentYval + directionArray[find_direction,1];
//do something
}
这样,我们就比较高效的实现了方向惯性~~~~
<<<结束>>>
我学习A*不久,如有错误,请大家多多指点
另外,这个A*用了大量预定义的数组,使用内存很高。
游戏中大的地图有1000*1000的节点,那么用这个算法需要40MB内存。就算将int修改为ushort也是20MB
我不太能接受。但如果用集合或者动态Array速度就会慢了~~~还不想慢
这个A*用C#实现,在1000*1000的障碍图上(有一些大的障碍/但不是迷宫类型),在AMD2500+的机器上寻路速度为300多ms。我还是不太能接受,因为听说有人的算法只用80ms,也是没有分层的(估计他的CPU不会比我的快太多)。