首页 > 代码库 > cocos2d-js版本A*算法

cocos2d-js版本A*算法

【转】http://blog.csdn.net/realcrazysun1/article/details/43054229

A*算法的东西网上讲了很多~但还是不可避免的要去研究一下,cocos官网上有一个cocos2dx版本的A星算法(cocos2d-x A星算法),正好拿来改造一下,顺便踩踩cocos2d-js的坑

原理和伪代码部分可以参考这个(A*算法伪代码)废话不多说,直接上正题.

主要有2个封装原型类:

1、GameLayer:负责加载地图、保持地图跟随、坐标转换、地图上物品判断、接受触控事件

var GameLayer = cc.Layer.extend({
    _tileMap:null,
    _bgLayer:null,
    _objectLayer:null,
    _batchNode:null,
    _cat:null,
    bonesCount:null,
    ctor:function () {
        // ////////////////////////////
        // 1. super init first
        this._super();
        // 加载地图
        this._tileMap = new cc.TMXTiledMap(res.CatMaze_tmx);
        this._bgLayer = this._tileMap.getLayer("Background");
        this._objectLayer = this._tileMap.getLayer("Objects");
        this.addChild(this._tileMap);
        
        // 25*25 tiles
        cc.log("map size width:"+this._tileMap.getMapSize().width+"        height:    "+this._tileMap.getMapSize().height);
        
        // tile size: 32*32
        cc.log("tile size width:"+this._tileMap.getTileSize().width+"        height:    "+this._tileMap.getTileSize().height);
        
        // tile坐标
        var spawnTileCoord = cc.p(24, 0);
        var spawnPos = this.positionForTileCoord(spawnTileCoord);
        
        // 视角跟随
        this.setViewpointCenter(spawnPos);
        // 背景音乐
        cc.audioEngine.playMusic(res.SuddenDefeat_mp3,true);

        cc.spriteFrameCache.addSpriteFrames(res.CatMaze_plist);
        
        //存放在地图上面~则会跟随地图
        this._batchNode = new cc.SpriteBatchNode(res.CatMaze_png);
        this._tileMap.addChild(this._batchNode);
        
        this._cat = new CatSprite(this);
        this._cat.setPosition(spawnPos);
        this._batchNode.addChild(this._cat);
        
        // bmfont 字体
        this.bonesCount = new cc.LabelBMFont("Bones: 0", res.Arial_fnt);
        this.bonesCount.setPosition(400, 30);
        this.addChild(this.bonesCount);
        
        cc.eventManager.addListener({
                event: cc.EventListener.TOUCH_ONE_BY_ONE,
                swallowTouches: true,
                onTouchBegan:function (touch, event) {
                    // 猫移动向该点
                    var point = event.getCurrentTarget()._tileMap.convertTouchToNodeSpace(touch);
                    cc.log("touch point x:    "+point.x+"y:    "+point.y);
                    event.getCurrentTarget()._cat.moveToward(point);
                    return true;
                }
        }, this);
        
        this.scheduleUpdate();
        return true;
    },
    
    // 坐标转换为tile
    positionForTileCoord:function(p){
        var x = (p.x * this._tileMap.getTileSize().width) + this._tileMap.getTileSize().width / 2;
        var y = (this._tileMap.getMapSize().height *this. _tileMap.getTileSize().height) -
        (p.y *this._tileMap.getTileSize().height) - this._tileMap.getTileSize().height / 2;
        return cc.p(x, y);
    },
    
    // 地图跟随
    setViewpointCenter:function(position){
        var size = cc.director.getWinSize();
        var x = Math.max(position.x, size.width / 2);
        var y = Math.max(position.y, size.height / 2);
        x = Math.min(x, (this._tileMap.getMapSize().width * this._tileMap.getTileSize().width) - size.width / 2);
        y = Math.min(y, (this._tileMap.getMapSize().height * this._tileMap.getTileSize().height) - size.height / 2);
        var p = cc.p(x,y);
        var center = cc.p(size.width/2,size.height/2);
        var viewPoint = cc.pSub(center,p);

        this._tileMap.setPosition(viewPoint);
    },
    update:function(){
        this.setViewpointCenter(this._cat.getPosition());
    },
    tileCoordForPosition:function(position){
        var x = parseInt( position.x / this._tileMap.getTileSize().width);
        var y = parseInt(((this._tileMap.getMapSize().height *this._tileMap.getTileSize().height) - position.y) / this._tileMap.getTileSize().height);
        return cc.p(x, y);
    },
    //是否是墙壁
    isWallAtTileCoord:function(tileCoord){
        return this.isPropAtTileCoordForLayer("Wall", tileCoord, this._bgLayer);
    },
    //显示骨头
    showNumBones:function(numBones)
    {
        this.bonesCount.setString("Bones: "+ numBones);
    },
    isValidTileCoord:function(tileCoord){
        if (tileCoord.x < 0 || tileCoord.y < 0 ||
                tileCoord.x >= this._tileMap.getMapSize().width ||
                tileCoord.y >= this._tileMap.getMapSize().height)
        {
            return false;
        }
        else
        {
            return true;
        }
    },
    //是否有骨头
    isBoneAtTilecoord:function(tileCoord)
    {
        //bone 存放在_objectLayer上
        return this.isPropAtTileCoordForLayer("Bone", tileCoord, this._objectLayer);
    },
    //是否有狗
    isDogAtTilecoord:function(tileCoord) 
    {
        return this.isPropAtTileCoordForLayer("Dog", tileCoord,  this._objectLayer);
    },
    
    //是否为出口
    isExitAtTilecoord:function(tileCoord) 
    {
        return this.isPropAtTileCoordForLayer("Exit", tileCoord, this._objectLayer);
    },
    
    //判断tile上存放了什么
    isPropAtTileCoordForLayer:function(prop,tileCoord, layer)
    {
        if (!this.isValidTileCoord(tileCoord))
        {
            return false;
        }
        
        //获得tile对应id
        var gid = layer.getTileGIDAt(tileCoord);
        
        //这里返回的是dict类型
        var properties = this._tileMap.getPropertiesForGID(gid);
        if (properties==null)
        {
            return false;
        }
        return properties[prop]==1;
    },
    //移除tiles
    removeObjectAtTileCoord:function(tileCoord){
        this._objectLayer.removeTileAt(tileCoord);
    },
    winGame:function(){
        cc.log("win");
        this.endScene();
    },
    loseGame:function(){
        cc.log("lose");
        this.endScene();
    },
    endScene:function()
    {
        var self = this;
        this._cat.runAction(cc.sequence( cc.scaleBy(0.5, 3.0),
                cc.delayTime(1.0),
                cc.scaleTo(0.5, 0.0),
                cc.callFunc(self.showRestartMenu, self)
                ));
        this._cat.runAction(cc.repeatForever(cc.rotateBy(0.5, 360.0)));
    },
    showRestartMenu:function(){
        cc.log("showRestartMenu");
    },
    //是否可以通过
    walkableAdjacentTilesCoordForTileCoord:function(tileCoord){
            var tmp = [];
            //
            var p1=cc.p(tileCoord.x, tileCoord.y - 1);
            if (this.isValidTileCoord(p1) && !this.isWallAtTileCoord(p1)){
                tmp.push(p1);
            }
            //
            var p2=cc.p(tileCoord.x - 1, tileCoord.y);            
            if (this.isValidTileCoord(p2) && !this.isWallAtTileCoord(p2)){
                tmp.push(p2);
            }
            //
            var p3=cc.p(tileCoord.x, tileCoord.y + 1);    
            if (this.isValidTileCoord(p3) && !this.isWallAtTileCoord(p3)){
                tmp.push(p3);
            }
            //
            var p4=cc.p(tileCoord.x + 1, tileCoord.y);    
            if (this.isValidTileCoord(p4) && !this.isWallAtTileCoord(p4)){
                tmp.push(p4);
            }
            cc.log("tileCoord: "+tileCoord.x+" "+tileCoord.y);
            for(var i = 0;i<tmp.length;i++){
                cc.log("tmp "+i+":    "+tmp[i].x+    "  "+tmp[i].y);
            }
            return tmp;
    }
});

2、CatSprite:负责A*算法实现、猫展示动画

var CatSprite = cc.Sprite.extend({
    _gameLayer:null,
    _facingForwardAnimation:null,
    _facingBackAnimation:null,
    _facingLeftAnimation:null,
    _facingRightAnimation:null,
    _bonenum:0,
    _shortestPath:[],//最短路径
    _spOpenSteps:[],//开放列表
    _spClosedSteps:[],//关闭列表
    _tempShortestPath:[],
    ctor:function(gameLayer){
        this._super("#cat_forward_1.png");
        this._gameLayer = gameLayer;

        this._facingForwardAnimation = this.createCatAnimation("forward");
        
        this._facingBackAnimation = this.createCatAnimation("back");
    
        this._facingLeftAnimation = this.createCatAnimation("left");
        
        this._facingRightAnimation = this.createCatAnimation("right");
            
        return true;
    },
    
    moveToward:function(target){
        cc.log("moveToward");
        var fromTileCoord = this._gameLayer.tileCoordForPosition(this.getPosition());
        var toTileCoord = this._gameLayer.tileCoordForPosition(target);
        
        if(toTileCoord.x == fromTileCoord.x&&toTileCoord.y==fromTileCoord.y){
            cc.log("You‘re already there! :P");
            return;
        }
        if(!this._gameLayer.isValidTileCoord(toTileCoord) ||this._gameLayer.isWallAtTileCoord(toTileCoord)){
            cc.audioEngine.playEffect(res.hitWall_wav);
            return;
        }
        cc.log("From:    " + fromTileCoord.x + "    "+ fromTileCoord.y);
        cc.log("To:    " + toTileCoord.x + "    "+ toTileCoord.y);
        
        this._spOpenSteps = [];
        this._spClosedSteps = [];
        // 首先,添加猫的方块坐标到open列表
        this.insertInOpenSteps(new ShortestPathStep(fromTileCoord));
        do{
            //这里要当心死循环        
            var currentStep = this._spOpenSteps[0];
            currentStep.retain();        
            cc.log("currentStep:"+currentStep.getPosition().x+"  "+currentStep.getPosition().y);
            // 添加当前步骤到closed列表
            this._spClosedSteps.push(currentStep);
            // 将它从open列表里面移除
            this._spOpenSteps.splice(0,1);
            // 如果当前步骤是目标方块坐标,那么就完成了
            if (toTileCoord.x == currentStep.x&&toTileCoord.y==currentStep.y){
                cc.log("path found");
                this.constructPathAndStartAnimationFromStep(currentStep);
                this._spOpenSteps = [];
                this._spClosedSteps = [];
                break;        
            }
            //this.printPath(currentStep);
            var adjSteps = this._gameLayer.walkableAdjacentTilesCoordForTileCoord(currentStep.getPosition());
            for (var i = 0; i < adjSteps.length; ++i){
                var step = new ShortestPathStep(adjSteps[i]);
                if (this.indexOf(this._spClosedSteps,step)!=-1){
            
                    continue;
                }
                var moveCost = this.costToMoveFromStepToAdjacentStep(currentStep, step);
                var index = this.indexOf(this._spOpenSteps,step);
                if (index == -1){
                    step.setParent(currentStep);
                    step.setGScore(currentStep.getGScore() + moveCost);
                    step.setHScore(this.computeHScoreFromCoordToCoord(step.getPosition(), toTileCoord));
                    this.insertInOpenSteps(step);
                }else{
                    step = this._spOpenSteps[index];
                    if ((currentStep.getGScore() + moveCost) < step.getGScore()){
                        step.setGScore(currentStep.getGScore() + moveCost);
                        // 因为G值改变了,F值也会跟着改变
                        // 所以为了保持open列表有序,需要将此步骤移除,再重新按序插入
                        // 在移除之前,需要先保持引用
//                        step.retain();
                        // 现在可以放心移除,不用担心被释放
                        this._spOpenSteps.splice(index,1);
//                        // 重新按序插入
                        this.insertInOpenSteps(step);
//                        // 现在可以释放它了,因为open列表应该持有它
//                        step.release();
                    }
                }
            }
        }while (this._spOpenSteps.length > 0);

        for (var i = 0;i<this._shortestPath.length;i++){
            cc.log("Description:", this._shortestPath[i].getDescription());
        }        

    },
    
    //序列帧动画
    createCatAnimation:function(animType)
    {
        var animation = new cc.Animation();
        for (var i = 1; i <= 2; ++i)
        {
            animation.addSpriteFrame(cc.spriteFrameCache.getSpriteFrame("cat_"+animType+"_"+i+".png"));
        }
        animation.setDelayPerUnit(0.2);
        animation.retain()
        return animation;
    },
    
    //用来判断step所在位置
    indexOf:function(array,step){
        if(array.length>0){
            for(var i = 0;i<array.length;i++){
                if(array[i].isEqual(step)){
                    return i;
                }
            }
        }
        return -1;
    },
    
    
    //插入一个step  维护一个有序列表
    insertInOpenSteps:function(step)
    {
        var stepFScore = step.getFScore();
        var count = this._spOpenSteps.length;
        var i ;
        for (i = 0; i < count; ++i)
        {
            if (stepFScore <= this._spOpenSteps[i].getFScore())
            {
                break;
            }
        }

        this._spOpenSteps.splice(i, 0, step);
    },
    
    //计算H值
    computeHScoreFromCoordToCoord:function(fromCoord,toCoord)
    {
        // 这里使用曼哈顿方法,计算从当前步骤到达目标步骤,在水平和垂直方向总的步数
        // 忽略了可能在路上的各种障碍
        return Math.abs(toCoord.x - fromCoord.x) +  Math.abs(toCoord.y - fromCoord.y);
    },
    //这里可以扩展~
    costToMoveFromStepToAdjacentStep:function(fromStep,toStep){
        //return ((fromStep->getPosition().x != toStep->getPosition().x)
        //    && (fromStep->getPosition().y != toStep->getPosition().y)) ? 14 : 10;
        return 1;
    },
    
    //构造最短路径
    constructPathAndStartAnimationFromStep:function(step){
        this._shortestPath=[];
        do{
            // 起始位置不要进行添加
            if (step.getParent())
            {
                // 总是插入到索引0的位置,以便反转路径
                this._shortestPath.splice(0,0,step); 
            }
            step = step.getParent();   // 倒退
        } while (step);                 // 直到没有上一步
        for (var i = 0;i<this._shortestPath.length;i++){
            cc.log("Description:", this._shortestPath[i].getDescription());
        }
        this.popStepAndAnimate();
    },
    //打印路径
    printPath:function(step){
        this._tempShortestPath=[];
        do{
            // 起始位置不要进行添加
            if (step.getParent())
            {
                // 总是插入到索引0的位置,以便反转路径
                this._tempShortestPath.splice(0,0,step); 
            }
            step = step.getParent();   // 倒退
        } while (step);                 // 直到没有上一步
        for (var i = 0;i<this._tempShortestPath.length;i++){
            cc.log("Description:", this._tempShortestPath[i].getDescription());
        }
        cc.log("---------------------------------------------------------------------");
    },
    //展示运动
    popStepAndAnimate:function(){
        var currentPosition = this._gameLayer.tileCoordForPosition(this.getPosition());
        if(this._gameLayer.isBoneAtTilecoord(currentPosition)){
            this._bonenum++;
            this._gameLayer.showNumBones(this._bonenum);
            this._gameLayer.removeObjectAtTileCoord(currentPosition);
        }else if(this._gameLayer.isDogAtTilecoord(currentPosition)){
            if (this._bonenum <= 0){
                this._gameLayer.loseGame();
                return;
            }else{
                this._bonenum--;
                this._gameLayer.showNumBones(this._bonenum);
                this._gameLayer.removeObjectAtTileCoord(currentPosition);
            }
        }else if (this._gameLayer.isExitAtTilecoord(currentPosition)){
            this._gameLayer.winGame();
        }
        
        var  s = this._shortestPath[0];
        
        if(s==undefined){
            return;
        }
        s.retain();
        var futurePosition = s.getPosition();
    
        var diff = cc.pSub(futurePosition , currentPosition);
        if (Math.abs(diff.x) > Math.abs(diff.y)){
            if (diff.x > 0){
                this.runAction(cc.animate(this._facingRightAnimation));
            }else{
                this.runAction(cc.animate(this._facingLeftAnimation));
            }
        }else{
            if (diff.y > 0){
                this.runAction(cc.animate(this._facingForwardAnimation));
            }else{
                this.runAction(cc.animate(this._facingBackAnimation));
            }
        }
            
        var moveAction = cc.moveTo(0.4, this._gameLayer.positionForTileCoord(s.getPosition()));
        
        this._shortestPath[0].retain();
        this._shortestPath.splice(0,1);
        var moveCallback = cc.callFunc(this.popStepAndAnimate,this);
        this.runAction(cc.sequence(moveAction,moveCallback))    
    },
});

cocos2d-js版本A*算法