`
Fis
  • 浏览: 87525 次
  • 性别: Icon_minigender_1
  • 来自: 龙城
社区版块
存档分类
最新评论

[原创] as3 做的 A*寻路

    博客分类:
  • AS
阅读更多
原创首发www.eb163.com,转载请保留出处 作者:iiacm (着火骷髅)
  A*算法的思想很丰富,游戏中用来解决人物角色、敌人的寻路问题是相当不错的方法。以前玩帝国时代的时候,看到里面的士兵跑路的路径都是择优而行,感到很奇妙,后来才知道,那是A*算法的功劳。随着逐渐的学习,对这个算法更是无比的倾慕,一度听到这个名词,心中一股钦佩之情油然而生…… 于是一直都在寻找学习的方法,不断的看帖、搜索,终于,在jr高手发布的帖子(http://www.eb163.com/club/viewthread.php?tid=2458&highlight=%D1%B0%C2%B7)和超哥的启发下,写出了自己的A*例子,感谢他们给予的帮助,拿出来和大家一起分享一下。
  演示地址:http://www.eb163.com/club/thread-11209-1-1.html

一、demo的结构
  demo分为三个部分:MainClass文档类——构建一个二维地图,并调用A*寻路类寻路;AStar A*寻路算法类——提供了所有寻路时用到的方法函数;AStarGrid 节点数据类——为寻路过程提供中间的数据结构,引入这个类的对象可以剥离地图单元,寻路时操作节点数据对象替代直接操作地图单元,方便管理。具体代码:

package{
        /*
        iiacm(着火骷髅),网页游戏开发网 www.eb163.com 
        网页游戏互动社区
        
        A*寻路算法启发性搜索 demo 文档类
        */
        import flash.display.Sprite;
        import flash.events.MouseEvent;
        import flash.geom.Point;
        public class MainClass extends Sprite{
                //设定地图单元行数
                protected var rowNum:uint=22;
                //设定地图单元列数
                protected var columnNum:uint=20;
                //设定地图单元宽度
                protected var mcWidth:uint=25;
                //设定地图单元高度
                protected var mcHeight:uint=20;
                //存储地图单元数据的数组
                protected var mcArr:Array=new Array();
                //声明一个A*寻路类的实例对象,把地图单元数据作为参数传入类的构造函数
                protected var aStar:AStar=new AStar(mcArr);
                //当前起点
                protected var nowPoint:Point=new Point(5,5);
                //当前终点
                protected var endPoint:Point;
                public function MainClass(){
                        init();
                }
                //初始化,创建地图
                protected function init(){
                        createMcs();
                }
                //根据行数列数创建地图,同时填充地图数据
                protected function createMcs(){
                        for(var i:int=0; i<columnNum; i++){
                                mcArr[i]=new Array();
                                for(var j:int=0; j<rowNum; j++){
                                        var sprite:Sprite=drawRect();
                                        sprite.x=j*mcWidth;
                                        sprite.y=i*mcHeight;
                                        sprite.name="mc_"+i+"_"+j;
                                        mcArr[i].push(sprite);
                                        addChild(sprite);
                                        sprite.addEventListener(MouseEvent.CLICK, onClickSpriteHandler);
                                }
                        }
                }
                //为地图单元添加的鼠标点击事件处理函数,由起点到被点击地图单元的位置进行寻路,改变alpha属性对找到的路径标记
                protected function onClickSpriteHandler(e:MouseEvent){
                        for(var i=0;i<mcArr.length;i++){
                                for(var j=0;j<mcArr[i].length;j++){
                                        mcArr[i][j].alpha=1;
                                }
                        }
                        endPoint=checkMapPos(e.currentTarget as Sprite);
                        aStar.findPath(nowPoint, endPoint);
                        nowPoint=endPoint;
                }
                //查找当前点击的地图单元,返回单元所在坐标
                protected function checkMapPos(mapGrid:Sprite):Point{
                        var point:Point;
                        for(var i=0;i<mcArr.length;i++){
                                for(var j=0;j<mcArr[i].length;j++){
                                        if(mapGrid.name==mcArr[i][j].name){
                                                point=new Point(i,j);
                                                break;
                                        }
                                }
                        }
                        return point;
                }
                //绘制地图单元
                protected function drawRect():Sprite{
                        var sprite:Sprite=new Sprite();
                        sprite.graphics.lineStyle(.25, 0x232323, 1);
                        sprite.graphics.beginFill(0xffffff, 0.5);
                        sprite.graphics.drawRect(0, 0, mcWidth, mcHeight);
                        sprite.graphics.endFill();
                        return sprite;
                }
        }
}


package{
        /*
        iiacm(着火骷髅),网页游戏开发网 www.eb163.com 
        网页游戏互动社区
        
        A*寻路类
                使用A*算法实现自动寻找最佳路径功能
        */
        import flash.geom.Point;
        import flash.display.DisplayObject;
        public class AStar{
                //封闭列表
                protected var _closeList:Array=new Array();
                //开放列表
                protected var _openList:Array=new Array();
                //地图节点数据
                protected var _mapData:Array;
                //存储寻路结果的数组
                protected var way:Array=new Array();
                //起点节点
                protected var _startNode:AStarGrid;
                //目标节点
                protected var _targetNode:AStarGrid;
                //当前操作节点
                protected var _nowNode:AStarGrid;
                //是否找到路径,起始值false,完成寻路后为true
                protected var _isFind:Boolean=false;
                //从起点起点沿着已生成的路径到给定节点的移动开销,直线方向为g=10,斜线方向为obliqueG=14
                public var g:Number=10;
                public var obliqueG:Number=14;
                //从给定节点到目标节点的估计移动开销
                public var h:Number=10;
                //允许寻找的方向,值为8时表示可以从8个方向寻找路线,即允许斜线行走
                //public var direct:int=8;
                //构造函数,需要一个存储有地图节点数据的数组做参数
                public function AStar(mapData:Array){
                        _mapData=mapData;
                }
                //启动寻路
                public function findPath(startPos:Point, targetPos:Point):Array{
                        //设置起点、终点
                        _startNode=new AStarGrid(startPos.x, startPos.y, _mapData[startPos.x][startPos.y]);
                        _targetNode=new AStarGrid(targetPos.x, targetPos.y, _mapData[targetPos.x][targetPos.y]);
                        //在地图上标记出起点和终点
                        _startNode.mc.alpha=0;
                        _targetNode.mc.alpha=0;
                        //标记,设置起点为当前操作节点
                        _nowNode=_startNode;
                        //设置起始节点的父节点为null
                        setFather(_startNode, null);
                        //重置寻路结果
                        way=new Array();
                        //计算起始节点的g, h, f值
                        countHG(_startNode);
                        //将起始节点加入开放列表及封闭列表
                        addToOpen(_startNode);
                        addToClose(_startNode);
                        //获取起始节点周围节点
                        way=getAround(_startNode);
                        //-----------------------
                        return way;
                }
                //获取当前操作节点周围的节点,通过递归调用执行寻路过程
                protected function getAround(centreNode:AStarGrid):Array{
                        //声明两个数组,分别存放当前操作节点周围的节点数据及对应的地图单元
                        var aroundArr:Array=new Array();
                        var aroundNodeArr:Array=new Array();
                        //将当前操作节点周围的非空节点、地图单元分别加入aroundNodeArr和aroundArr数组,
                        if(centreNode.dC+1<_mapData[0].length){
                                aroundArr.push(_mapData[centreNode.dR][centreNode.dC+1]);
                                aroundNodeArr.push(dataToGrid(centreNode.dR, centreNode.dC+1, aroundArr[0]));
                        }
                        if(centreNode.dC-1>=0){
                                aroundArr.push(_mapData[centreNode.dR][centreNode.dC-1]);
                                aroundNodeArr.push(dataToGrid(centreNode.dR, centreNode.dC-1, aroundArr[1]));
                        }
                        if(centreNode.dR+1<_mapData.length){
                                aroundArr.push(_mapData[centreNode.dR+1][centreNode.dC]);
                                aroundNodeArr.push(dataToGrid(centreNode.dR+1, centreNode.dC, aroundArr[2]));
                        }
                        if(centreNode.dR-1>=0){
                                aroundArr.push(_mapData[centreNode.dR-1][centreNode.dC]);
                                aroundNodeArr.push(dataToGrid(centreNode.dR-1, centreNode.dC, aroundArr[3]));
                        }
                        if(centreNode.dR-1>=0 && centreNode.dC-1>=0){
                                aroundArr.push(_mapData[centreNode.dR-1][centreNode.dC-1]);
                                aroundNodeArr.push(dataToGrid(centreNode.dR-1, centreNode.dC-1, aroundArr[4]));
                        }
                        if(centreNode.dR+1<_mapData.length && centreNode.dC+1<_mapData[0].length){
                                aroundArr.push(_mapData[centreNode.dR+1][centreNode.dC+1]);
                                aroundNodeArr.push(dataToGrid(centreNode.dR+1, centreNode.dC+1, aroundArr[5]));
                        }
                        if(centreNode.dR-1>=0 && centreNode.dC+1<_mapData[0].length){
                                aroundArr.push(_mapData[centreNode.dR-1][centreNode.dC+1]);
                                aroundNodeArr.push(dataToGrid(centreNode.dR-1, centreNode.dC+1, aroundArr[6]));
                        }
                        if(centreNode.dR+1<_mapData.length && centreNode.dC-1>=0){
                                aroundArr.push(_mapData[centreNode.dR+1][centreNode.dC-1]);
                                aroundNodeArr.push(dataToGrid(centreNode.dR+1, centreNode.dC-1, aroundArr[7]));
                        }
                        //声明一个AStarGrid对象,存储在 aroundNodeArr 数组中具有f值最大的节点 
                        var fMaxGrid:AStarGrid;
                        //遍历 aroundNodeArr 数组,把数组中所有节点的父节点设为当前操作节点,计算他们的g, h, f值,并全部执行加入开放列表操作
                        for(var n:int=0; n<aroundArr.length; n++){
                                setFather(aroundNodeArr[n], centreNode);
                                countHG(aroundNodeArr[n]);
                                addToOpen(aroundNodeArr[n]);
                        }
                        //找出 aroundNodeArr 数组中具有f值最大的节点,存入 fMaxGrid 变量
                        for(var i:int=1; i<aroundNodeArr.length; i++){
                                for(var m:int=aroundNodeArr.length-1; m>=i; m--){
                                        if(aroundNodeArr[m].f<aroundNodeArr[m-1].f){
                                                var sNode:AStarGrid=aroundNodeArr[m-1];
                                                aroundNodeArr[m-1]=aroundNodeArr[m];
                                                aroundNodeArr[m]=sNode;
                                        }
                                }
                        }
                        //设置当前操作节点为 f 值最大的节点,并将其加入封闭列表
                        _nowNode=aroundNodeArr[0];
                        addToClose(aroundNodeArr[0]);
                        //判断 _isFind 的值确认是否完成寻路
                        var finishArr:Array=new Array();
                        if(!_isFind){
                                //否,则继续搜索当前节点的周围节点;
                                getAround(_nowNode);
                        }else{
                                //是,则输出路径
                                finishArr=getPath();
                        }
                        return finishArr;
                }
                //由目标节点开始,回溯父节点,知道父节点为null,即完成路径的查找
                protected function getPath():Array{
                        var pathArr:Array=new Array();
                        //判断如果起点与终点一致,则立刻返回路径,不一致就正常寻路
                        if(_targetNode.dR==_startNode.dR && _targetNode.dC==_startNode.dC){
                                pathArr.push(_targetNode);
                        }else{
                                //完成寻路后,清空开放、封闭列表,为下次寻路做准备
                                var node:AStarGrid=_targetNode;
                                pathArr.push(node);
                                while(node.father!=null){
                                        var f:AStarGrid=node.father;
                                        pathArr.push(f);
                                        _mapData[node.dR][node.dC].alpha=.4;
                                        node=node.father;
                                }
                        }
                        pathArr[0].mc.alpha=0;
                        _isFind=false;
                        _closeList=new Array();
                        _openList=new Array();
                        _startNode=null;
                        _targetNode=null;
                        return pathArr;
                }
                //设置父节点
                protected function setFather(sonNode:AStarGrid, fatherNode:AStarGrid){
                        sonNode.father=fatherNode;
                }
                //计算节点的 g, h, f值
                protected function countHG(node:AStarGrid){
                        countH(node);
                        countG(node);
                        node.f=node.h+node.g;
                }
                //计算节点的 h 值
                protected function countH(node:AStarGrid):uint{
                        var xH:uint=Math.abs(_targetNode.dR-node.dR)*h;
                        var yH:uint=Math.abs(_targetNode.dC-node.dC)*h;
                        var hValue:uint=xH+yH;
                        node.h=hValue;
                        return hValue;
                }
                //计算节点的 g 值
                protected function countG(node:AStarGrid):uint{
                        var n:AStarGrid=node;
                        var gValue:uint=0;
                        if(n.father!=null){
                                while(n.father!=null){
                                        var f:AStarGrid=n.father;
                                        if(f.dR==n.dR || f.dC==n.dC){
                                                gValue+=g;
                                        }else{
                                                gValue+=obliqueG;
                                        }
                                        n=n.father;
                                }
                        }else{
                                gValue=0;
                        }
                        node.g=gValue;
                        return gValue;
                }
                //将节点加入开放列表
                protected function addToOpen(addedNode:AStarGrid){
                        /*for(var i:int=0; i<_openList.length;i++){
                                if(_openList[i].dR==_targetNode.dR && _openList[i].dC==_targetNode.dC){
                                        _isFind=true;
                                        setFather(_targetNode, addedNode);
                                        break;
                                }
                        }
                        */
                        //判断加入开放列表的节点是否重复,重复则重新计算节点经由当前操作节点的 g 值,如果小于原来的 g 值,则将节点的父节点置为当前操作节点,否则取消加入开放列表
                        if(!_isFind){
                                for(var n:int=0; n<_openList.length; n++){
                                        if(_openList[n].dR==addedNode.dR && _openList[n].dC==addedNode.dC){
                                                var oldG:uint=addedNode.g;
                                                var newG:uint;
                                                if(_nowNode.dR==addedNode.dR || _nowNode.dC==addedNode.dC){
                                                        newG=_nowNode.g+g;
                                                }else{
                                                        newG=_nowNode.g+obliqueG;
                                                }
                                                if(newG<oldG){
                                                        setFather(addedNode, _nowNode);
                                                        countHG(addedNode);
                                                }else{
                                                }
                                        }else{
                                                _openList.push(addedNode);
                                        }
                                }
                        }else{
                        }
                }
                //将节点加入封闭列表
                protected function addToClose(addedNode:AStarGrid){
                        _closeList.push(addedNode);
                        //判断目标节点是否在封闭列表中,在则将目标节点的父节点设为当前操作节点,寻路结束
                        for(var i:int=0; i<_closeList.length;i++){
                                if(_closeList[i].dR==_targetNode.dR && _closeList[i].dC==_targetNode.dC){
                                        _isFind=true;
                                        setFather(_targetNode, addedNode);
                                        break;
                                }
                        }
                }
                //把节点转化为相应的地图单元
                protected function gridToData(starGrid:AStarGrid):*{
                        return _mapData[starGrid.dR][starGrid.dC];
                }
                //把地图单元转化为相应的节点
                protected function dataToGrid(r:uint, c:uint, mc:DisplayObject):*{
                        return new AStarGrid(r, c, mc, null);
                }
        }
}


package{
        /*
        iiacm(着火骷髅),网页游戏开发网 www.eb163.com 
        网页游戏互动社区
        
        A*寻路运算节点类
                为寻路提供数据节点对象
        */
        import flash.display.DisplayObject;
        public class AStarGrid{
                //节点的父节点
                public var father:AStarGrid;
                //节点所在的行号
                public var dR:uint;
                //节点所在的列号
                public var dC:uint;
                //节点对应的地图单元
                public var mc:DisplayObject;
                //从此节点到目标节点的估计移动开销
                public var h:uint=0;
                //从起点起点沿着已生成的路径到此节点的移动开销
                public var g:uint=0;
                //节点的移动开销,用来衡量此节点的路径优先度
                public var f:uint=0;
                //构造函数,初始化节点的值
                public function AStarGrid(dataR:uint=0, dataC:uint=0, displayObj:DisplayObject=null, father=null){
                        father=father;
                        dR=dataR;
                        dC=dataC;
                        mc=displayObj;
                }
        }
}


二、A*寻路的步骤
  起初看些关于A*寻路的文章的时候,都很混乱,因为对它的原理并不了解,甚至于越看越乱…… 不过,当搞清楚了它的原理及他的寻路步骤后,就会很好理解了。正如jr在帖子中提到的那样,寻路的流程分别为:

1、寻路的过程就是检验地图单元(或称为节点)的过程,算法是通过对周围地形的评价得到路径的。寻路前要准备一些东西,为寻路存放数据作准备,他们分别是:
1)一个开放列表:存放以检验过和待检验的节点
2)一个封闭列表:存放操作过的节点,只有被检验过并且具是最优评估值(后面会介绍)的节点才是操作过的节点,路径将通过遍历他们的父节点产生
3)当前操作节点:经检验,计算出的评估值最优的节点
4)评估值:也就是jr高手帖子中提到的 f=h+g,评估值 f 的函数等于—— h(水平和垂直方向距离的和)加上g(直线方向或45度方向的移动消耗)。h、g的值可以自己约定,只要都是一个数量级的就可以了,而且 g 值在直线方向上的消耗值一定要大于 45 度方向上的移动消耗,demo中沿用 h=10,g=10(直线),g=14(45度方向)

2、从起点开始,把起点首先放入封闭列表,将它的父节点设置为null,获取起点周围的节点,将这些周围节点也加开放列表,把他们的父节点设为起点,对开放列表中的节点进行检验,同时计算他们的 f、h、g 值,选择其中 f 值最小的为下一个操作节点,这样我们就有了下一个要操作的对象,把这个节点加入到封闭列表中去

3、获取当前操作节点周围的节点,将这些节点放到开放列表,这里以为开放列表中已经有元素了,得给这些要加入开放列表的“周围节点”作个判断:如果该“周围节点”已存在列表中的话就取消添加入列表的操作,然后看这个节点在经过当前操作节点的情况下的 g 值是否小于它原来的 g 值,如果小于,就把这个更小的 g 值更新到这个“周围节点”中,且把这个“周围节点”的父节点设置为当前操作节点;如果大于,则不作改动,继续检验下一个“周围节点”,检验完后再从这些“周围节点”中挑出 f 值最小的作下一个操作节点……如此反复作递归调用

4、重复步骤 3 中的内容,直到发现封闭列表中出现有终点,说明我们已经到达了终点,终止递归调用,开始路径回溯,即由终点开始,追寻他们的父节点,这些父节点都是在寻路的过程中以评估值最优为条件设置的,当找到父节点为null的节点,就说明已回到了起点,完成了整个寻路的过程

三、小结
  好了,demo做完了,学习过程是痛苦的,但是得到成果的时候是喜悦的……在做的过程中,只要理解了寻路的步骤,把每步要用的功能封装好,各个环节就会清晰的暴露在视野中。 这个demo只展现了A*寻路中的一些原理,还没有加入障碍策略和绕路策略等机制,这在今后会不断的做出完善。顺带提提我的体会,寻路过程中允许穿越地图单元行走,所以会看到斜线的路径,这和检验的周围节点有关,如果不希望斜线行走,则在检验周围节点的时候不添加斜线方向的节点就成;若要做类似蜂窝型的不规则地图寻路,同样在检验周围节点上作修改就可以了。由于有了中间操作对象 AStarGrid 类,寻路类只关心传入的地图数据是什么样的,而不用关心实际地图的形状,这提高了寻路类的适应性。
分享到:
评论

相关推荐

    A*寻路算法 源码

    A*寻路算法(A* Pathfinding Algorithm)是游戏开发、地图导航、机器人路径规划等领域广泛应用的一种高效路径搜索算法。它结合了Dijkstra算法的全局最优性和BFS(广度优先搜索)的效率,通过引入启发式函数来指导搜索,...

    as3 A*寻路源码

    在这个"as3 A*寻路源码"中,我们可以深入理解A*算法的工作原理及其在ActionScript 3(AS3)中的实现。 A*算法的核心思想是结合了Dijkstra算法的全局最优性和最佳优先搜索的效率。它通过一个评估函数f(n)来指导搜索...

    六边形A*寻路算法源码(As3版本)

    六边形A*寻路算法源码(As3版本)在六边形战棋类游戏中可以使用

    cocos creator 实现A*寻路

    A*(A-Star)寻路算法是路径规划领域中一种广泛应用的算法,它结合了Dijkstra算法的最短路径特性与优先级队列的效率优势。在Cocos Creator中,利用JavaScript实现A*寻路算法可以为游戏或交互式应用提供高效、智能的...

    as3 A* 寻路源码

    在AS3(ActionScript 3)这种面向对象的编程语言中实现A*寻路算法,可以为游戏开发、地图导航等场景提供强大的路径计算支持。 首先,我们来看A*算法的基本原理。A*算法通过评估每个节点的F值来决定搜索方向,F值由G...

    Python版的A*寻路算法

    A*(A-star)寻路算法是一种广泛应用在游戏开发、地图导航、机器人路径规划等领域的高效路径搜索算法。它结合了Dijkstra算法的全局最优性和Greedy最佳优先搜索的效率,通过引入启发式函数来估计从当前节点到目标节点...

    AS3最好的A*寻路算法()

    3. **多目标寻路**:除了单个目标,PAStar可能还支持同时寻找多个目标的路径。 4. **可定制性**:算法可能允许用户自定义启发式函数,以适应不同场景的需求。 5. **优化的搜索算法**:PAStar可能采用了特定的优化...

    A*寻路的AS实现例子

    标题 "A*寻路的AS实现例子" 指的是使用ActionScript(一种基于ECMAScript的脚本语言,常用于Adobe Flash开发)实现A*(A-star)寻路算法的一个示例项目。A*算法是一种广泛应用在路径规划中的启发式搜索算法,尤其在...

    A*寻路算法

    ### A*寻路算法详解 #### 一、引言 A*寻路算法是一种广泛应用于游戏开发、机器人路径规划等领域的重要算法。它结合了最佳优先搜索策略与启发式搜索技术,能够有效地找到从起点到终点的最短路径。尽管A*算法在初次...

    A*寻路 -- 更加真实 的路径(二)

    3. `AStar`类:核心算法实现,包括寻路函数和启发式函数。 4. `Path`类:表示从起点到终点的路径,可以是节点列表或者坐标序列。 源码分析时,我们需要关注以下几个关键点: - 如何建立节点间的连接,即邻接表的...

    A*寻路算法源码 附带桌面演示工具

    A*算法是自动寻路的理想解决办法 像自动寻找NPC 自动寻找地图出口 我写的这个只是简单的实现了功能 在运算效率上没有做优化 在接下来的版本中我会增加二*堆算法 来提高A*算法的效率 请大家多多关注 包中还有一个我用...

    A*寻路算法(带UI界面)

    3. **AStar.cs**:这是A*寻路算法的具体实现。在这个文件中,你可以找到核心的搜索算法,包括节点的开放列表和关闭列表管理、F值(总成本)、G值(实际代价)和H值(启发式估计)的计算,以及最佳路径的回溯。 4. *...

    A*自动寻路算法实现(java)

    下载本程序仅可演示A*自动寻路算法实现(java),该程序是基于我写的网络版贪吃蛇基础上编写的(网络版贪吃蛇...wasd键控制太阳的方向,鼠标左击目的地,会根据A*自动寻路算法计算出一条最优路线,太阳按最优路线移动。

    C#A*寻路demo,图形演示

    《C# A*寻路算法在游戏中的应用与可视化演示》 在计算机科学尤其是游戏开发领域,路径规划是一项至关重要的技术。A*(A-Star)算法作为一种高效的寻路算法,被广泛应用于各种需要智能体寻找最短路径的问题中。本文...

    A*走路 自动寻路A*算法 易语言源码优化版

    A*(A-star)算法是一种广泛应用的路径搜索算法,它在自动寻路系统中起着核心作用。易语言是一种中国本土开发的、面向对象的编程语言,它以其简单易学的特点受到很多程序员的喜爱。这个“A*走路 自动寻路A*算法 ...

    CocosCreator A*自动寻路demo

    《CocosCreator A*自动寻路技术解析与实践》 在游戏开发中,角色的智能移动是至关重要的一环,这通常涉及到自动寻路系统。CocosCreator作为一个强大的2D游戏开发引擎,为开发者提供了丰富的工具和技术支持。本篇将...

    A*寻路算法C#

    A*寻路算法,提供顶点信息矩阵和移动消耗矩阵,返回最短路径顶点列表

    a*寻路 c++实现

    **a*寻路算法**是一种广泛应用的路径搜索算法,它结合了Dijkstra算法的最短路径特性与启发式搜索的效率。在C++中实现a*寻路算法,通常会用到数据结构如二叉堆(优先队列)来优化性能。在此项目中,八方向的移动模型...

Global site tag (gtag.js) - Google Analytics