转载注明:http://blog.youkuaiyun.com/lqk1985/article/details/6679754
A*算法是根据可行走区域和不可行走区域wall得到的。一般用正方块模拟,当然也可以其他形状。
以前老的游戏或小游戏用A*,如果游戏行走的概念本来就是走格子的,格子会用很大,比如战棋类游戏,那直接用A*得到的路径没关系。
现有游戏分地图很大分辨率很高,wall的精度一般是10*10,还是比较小的,可以保证角色可以站在很靠近障碍物的地方。
微观:走格子 抖动
MMO游戏中角色行走的概念中其实是没有格子的。游戏中行走其实是根据给出的路径中的两点,判断朝向,播放相应的行走动画,移动(可以是地图动人不动,或人动地图不动)。
为了保证精度,我们使用的格子很小,得到的路径上点也就很多了。实际行走中将会发现角色行走时会不停抖动,其实是因为格子太小,路径点太近,人物不停在改变朝向。
只要保证路径上两点间的距离足够远就行了。我们需要在A*给出的由格子序列组成的路径上进行过滤。
取其中两个格子,判断两个点之间有无不可行走区域,如果没有,这两个格子之间的格子都可以从路径中移除。
例子:格子12345678910。
我们采用分治的方法,先取中间点5,看1-5和5-10直线连接上有没有不可行走区域。
如可以则把两个端点加入路径,中间的格子去掉,不可以就继续对两个端点之间进行分治。
private function getRoute(startX : int,startY : int,endX : int,endY : int,aStarPath : Array,wall:Wall):void {
var xDistance : int = endX - startX;
var yDistance : int = endY - startY;
var distance : int = Math.round(Math.pow(xDistance * xDistance + yDistance * yDistance,1 / 2));
var i : int;
var tempX : int,tempY : int;
var flag : Boolean = false;
var isExistStart : Boolean = false;
var isExistEnd : Boolean = false;
var note : Array;
// 这一步作为递归的出口
for (i = 1;i < distance;i += KEY_POINT_LENGTH) {
tempX = Math.round(startX + i * xDistance / distance);
tempY = Math.round(startY + i * yDistance / distance);
if (wall.isWall(tempX,tempY)) {
flag = true;
break;
}
}
// 如果没有障碍物,则将起始点加入到新路径并返回
if (! flag) {
for (i = 0;i < route.length;i ++) {
note = route[i];
if (note[0] == startX && note[1] == startY) isExistStart = true;
if (note[0] == endX && note[1] == endY) isExistEnd = true;
}
if (! isExistStart) route.push([startX,startY]);
if (! isExistEnd) route.push([endX,endY]);
return;
}
var tmpDistance : Number;
var a : int,b : int,c : int,tmpX : int = startX,tmpY : int = startY,tmpPos : int;
a = endY - startY;
b = startX - endX;
c = endX * startY - startX * endY;
tmpPos = 0;
tmpDistance = 0;
distance = 0;
var length:int = aStarPath.length;
//有碰撞点则选择高度最大的点为关键点
for (i = 0;i < length;i ++) {
note = aStarPath[i];
tmpDistance = Math.abs((a * note[0] + b * note[1] + c) / Math.sqrt(a * a + b * b));
if (distance < tmpDistance) {
distance = tmpDistance;
tmpX = note[0];
tmpY = note[1];
tmpPos = i + 1;
}
}
//防止栈溢出
if (distance == 0){
return;
}
//如果起始点不在路径中将起始点加入新路径
for (i = 0;i < route.length;i ++) {
note = route[i];
if (note[0] == startX && note[1] == startY) isExistStart = true;
}
if (! isExistStart) route.push([startX,startY]);
//递归计算关键坐标并加入到路径数组
var tmpFirstArray : Array = new Array();
for (i = 0;i < tmpPos;i ++) {
tmpFirstArray[i] = aStarPath[i];
}
var tmpLastArray : Array = new Array();
for (i = tmpPos;i < length;i ++) {
tmpLastArray[i - tmpPos] = aStarPath[i];
}
if (tmpFirstArray.length > 0) getRoute(startX,startY,tmpX,tmpY,tmpFirstArray,wall);
if (tmpLastArray.length > 0) getRoute(tmpX,tmpY,endX,endY,tmpLastArray,wall);
//如果目标点不在路径中,将目标点坐标加入到新的路径
for (i = 0;i < route.length;i ++) {
note = route[i];
if (note[0] == endX && note[1] == endY) isExistEnd = true;
}
if (! isExistEnd) route.push([endX,endY]);
return;
}
宏观:沿着不可行走区域走
现在角色行走时已经不抖动了,但是我们会发现角色有时会沿着不可行走区域走,而事实上某两点之间是有直线可以走的。
这其实是因为我们在分治过程中,这两个可直线行走的点被分到两段中,就不会去检测他们是否可以直线行走了。
这时经过上面针对微观抖动的过滤后,路径上的点已经比较少了。所以我们可以采用遍历路径上所有路点,两两之间是否可以直线行走,每次过滤掉的点接着的判断就不纳入计算,复杂度为O(N^2)。
当然可能还是有点沿着不可行走区域,因为整个过滤过程还是基于A*的,A*给出的路径本身就是有点沿着不可行走区域。
这样我们就可以获得比较不错的角色寻路体验了。
private function filter(route:Array, wall:Wall):void{
GameStage.itemLayer.graphics.clear();
GameStage.itemLayer.graphics.beginFill(0xffff00);
for(var i:int=0; i<route.length; i++){
//GameStage.itemLayer.graphics.drawRect(route[i][0], route[i][1], 5, 5);
}
var i:int=0;
var j:int=route.length-1;
while(j-i>1){
while(j>i+1){
var xDistance : int = route[j][0] - route[i][0];
var yDistance : int = route[j][1] - route[i][1];
var distance : int = Math.round(Math.pow(xDistance * xDistance + yDistance * yDistance,1 / 2));
var tempX:int, tempY:int;
var flag : Boolean = false;
for(var k:int=1; k<distance; k+=KEY_POINT_LENGTH){
tempX = route[i][0] + k * xDistance / distance;
tempY = route[i][1] + k * yDistance / distance;
if (wall.isWall(Math.floor(tempX),Math.floor(tempY))
&& wall.isWall(Math.floor(tempX),Math.ceil(tempY))
&& wall.isWall(Math.ceil(tempX),Math.ceil(tempY))
&& wall.isWall(Math.ceil(tempX),Math.floor(tempY))) {
GameStage.itemLayer.graphics.beginFill(0xff00ff);
GameStage.itemLayer.graphics.drawRect(tempX, tempY, 5, 5);
flag = true;
break;
}
}
if(!flag){
GameStage.itemLayer.graphics.beginFill(0x00ffff);
for(var d:int=i+1; d<j; d++){
//GameStage.itemLayer.graphics.drawRect(route[d][0], route[d][1], 5, 5);
}
route.splice(i+1, j-i-1);
break;
}else{
j--;
}
}
i++;
j=route.length-1;
}
//GameStage.itemLayer.graphics.endFill();
}