A*尋路算法 (cocos2d-js詳細代碼)

看了幾天的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

相關文章
相關標籤/搜索