`

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

 
阅读更多

转:http://bbs.9ria.com/thread-95620-1-1.html

 

对于A*传统寻路的结果不平滑的问题,我们讨论了一种判断两点间是否存在障碍物的算法,并在用户点击鼠标选择了目的地后先判断起终点间是否存在障碍物,若不存在,则路径数组中将只具有一个终点节点;否则进行A*寻路运算。大致过程可用下面代码表示:

 

//判断起终点间是否存在障碍物,若存在则调用A*算法进行寻路,通过A*寻路得到的路径是一个个所要经过的节点数组;否不存在障碍则直接把路径设置为只含有一个终点元素的数组

var hasBarrier:Boolean = _grid.hasBarrier(startPosX, startPosY, endPosX, endPosY);

if( hasBarrier )
{
      _grid.setStartNode(startPosX, startPosY);
      _grid.setEndNode(endPosX, endPosY);
      findPath();
}

else
{
        _path = [_grid.getNode(endPosX, endPosY)];
       _index = 0;
       addEventListener(Event.ENTER_FRAME, onEnterFrame);//开始行走
}

 

如果你听从了我的建议这么做了,我只能说声抱歉,因为在实际应用中发现了一些问题,什么问题呢?就是当起终点间不存在障碍物时,主角行走的目的地将跳过 A*寻路算法的“考验”,直接设置为我点击的位置(endPosX, endPosY),因此,此位置若有时候会是路径网格外的某一点,主角也以此为目的地进行移动,这样导致的结果就是主角“飞”起来了,或是主角“穿越” 了……为了避免这个问题的发生,我们就必须时时刻刻地使用A*算法去计算路径,即使起终点间没有障碍物也必须调用寻路方法进行寻路。但是如果用A*寻路算 法计算出来的路径不平滑怎么办呢?别急,下面我们来讨论A*寻路算法的路径平滑处理办法:弗洛伊德路径平滑算法。这个算法出之lizhi写的文章:http://wonderfl.net/c/aWCe ,并已被很多人采纳~我们先看一下结果演示:MyPathFinding2

弗洛伊德路径平滑算法应在通过A*寻路算法得出路径后进行,它的步骤分为两步:一、合并路径数组中共线的节点;二、尽可能地去掉多余拐点。这个过程如下图所示:

原始A*寻路路径



 去掉共线点



 去掉多余拐点


可以看到,使用弗洛伊德路径平滑处理 后的路径正如我们期望的那样,而且大大削减了路径数组中的节点数目。

那么接下来来讲讲实现思路吧。首先,不难发现,若存在三点A(1,1), B(2,2), C(3,3),若B与A的横、纵坐标差值分别等于C与B的横、纵坐标差值,则A,B,C三点共线,使用代码来表示就是:

 

 

if( (bx -ax == cx - bx) && (by-ay == cy - by) )
{

//三点共线

}
 

由上式可知去掉路径中共线节点的方法。接下来讨论如何去掉多余的拐点。
仔细观察第三幅图你会发现,若路径中存在节点A,B,C,D,E,F,G,如果A与G之间的连线所经过的节点中没有一个节点是不可移动节点,则我们称A与 G之间是不存在障碍物的。如两节点间不存在障碍物,则可以去掉此两点间其他所有节点。如上例中A-G这些节点,若A与G之间不存在障碍物,则我们可以去掉 A与G之间的B,C,D,E,F节点,最终路径数组中只剩下A与G两个节点。那么如何判断两个点之间存不存在障碍物呢?在上一篇的教程中我已详细解释过方 法,列位道友若不记得了,可以再回头再去仔细研读一二。
        那么最后我们使用代码来实现弗洛伊德路径平滑处理的算法:

 

 

/** 弗洛伊德路径平滑处理 */
public function floyd():void {
        if (path == null)
                return;
        _floydPath = path.concat();
        var len:int = _floydPath.length;
        if (len > 2)
        {
                var vector:ANode = new ANode(0, 0);
                var tempVector:ANode = new ANode(0, 0);
                //遍历路径数组中全部路径节点,合并在同一直线上的路径节点
                //假设有1,2,3,三点,若2与1的横、纵坐标差值分别与3与2的横、纵坐标差值相等则
                //判断此三点共线,此时可以删除中间点2
                floydVector(vector, _floydPath[len - 1], _floydPath[len - 2]);
                for (var i:int = _floydPath.length - 3; i >= 0; i--)
                {
                        floydVector(tempVector, _floydPath[i + 1], _floydPath[i]);
                        if (vector.x == tempVector.x && vector.y == tempVector.y)
                        {
                                _floydPath.splice(i + 1, 1);
                        }
                        else
                        {
                                vector.x = tempVector.x;
                                vector.y = tempVector.y;
                        }
                }
        }
        //合并共线节点后进行第二步,消除拐点操作。算法流程如下:
        //如果一个路径由1-10十个节点组成,那么由节点10从1开始检查
        //节点间是否存在障碍物,若它们之间不存在障碍物,则直接合并
        //此两路径节点间所有节点。
        len = _floydPath.length;
        for (i = len - 1; i >= 0; i--)
        {
                for (var j:int = 0; j <= i - 2; j++)
                {
                        if ( _grid.hasBarrier(_floydPath[i].x, _floydPath[i].y, _floydPath[j].x, _floydPath[j].y) == false )
                        {
                                for (var k:int = i - 1; k > j; k--)
                                {
                                        _floydPath.splice(k, 1);
                                }
                                i = j;
                                len = _floydPath.length;
                                break;
                        }
                }
        }
}
 

接下来再讲一讲第二个棘手的问题,就是A*寻路会当你点击一个不可移动点之后返回false的寻路结果,即告知你无路可走。这不是我们想要的结果,我们想 要的是点击一个不可移动点后玩家应该走到离此不可移动点最近的一个可移动点位上面。那么如果你阅读过我的上一篇帖子,你会看到我解决此问题的一个方式是使 用“将不可移动点设置超大代价法”。不过使用此方法的一个弊病在于当你在选择一个不可移动点作为终点后,A*寻路算法会遍历大量的点,造成性能的低下。那 么在经过另一番研究后发现还有一种办法能解决这个问题,就是“寻找替代点法”。
        先解释一下什么是“寻找替代点法”,当点击一个不可移动点U之后我们将寻找此不可移动点外围一个离起点距离最短的可移动点R作为U的替代点,替代U来完成寻路且玩家将最终移动到此R点位置。如下图所示

 

 



  那么,为了尽可能地降低寻找替代点的时间,我们提出了一种“埋葬深度”的概念。先看下图:

 


 这种情况下,U点由一圈甚至两圈或更多圈不可移动点包围着,若我们采用传统的方式以辐射型遍历U点外圈节点(从U点外围第一圈开始遍历,遍历完毕没有找 到替代点,继续再遍历更外边的圈直到找到为止),将会在多次点击同一点时产生冗余遍历时间。那么此时我们为了降低重复遍历的次数,就引入了“埋葬深度的概 念”。若一个节点为不可移动点,则其埋葬深度为1;在此基础上,若其周围一圈全部为不可移动点,则其埋葬深度加一,为2;若更外围一圈依然全部为不可移动 点,埋葬深度再加一,依次类推,下图列出了埋葬深度为1-3的情况:

 



 在为某一个节点第一次寻找替代点时以辐射型遍历此点周围节点以计算出此节点的埋葬深度并记录于每个节点对象Node类中新增的一个埋葬深度属性buriedDepth中,下一次为此点寻找替代点时就可以根据埋葬深度从存在可移动点的那一圈遍历

 



 

 在遍历原始终点U周围存在可移动点的那一圈之后把所有可移动点都存到一个数组中,之后比较此数组中全部候选点与起点的距离,选出距离最短的一个点作为替代点R即可。

 



 好吧,寻找替代点的过程大致就是这样,最后来看代码呗。先看节点类ANode类中一些要用到的新增属性和方法:

 

public class ANode
{
        .....
        /** 埋葬深度 */
        public var buriedDepth:int = -1;

        /** 距离 */
        public var distance:Number;

        .....
        /** 得到此节点到另一节点的网格距离 */
        public function getDistanceTo( targetNode:ANode ):Number
        {
                var disX:Number = targetNode.x - x;
                var disY:Number = targetNode.y - y;
                distance = Math.sqrt( disX * disX + disY * disY );
                return distance;
        }
}
 

再看节点网格NodeGrid类中新增方法。

 

/**当终点不可移动时寻找一个离原终点最近的可移动点来替代之 */
public function findReplacer( fromNode:ANode, toNode:ANode ):ANode
{
        var result:ANode;
        //若终点可移动则根本无需寻找替代点
        if( toNode.walkable )
        {
                result = toNode;
        }
        //否则遍历终点周围节点以寻找离起始点最近一个可移动点作为替代点
        else
        {
                //根据节点的埋葬深度选择遍历的圈
                //若该节点是第一次遍历,则计算其埋葬深度
                if( toNode.buriedDepth == -1 )
                {
                        toNode.buriedDepth = getNodeBuriedDepth( toNode, Math.max(_numCols, _numRows) );
                }
                var xFrom:int = toNode.x - toNode.buriedDepth < 0 ? 0 : toNode.x - toNode.buriedDepth;
                var xTo:int = toNode.x + toNode.buriedDepth > numCols - 1 ? numCols - 1 : toNode.x + toNode.buriedDepth;
                var yFrom:int = toNode.y - toNode.buriedDepth < 0 ? 0 : toNode.y - toNode.buriedDepth;
                var yTo:int = toNode.y + toNode.buriedDepth > numRows - 1 ? numRows - 1 : toNode.y + toNode.buriedDepth;                

                var n:ANode;//当前遍历节点

                for( var i:int=xFrom; i<=xTo; i++ )
                {
                        for( var j:int=yFrom; j<=yTo; j++ )
                        {
                                if( (i>xFrom && i<xTo) && (j>yFrom && j<yTo) )
                                {
                                        continue;
                                }
                                n = getNode(i, j);
                                if( n.walkable )
                                {
                                        //计算此候选节点到起点的距离,记录离起点最近的候选点为替代点
                                        n.getDistanceTo( fromNode );

                                        if( !result )
                                        {
                                                result = n;
                                        }
                                        else if( n.distance < result.distance )
                                        {
                                                result = n;
                                        }
                                }
                        }
                }

        }
        return result;
}
/** 计算一个节点的埋葬深度
 * @param node                欲计算深度的节点
 * @param loopCount        计算深度时遍历此节点外围圈数。默认值为10*/
private function getNodeBuriedDepth( node:ANode, loopCount:int=10 ):int
{
        //如果检测节点本身是不可移动的则默认它的深度为1
        var result:int = node.walkable ? 0 : 1;
        var l:int = 1;

        while( l <= loopCount )
        {
                var startX:int = node.x - l < 0 ? 0 : node.x - l;
                var endX:int = node.x + l > numCols - 1 ? numCols - 1 : node.x + l;
                var startY:int = node.y - l < 0 ? 0 : node.y - l;
                var endY:int = node.y + l > numRows - 1 ? numRows - 1 : node.y + l;                

                var n:ANode;
                //遍历一个节点周围一圈看是否周围一圈全部是不可移动点,若是,则深度加一,
                //否则返回当前累积的深度值
                for(var i:int = startX; i <= endX; i++)
                {
                        for(var j:int = startY; j <= endY; j++)
                        {
                                n = getNode(i, j);
                                if( n != node && n.walkable )
                                {
                                        return result;
                                }
                        }
                }

                //遍历完一圈,没发现一个可移动点,则埋葬深度加一。接着遍历下一圈
                result++;
                l++;
        }
        return result;
}

 

 

那么最后,看看实际应用这个方法进行寻路的部分吧。

 

private function onGridClick(event:MouseEvent):void
{
        var startTime:int = getTimer();

        var startPosX:int = Math.floor(_player.x / _cellSize);
        var startPosY:int = Math.floor(_player.y / _cellSize);
        var startNode:ANode = _grid.getNode(startPosX, startPosY);

        var endPosX:int = Math.floor(event.localX / _cellSize);
        var endPosY:int = Math.floor(event.localY / _cellSize);
        var endNode:ANode = _grid.getNode(endPosX, endPosY);

        if( endNode.walkable == false )
        {
                replacer = _grid.findReplacer(startNode, endNode);
                if( replacer )
                {
                        endPosX = replacer.x;
                        endPosY = replacer.y;
                }
        }

        _grid.setStartNode(startPosX, startPosY);
        _grid.setEndNode(endPosX, endPosY);

        findPath();

}
 

 

OK, 这就是本次教程全部内容了,使用“弗洛伊德路径平滑处理”结合“寻找替代点法”做寻路,不论效率还是结果都还算如人意啦。不过,细心的朋友可以发现,“寻找替代点法”的弊端在于无法在存在“孤岛”和“回”字形的路径网格中找到正确的替代点,如下图:

 



 所以为了避免此情况,要么就用上一篇帖子中提到的“极大代价法”来进行寻路(结果准确,效率过低),要么就在布置路径网格时刻意避免编出这种形式的路径网格。
        最后,奉上全部测试用代码,希望对列位有所帮助: (附件)

 

 

 

 

  • 大小: 163.1 KB
  • 大小: 156.8 KB
  • 大小: 156.9 KB
  • 大小: 15.2 KB
  • 大小: 4.2 KB
  • 大小: 13.4 KB
  • 大小: 6.1 KB
  • 大小: 21 KB
  • 大小: 4.1 KB
  • src.rar (11.8 KB)
  • 下载次数: 46
分享到:
评论

相关推荐

    A*寻路算法《三》

    A*寻路算法是计算机图形学、游戏开发和人工智能领域中的一个重要算法,它用于寻找从起点到终点的最短路径。本节将深入探讨A*算法的原理、工作流程及其在Unity引擎中的应用。 A*算法的核心思想是结合了Dijkstra算法...

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

    《C# A*寻路算法在游戏中的应用与可视化演示》 在计算机科学尤其是游戏开发领域,路径规划是一...无论是简单的迷宫探索还是复杂的策略游戏,A*算法都能为游戏中的角色提供智能化的导航,使得游戏世界更加生动和真实。

    A Pathfinding Project Pro.rar a*寻路 4.1.16

    《A*寻路技术在Unity中的应用》 在游戏开发中,寻路系统是不可或缺的一部分,它使得游戏角色能够智能地在复杂环境中找到最优路径。...开发者可以通过灵活运用这些工具和算法,创造出更加真实和智能的游戏世界。

    游戏算法A*寻路初探

    总结来说,A*寻路算法通过其科学的原理设计和灵活的实现方法,解决了游戏开发中的路径寻找问题,大大提升了游戏人物的行为智能性,使玩家获得更加真实、富有挑战性的游戏体验。掌握A*算法的原理和应用对于游戏开发者...

    cocos2d a*寻路

    总之,cocos2d a*寻路是Cocos2d-x游戏开发中的一个重要技术,通过合理运用A*算法,我们可以为游戏角色提供智能的路径规划,使得游戏世界更加生动和真实。理解并掌握这个知识点对于游戏开发者来说是非常有价值的。

    A*寻路算法 教程(一) (转)

    A*寻路算法(A* Pathfinding Algorithm)是游戏开发、地图导航、图形处理等领域中广泛使用的一种高效路径搜索算法。它的核心思想是在宽度优先搜索(BFS)和Dijkstra算法的基础上,通过引入启发式函数来优化搜索效率,尽...

    A*寻路算法《五》

    A*寻路算法是计算机图形学、游戏开发和人工智能领域中...通过理解并熟练运用A*,开发者可以创建更加智能和真实的虚拟世界。同时,掌握A*算法也有助于理解其他相关领域,如图论、最优化问题和机器学习中的路径规划问题。

    A星寻路插件A*Pathfind Project

    A星(A*)路径搜索算法是游戏开发中广泛使用的寻路技术,特别是在Unity这样的3D游戏引擎中。这个“A星寻路插件A*Pathfind Project”提供了一个方便、高效的解决方案,帮助开发者实现在复杂的游戏环境中实现智能AI角色...

    j2me版A*寻路算法

    A*(发音为 "A-star")寻路算法是一种在图形和游戏开发中广泛使用的路径查找算法,尤其在有限的网格或图结构中寻找最短路径。它是一种启发式搜索算法,结合了Dijkstra算法的全局最优性和Greedy Best-First Search的...

    unity寻路插件:A*Pathfinding

    在游戏设计中,合理利用寻路插件,可以极大地提升玩家的游戏体验,使游戏世界更加真实和生动。 综上所述,A*Pathfinding Project Pro 4.1.16是Unity开发者的强大工具,它通过强大的A*寻路算法和丰富的功能,简化了...

    A星寻路,a星寻路算法,matlab源码.zip

    A星寻路(A* Search Algorithm)是一种在图形或网格中寻找从起点到目标点最短路径的搜索算法。它是Dijkstra算法的一种扩展,引入了启发式信息来提高搜索效率,使得算法能够在有限的计算时间内找到最优解。A星算法在...

    as3.0 A星寻路算法,含源码

    A星(A*)寻路算法是计算机图形学和游戏开发中常用的一种路径搜索算法,它结合了最佳优先搜索(Dijkstra算法)和启发式搜索。在AS3.0中,这个算法通常用于创建智能角色在复杂网格环境中的移动路径,比如游戏中的NPC或...

    游戏开发学习要看的书籍

    - **算法应用**:如搜索算法(A*寻路)、图形渲染算法(像素着色器、顶点着色器)。 4. **图形学**: - **3D建模**:学习3DS Max、Maya等软件,掌握模型创建、贴图烘焙等技巧。 - **渲染技术**:理解DirectX和...

    Android 3D游戏开发技术详解与典型案例

    - **路径寻找**:实现NPC的智能移动,如A*寻路算法。 - **行为树**:构建复杂的AI决策树,实现更智能的游戏角色。 **15. 开发小秘籍** - **性能优化**:分享提高游戏运行效率的技巧。 - **调试技巧**:教授如何...

    A* Pathfinding Project Pro 4.1.16(最新版)

    这些特性使得插件能适应复杂的游戏场景,提供更加真实和流畅的寻路体验。 4. **性能优化**:考虑到游戏运行时的性能,该插件进行了大量优化。例如,使用按需计算和记忆化来减少计算量,同时支持多线程计算,以充分...

    课程设计题目及要求.pdf

    #### 二、任务要求详解 1. **迷宫地图生成算法** - **自动生成**: 利用随机算法生成具有一定复杂度的迷宫地图。例如,可以通过递归分割或Prim算法等方法实现。 - **手动生成**: 依据预设的数据集生成固定的迷宫...

    21天学会用JAVA开发网络游戏

    - **寻路算法**:如A*算法,用于AI角色寻找路径。 5. **游戏架构**: - **MVC(模型-视图-控制器)**:分离游戏的数据、显示和控制逻辑,提高代码的可维护性。 - **状态机**:用于管理游戏的不同阶段,如菜单、...

    Python实现的迷宫游戏源代码,迷宫基于Prim算法生成,存在“有环”与“无环”两种模式,支持战争迷雾,含A*寻路算法实现

    再者,A*寻路算法是游戏开发中广泛使用的路径规划算法,它结合了最佳优先搜索(BFS)和Dijkstra算法的优点,通过引入启发式函数来预估从当前位置到目标的距离,从而高效地找到最短路径。在这款迷宫游戏中,A*算法...

Global site tag (gtag.js) - Google Analytics