A *寻路算法

来源:互联网 发布:荒野猎人 知乎 编辑:程序博客网 时间:2024/05/16 04:53

直接上代码哦。是用as3实现的。注释很详细,直接看注释就OK。

一共两个as文件,一个是Maps.as,还有一个ARoad.as

Maps.as

package {
 import flash.display.MovieClip;
 import flash.display.Sprite;
 import flash.events.KeyboardEvent;
 import flash.events.MouseEvent;
 import flash.events.TimerEvent;
 import flash.text.TextField;
 import flash.ui.Keyboard;
 import flash.utils.Timer;
 /**
  *  Author 吴日辉
  */
 [SWF(width="900", height="540", frameRate="25", backgroundColor="0xffffff")]
 public class Maps extends Sprite {
  private var w:uint;//横向节点数
  private var h:uint;//纵向节点数
  private var wh:uint;//节点的宽与高
  private var goo:Number;//控制地图中节点是否可通过的比率,数值在0.1-0.5之间,数值越小,地图上障碍越多
  private var map:Sprite;//地图容器
  private var mapArr:Array=new Array;//地图信息数组
  private var roadMen:MovieClip;//寻路人
  private var roadList:Array;//寻路返回的路径
  private var roadTimer:Timer;//计数器
  private var timer_i:uint=0;//配合计数器实现寻路人动画(很郁闷,不知如何在Timer事件中传递参数,只能这么解决)
  private var roadinf:TextField = new TextField();
  private var roadLen:TextField = new TextField();;
  public function Maps() {
   init();
  }
  //初始化---------------------》
  private function init():void {
   w=100;//地横向节点
   h=60;//地图竖向节点
   wh=9;//节点大小
   goo=0.3;//地图障碍几率
   createMaps();//生成随机地图
   roadMens();//生成寻路人
   roadTimer=new Timer(80,0);//定义计时器以完成寻路人行走动画
   stage.addEventListener(KeyboardEvent.KEY_DOWN,keyDowns);//空格按键事件
  }//End fun init
  
  //当用户点击地图时开始寻路-------------》
  private function mapMousedown(evt:MouseEvent):void {
   var endX:Number=Math.floor((mouseX-map.x)/ wh);//将鼠标点击位置转化为节点索引值
   var endY:Number=Math.floor((mouseY-map.y)/ wh);//将鼠标点击位置转化为节点索引值
   var endPoint:MovieClip=mapArr[endY][endX];//从地图中取出鼠标点击的节点作为寻路终点
   //如果目的地是可通过的则开始寻路
   if (endPoint.go == 0) {
    //每次寻路开始前将上次的路径清空
    if (roadList) {
     for each (var mc in roadList) {
      mc.alpha=1;
     }
     roadList=[];
    }//End if
    roadTimer.stop();//停止走路
    //动态取得寻路人当前位置的索引,并更新
    roadMen.px=Math.floor(roadMen.x/wh);
    roadMen.py=Math.floor(roadMen.y/wh);
    var _ARoad:ARoad=new ARoad();//生成寻路实例
    var oldTimes:int=new Date().getTime();//记录发送寻路方法时间
    roadList=_ARoad.searchRoad(roadMen,endPoint,mapArr);//调用寻路方法(寻路人,目的地,地图信息)
    var times:int=new Date().getTime()-oldTimes;//寻路方法执行完毕计算寻路花费时间
    if (roadList.length>0) {
     roadinf.htmlText="<FONT color='#00ff00'>本次寻路: "+times.toString()+"毫秒</FONT> ";
     addChild(roadinf);
     roadLen.htmlText="<FONT color='#00ff00'>路径长度: "+roadList.length.toString()+"</FONT>";//路径长度
     roadLen.y = 20;
     addChild(roadLen);
     MC_play(roadList);//让寻路人行走
    } else {
     roadinf.htmlText="<FONT color='#00ff00'>对不起,无路可走</FONT>";
     addChild(roadinf);
    }//End if
   }//End if
  }//End fun
  
  //寻路人行走----------------------》
  private function MC_play(roadList:Array):void {
   roadList.reverse();//倒转数组
   roadTimer.stop();
   timer_i=0;
   roadTimer.addEventListener(TimerEvent.TIMER,goMap);
   roadTimer.start();
   for each (var mc in roadList) {
    mc.alpha=0.3;
   }//End if
  }//End fun
  //每隔一定时间行走一格----------------》
  private function goMap(evt:TimerEvent):void {
   var tmpMC:MovieClip=roadList[timer_i];
   roadMen.x=tmpMC.x;
   roadMen.y=tmpMC.y;
   tmpMC.alpha=1;//经过路径后消除其标识状态
   timer_i++;
   //达到终点行走停止
   if (timer_i>=roadList.length) {
    roadTimer.stop();
   }//End if
  }//End fun
  //生成地图并存储信息-----------------》
  private function createMaps() {
   map=new Sprite  ;//地图容器
   addChild(map);
   map.addEventListener(MouseEvent.MOUSE_DOWN,mapMousedown);//鼠标点击地图事件
   for (var y:uint=0; y < h; y++) {
    mapArr.push(new Array  );//建立二维数组存储地图信息
    for (var x:uint=0; x < w; x++) {
     var mapPoint:uint=Math.round(Math.random() - goo);//随机节点可通过与不可通过
     var point:MovieClip=drawRect(mapPoint);//画出节点
     mapArr[y].push(point);//将节点加入地图数组中
     mapArr[y][x].px=x;//当前节点横向索引位置
     mapArr[y][x].py=y;//当前节点纵向索引位置
     mapArr[y][x].go=mapPoint;//当前节点是否可通过
     mapArr[y][x].x=x * wh;//当前节点的x位置
     mapArr[y][x].y=y * wh;//当前节点的y位置
     map.addChild(mapArr[y][x]);//将节点显示到地图容器中
    }//End for x
   }//End for y
  }//End fun
  //空格键重生地图--------------------》
  private function keyDowns(evt:KeyboardEvent) {
   var _key=evt.keyCode;
   if ( _key == Keyboard.SPACE) {
    removeChild(map);
    mapArr=[];
    createMaps();
    roadMens();//生成寻路人
    roadTimer.stop();
   }//End if
  }//End if
  
  //根据传入的随机数画出不同的节点(即可通过/不可通过/寻路人)---》
  private function drawRect(mapPoint:uint):MovieClip {
   var _tmp:MovieClip=new MovieClip;
   var color:uint;
   switch (mapPoint) {
    case 0 :
     color=0x999999;//可通过为灰色
     break;
    case 1 :
     color=0x000000;//不可通过为黑色
     break;
    default :
     color=0xFF0000;//否则为寻路人
   }//End switch
   _tmp.graphics.beginFill(color);
   _tmp.graphics.lineStyle(0.2,0xFFFFFF);
   _tmp.graphics.drawRect(0,0,wh,wh);
   _tmp.graphics.endFill();
   return _tmp;
  }//End fun drawRect
  
  //生成寻路人-------------------------》
  private function roadMens() {
   roadMen=drawRect(2);
   //让寻路人随机出现在地图上并设置寻路人的横纵向索引位置----->
   var _tmpx:uint=Math.round(Math.random() * (w-1));
   var _tmpy:uint=Math.round(Math.random() * (h-1));
   roadMen.px=_tmpx;//记录所在位置索引值
   roadMen.py=_tmpy;
   roadMen.x=_tmpx * wh;
   roadMen.y=_tmpy * wh;
   mapArr[_tmpy][_tmpx].go=0;//让寻路人出现的地图点变为可通过
   map.addChild(roadMen);
  }
  
 }
}

ARoad.as

package {
 import flash.display.MovieClip;
 import flash.display.Sprite;
 import flash.events.MouseEvent;
 import flash.utils.Timer;
 import flash.events.TimerEvent;
 import flash.events.KeyboardEvent;
 import flash.ui.Keyboard;
 import flash.text.TextField;
 /**
  *  Author 吴日辉
  */
 public class ARoad extends Sprite {
  private var startPoint:MovieClip;//寻路起点
  private var endPoint:MovieClip;//要到达的目的地
  private var mapArr:Array;//地图信息
  private var w:uint;//地图的横向节点数
  private var h:uint;//地图的纵向节点数
  private var openList:Array=new Array();//开启列表
  private var closeList:Array=new Array();//关闭列表
  private var roadArr:Array=new Array();//返回的路径
  public function ARoad() {
  }//End Fun
  //对外的寻路接口
  public function searchRoad(start:MovieClip,end:MovieClip,map:Array) {
   startPoint=start;//获得寻路起点
   endPoint=end;//获得要到达的目的地
   mapArr=map;//获得地图信息
   w=mapArr[0].length-1;//获得地图横向的节点数
   h=mapArr.length-1;//获得地图纵向的节点数
   openList.push(startPoint);//将起点加入开启列表
   while (true) {
    if (openList.length<1) {//无路可走
     //trace("无路可走");
     return roadArr;
     break;
    }
    var thisPoint:MovieClip=openList.splice(getMinF(),1)[0];//每次取出开启列表中F最低的节点
    if (thisPoint==endPoint) {//找到路径
     //trace("找到路径");
     //从终点开始往回找父节点,以生成路径列表,直到父节点为起始点
     while (thisPoint.father!=startPoint.father) {
      roadArr.push(thisPoint);
      thisPoint=thisPoint.father;
     }
     return roadArr;//返回路径列表
     break;
    }
    closeList.push(thisPoint);//把当前节点加入关闭列表
    addAroundPoint(thisPoint);//开始检查当前节点四周的节点
   }//End while
  }//End Fun
  
  //检查当前节点四周的八个节点,可通过并不在关闭及开启列表中的节点加入至开启列表
  private function addAroundPoint(thisPoint:MovieClip) {
   var thisPx:uint=thisPoint.px;//当前节点横向索引
   var thisPy:uint=thisPoint.py;//当前节点纵向索引
   //添加左右两个直点的同时过滤四个角点,以提高速度。
   //即如果左边点不存在或不可通过则左上左下两角点就不需检查,右边点不存在或不可通过则右上右下两角点不需检查
   //后面添加四个为角点,角点的判断为,自身可通过&&它相邻的两个当前点的直点都可通过
   if (thisPx>0 && mapArr[thisPy][thisPx - 1].go==0 ) {//加入左边点
    if (!inArr(mapArr[thisPy][thisPx - 1],closeList)) {//是否在关闭列表中
     if (!inArr(mapArr[thisPy][thisPx - 1],openList)) {//是否在开启列表中
      setGHF(mapArr[thisPy][thisPx - 1],thisPoint,10);//计算GHF值
      openList.push(mapArr[thisPy][thisPx - 1]);//加入节点
     } else {
      checkG(mapArr[thisPy][thisPx-1],thisPoint);//检查G值
     }//End if
    }//End if
    //加入左上点
    if (thisPy>0 && mapArr[thisPy-1][thisPx - 1].go==0 && mapArr[thisPy - 1][thisPx].go==0) {
     if (!inArr(mapArr[thisPy-1][thisPx - 1],closeList) && !inArr(mapArr[thisPy-1][thisPx - 1],openList)) {
      setGHF(mapArr[thisPy - 1][thisPx-1],thisPoint,14);//计算GHF值
      openList.push(mapArr[thisPy-1][thisPx - 1]);//加入节点
     }//End if
    }//End if
    //加入左下点
    if (thisPy<h && mapArr[thisPy+1][thisPx - 1].go==0  && mapArr[thisPy + 1][thisPx].go==0) {
     if (!inArr(mapArr[thisPy+1][thisPx - 1],closeList) && !inArr(mapArr[thisPy+1][thisPx - 1],openList)) {
      setGHF(mapArr[thisPy + 1][thisPx-1],thisPoint,14);//计算GHF值
      openList.push(mapArr[thisPy+1][thisPx - 1]);//加入节点
     }//End if
    }//End if
   }//End if
   if (thisPx<w && mapArr[thisPy][thisPx + 1].go==0) {//加入右边点
    if (!inArr(mapArr[thisPy][thisPx + 1],closeList)) {//是否在关闭列表中
     if (!inArr(mapArr[thisPy][thisPx + 1],openList)) {//是否在开启列表中
      setGHF(mapArr[thisPy][thisPx + 1],thisPoint,10);//计算GHF值
      openList.push(mapArr[thisPy][thisPx + 1]);//加入节点
     } else {
      checkG(mapArr[thisPy][thisPx + 1],thisPoint);//检查G值
     }//End if
    }//End if
    //加入右上点
    if (thisPy>0 && mapArr[thisPy-1][thisPx +1].go==0  && mapArr[thisPy - 1][thisPx].go==0) {
     if (!inArr(mapArr[thisPy-1][thisPx + 1],closeList) && !inArr(mapArr[thisPy-1][thisPx + 1],openList)) {
      setGHF(mapArr[thisPy - 1][thisPx+1],thisPoint,14);//计算GHF值
      openList.push(mapArr[thisPy-1][thisPx + 1]);//加入节点
     }//End if
    }//End if
    //加入右下点
    if (thisPy<h && mapArr[thisPy+1][thisPx + 1].go==0 && mapArr[thisPy + 1][thisPx].go==0) {
     if (!inArr(mapArr[thisPy+1][thisPx+ 1],closeList) && !inArr(mapArr[thisPy+1][thisPx + 1],openList)) {
      setGHF(mapArr[thisPy + 1][thisPx+1],thisPoint,14);//计算GHF值
      openList.push(mapArr[thisPy+1][thisPx + 1]);//加入节点
     }//End if
    }//End if
   }//End if
   if (thisPy>0 && mapArr[thisPy - 1][thisPx].go==0) {//加入上面点
    if (!inArr(mapArr[thisPy - 1][thisPx],closeList)) {//是否在关闭列表中
     if (!inArr(mapArr[thisPy - 1][thisPx],openList)) {//是否在开启列表中
      setGHF(mapArr[thisPy - 1][thisPx],thisPoint,10);//计算GHF值
      openList.push(mapArr[thisPy - 1][thisPx]);//加入节点
     } else {
      checkG(mapArr[thisPy - 1][thisPx],thisPoint);//检查G值
     }//End if
    }//End if
   }//End if
   if (thisPy<h && mapArr[thisPy + 1][thisPx].go==0) {//加入下面点
    if (!inArr(mapArr[thisPy + 1][thisPx],closeList)) {//是否在关闭列表中
     if (!inArr(mapArr[thisPy + 1][thisPx],openList)) {//是否在开启列表中
      setGHF(mapArr[thisPy + 1][thisPx],thisPoint,10);//计算GHF值
      openList.push(mapArr[thisPy + 1][thisPx]);//加入节点
     } else {
      checkG(mapArr[thisPy + 1][thisPx],thisPoint);//检查G值
     }//End if
    }//End if
   }//End if
  }//End Fun
  //判断当前点是否在某个列表中----------------------------》
  private function inArr(obj:MovieClip,arr:Array):Boolean {
   for each (var mc in arr) {
    if (obj == mc) {
     return true;
    }//End if
   }//End for
   return false;
  }//End Fun
  
  //设置节点的G/H/F值----------------------------》
  private function setGHF(point:MovieClip,thisPoint:MovieClip,G) {
   if (!thisPoint.G) {
    thisPoint.G=0;
   }
   point.G=thisPoint.G+G;
   //H值为当前节点的横纵向到终点的节点数×10
   point.H=(Math.abs(point.px - endPoint.px) + Math.abs(point.py - endPoint.py))*10;
   point.F=point.H + point.G;//计算F值
   point.father=thisPoint;//指定父节点
  }//End Fun
  
  //检查新的G值以判断新的路径是否更优
  private function checkG(chkPoint:MovieClip,thisPoint:MovieClip) {
   var newG=thisPoint.G + 10;//新G值为当前节点的G值加上10(因为只检查当前节点的直点)
   if (newG <= chkPoint.G) {//如果新的G值比原来的G值低或相等,说明新的路径会更好
    chkPoint.G=newG;//更新G值
    chkPoint.F=chkPoint.H+newG;//同时F值重新被计算
    chkPoint.father=thisPoint;//将其父节点更新为当前点
   }//End if
  }//End Fun
  
  //获取开启列表中的F值最小的节点,返回的是该节点所在的索引
  private function getMinF():uint {
   var tmpF:uint=100000000;//用以存放最小F值(这里先假定了一个很大的数值)
   var id:uint=0;
   var rid:uint;
   for each (var mc in openList) {
    //如果列表中的当前节点的F值比目前存放的F值小,就将F值更新为当前节点的F值,否则就什么都不做
    //这样循环和列表中所有节点的F值比较完成后,最后用以存放最小F值里的F值就是最小的
    if (mc.F<tmpF) {
     tmpF=mc.F;
     rid=id;//同时更新返加的索引值为当前节点的索引
    }
    id++;//因为for each方法是从数组中的第一个对象开始遍历,而每比一次id+1刚好可以匹配其索引位置
    //也可以使用FOR遍历,但FLASH中用 FOR EACH方法效率更高
   }//End for
   return rid;//比较完成后返回最小F值所在的索引
  }//End fun
 }//End Class
}//End package

 

总结:对openList的维护开销较大(因为每次都需要遍历得出F值最小的那个节点,可以采取二叉树排序来解决)。上述算法适合小地图。

最后推荐一篇文章。我觉讲的非常详细。http://www.cppblog.com/christanxw/archive/2006/04/07/5126.html

原创粉丝点击