• A*寻路算法 (cocos2d-js详细代码)


    看了几天的A*算法,感觉要成为一个游戏开发者,有必要把这个著名的算法拿到手。

    网上有此算法的代码片段,但主要还是些模板类的伪代码,所以想分享一段完整的A*算法代码供大家更好的理解!(这里使用的是js语言和cocos2d游戏引擎)

    对于A*算法概念性的描述,请看这里,本篇主要以代码为主。

    下面是我的学习成果,有晦涩和需改进的地方欢迎吐槽!

    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;
    };

    如下图,飞机在寻找路径的时候会避开红色区域。

  • 相关阅读:
    [工具推荐]005.Axure RP Pro 7.0模拟C#TAB控件
    [安卓基础] 008.Android中的显示单位
    [JavaWeb基础] 008.Spring初步配置
    [批处理教程之Shell]002.Linux 常用命令大全
    [注]新手学习编程的最佳方式是什么?
    [C#打包部署教程]001.VS2012 + InstallShield
    [站点推荐]001.学习新技能的37个最佳网站(The 37 Best Websites To Learn Something New)
    程序员如何像写代码一样找女朋友
    [工具-006] C#如何模拟发包登录
    [Linux系统] (1)常用操作(CentOS 7.x)
  • 原文地址:https://www.cnblogs.com/rhythm2014/p/3769893.html
Copyright © 2020-2023  润新知