看了幾天的A*算法,感受要成爲一個遊戲開發者,有必要把這個著名的算法拿到手。算法
網上有此算法的代碼片斷,但主要仍是些模板類的僞代碼,因此想分享一段完整的A*算法代碼供你們更好的理解!(這裏使用的是js語言和cocos2d遊戲引擎)數組
對於A*算法概念性的描述,請看這裏,本篇主要以代碼爲主。學習
下面是個人學習成果,有晦澀和需改進的地方歡迎吐槽!ui
var A_STAR_DISTANCE = 7; //像素大小,越小越精確,同時越耗時 var A_STAR_G_EXPEND_MIN = 10; //上下左右G值消耗數 var A_STAR_G_EXPEND_MAX = 14; //斜角G值消耗數 var HelloWorldLayer = cc.Layer.extend({ sprite:null, //角色 aStarPathArray:[], //最終角色要行走的路徑 aStarBarrierArray:[], //地圖障礙物 testNumber:1, ctor:function () { ////////////////////////////// // 1. super init first this._super(); ///////////////////////////// // 2. add a menu item with "X" image, which is clicked to quit the program // you may modify it. // ask director the window size var size = cc.director.getWinSize(); this.sprite = cc.Sprite.create(res.Plane_png); //角色初始化 this.sprite.attr({ x: 150, y: 50, rotation: 90 }); this.addChild(this.sprite, 0); var barrier1 = cc.rect(200,50,50,350); //繪製障礙物 var barrier2 = cc.rect(250,50,350,50); var barrier3 = cc.rect(250,350,350,50); this.aStarBarrierArray.push(barrier1); this.aStarBarrierArray.push(barrier2); this.aStarBarrierArray.push(barrier3); var drawBarrier = cc.DrawNode.create(); //在屏幕上顯示障礙物 this.addChild(drawBarrier, 10); var vertices = [cc.p(200,50),cc.p(600,50),cc.p(600,100),cc.p(250,100),cc.p(250,350),cc.p(600,350),cc.p(600,400),cc.p(200,400)]; drawBarrier.drawPoly(vertices,null,2,cc.color(255,0,0,255)); if ('mouse' in cc.sys.capabilities) cc.eventManager.addListener({ event: cc.EventListener.MOUSE, onMouseUp:function(event){ if(event.getButton() != undefined) { var t = new Date().getTime(); event.getCurrentTarget().getStartAndEndPoint(event); //A*算法開始 cc.log("算法耗時:"+(new Date().getTime() - t)+"ms"); //計算起點到終點的算法耗時 } } }, this); return true; }, getStartAndEndPoint:function (event) { //獲得起始點和終點座標 var sp = {coordX:parseInt(this.sprite.x,10),coordY:parseInt(this.sprite.y,10)}; var ep = {coordX:parseInt(event.getLocation().x,10),coordY:parseInt(event.getLocation().y,10)}; var endPointIsObstacle = false; //判斷終點是否在障礙物上,是的話就提示路徑走不了 for (var theBarrierIndex=0; theBarrierIndex<this.aStarBarrierArray.length; theBarrierIndex++){ if(cc.rectContainsPoint(this.aStarBarrierArray[theBarrierIndex],cc.p(ep.coordX,ep.coordY))) { endPointIsObstacle = true; cc.log("你要去的位置有障礙物,請換條路線"); break; } } if(!endPointIsObstacle) this.findingPath(sp,ep); }, findingPath:function(startPoint,endPoint){ //A*算法 var openList = []; //初始化開啓列表 var closeList = []; //初始化關閉列表 startPoint.ag = 0; startPoint.ah = 0; startPoint.af = startPoint.ag + startPoint.ah; //起點的G,H,F值爲0 openList.push(startPoint); //起點加入開啓列表 var findTheWay = false; do{ var centerNode = this.findMinNode(openList); //尋找F值最低的節點 openList.remove(centerNode); //將此節點從開啓列表中刪除,爲了下次遍歷開啓列表的時候再也不出現此節點 closeList.push(centerNode); //並將此節點加入到關閉列表 for(var i=0;i<8;i++) //遍歷此節點周圍的節點,並給這些節點加入座標屬性和G值 { var aroundNode = {}; switch (i){ case 0: aroundNode.coordX = centerNode.coordX+A_STAR_DISTANCE; //座標屬性 aroundNode.coordY = centerNode.coordY+A_STAR_DISTANCE; break; case 1: aroundNode.coordX = centerNode.coordX+A_STAR_DISTANCE; aroundNode.coordY = centerNode.coordY; break; case 2: aroundNode.coordX = centerNode.coordX+A_STAR_DISTANCE; aroundNode.coordY = centerNode.coordY-A_STAR_DISTANCE; break; case 3: aroundNode.coordX = centerNode.coordX; aroundNode.coordY = centerNode.coordY-A_STAR_DISTANCE; break; case 4: aroundNode.coordX = centerNode.coordX-A_STAR_DISTANCE; aroundNode.coordY = centerNode.coordY-A_STAR_DISTANCE; break; case 5: aroundNode.coordX = centerNode.coordX-A_STAR_DISTANCE; aroundNode.coordY = centerNode.coordY; break; case 6: aroundNode.coordX = centerNode.coordX-A_STAR_DISTANCE; aroundNode.coordY = centerNode.coordY+A_STAR_DISTANCE; break; case 7: aroundNode.coordX = centerNode.coordX; aroundNode.coordY = centerNode.coordY+A_STAR_DISTANCE; break; } for (var barrierIndex=0; barrierIndex<this.aStarBarrierArray.length; barrierIndex++){ aroundNode.isOb = cc.rectContainsPoint(this.aStarBarrierArray[barrierIndex],cc.p(aroundNode.coordX,aroundNode.coordY)); //判斷當前節點是否在障礙物造成的方框裏 if(aroundNode.isOb) break; } if (aroundNode.isOb){ //若是是障礙物,跳過 } else if(closeList.hasObject(aroundNode)){ //若是在關閉列表裏,跳過 } else if(!openList.hasObject(aroundNode)){ //若是不在開啓列表裏,加入到開啓列表 aroundNode.parentPath = centerNode; if (Math.abs(aroundNode.coordX-endPoint.coordX)<=A_STAR_DISTANCE/2 && Math.abs(aroundNode.coordY-endPoint.coordY)<=A_STAR_DISTANCE/2) //若是節點和終點的值相近,那麼A*算法結束,獲得路徑 { findTheWay = true; var pathArry = []; this.gettingAStarPath(aroundNode,pathArry); //尋找路徑 pathArry.splice(0,0,{starX:endPoint.coordX,starY:endPoint.coordY}); //加終點到數組頭部 pathArry.splice(pathArry.length-1,1); //刪一項數組底部的起點數據,此時的數組是最終的路徑數組 this.aStarPathArray = []; this.aStarPathArray = pathArry; this.aStarPathArray.theIndex = this.aStarPathArray.length; this.unschedule(this.thePathSelector); this.schedule(this.thePathSelector,null,pathArry.length-1); break; //找到最短路徑並跳出循環 } if (aroundNode.coordX!=centerNode.coordX && aroundNode.coordY!=centerNode.coordY) //肯定中心節點和周圍節點造成的角度,正交G值消耗10*像素,斜角G值消耗14*像素 aroundNode.ag = centerNode.ag + A_STAR_G_EXPEND_MAX*A_STAR_DISTANCE; else aroundNode.ag = centerNode.ag + A_STAR_G_EXPEND_MIN*A_STAR_DISTANCE; aroundNode.af = this.getAF(aroundNode,endPoint); openList.push(aroundNode); } else if(openList.hasObject(aroundNode)){ //若是在開啓列表裏 var newExpend = A_STAR_G_EXPEND_MIN*A_STAR_DISTANCE; if (aroundNode.coordX!=centerNode.coordX && aroundNode.coordY!=centerNode.coordY) //肯定中心節點和周圍節點造成的角度,正交G值消耗10*像素,斜角G值消耗14*像素 newExpend = A_STAR_G_EXPEND_MAX*A_STAR_DISTANCE; if (centerNode.ag + newExpend < aroundNode.ag){ //若是新的g值小於周圍節點自己的g值,那麼周圍節點的父節點改成當前中心節點,並從新計算其F值 aroundNode.parentPath = centerNode; aroundNode.ag = centerNode.ag + newExpend; aroundNode.af = this.getAF(aroundNode,endPoint); } } } }while(!findTheWay) }, findMinNode:function(openListArray){ var minNode = openListArray[0]; for (var i=0;i<openListArray.length;i++) { if (minNode.af>openListArray[i].af) minNode=openListArray[i]; } return minNode; }, getAF:function(thisNode,endNode){ var aHExpend = (Math.abs(thisNode.coordX-endNode.coordX) + Math.abs(thisNode.coordY-endNode.coordY))*A_STAR_G_EXPEND_MIN; return aHExpend+thisNode.ag; }, gettingAStarPath:function(laseNode,array){ if(laseNode.parentPath != null) { array.push({starX:laseNode.parentPath.coordX,starY:laseNode.parentPath.coordY}); this.gettingAStarPath(laseNode.parentPath,array); } }, thePathSelector:function(){ this.roleRunThePath(this.aStarPathArray); }, roleRunThePath:function(array){ this.sprite.x = array[--array.theIndex].starX; this.sprite.y = array[array.theIndex].starY; } }); var HelloWorldScene = cc.Scene.extend({ onEnter:function () { this._super(); var layer = new HelloWorldLayer(); this.addChild(layer); } }); //這裏給Array數組添加3個實例方法 Array.prototype.aStarIndexOf = function(val) { //經過對象尋找index值 for (var i = 0; i < this.length; i++) { if (this[i].coordX==val.coordX && this[i].coordY==val.coordY) return i; } return -1; }; Array.prototype.remove = function(val) { //刪除相應的對象 var index = this.aStarIndexOf(val); if (index > -1) { this.splice(index, 1); } }; Array.prototype.hasObject = function(val){ //判斷是不是同一個對象 for (var i = 0; i < this.length; i++){ if (this[i].coordX==val.coordX && this[i].coordY==val.coordY) return true; } return false; };
以下圖,飛機在尋找路徑的時候會避開紅色區域。this