- 浏览: 593492 次
- 性别:
- 来自: 广州
文章分类
最新评论
-
wzh051527:
我是大四实习生一个,自我感觉技术能力在第三年。。唯一不明白,为 ...
十年技术,不要再迷茫 -
room_bb:
.hrl文件怎么加入到编译规则里面?比如:pp.hrl文件-d ...
Erlang中用的makefile的一点解释 -
吉米家:
感觉帆软报表的flash打印就很不错哇,特别好用
JSP 实现报表打印 -
雪碧爱芬达:
苦逼程序员的辛酸反省与总结 -
mlyjxx:
...
十年技术,不要再迷茫
游戏时代群雄并起,寻路乃中原逐鹿第一步,重要性不言而喻。今习得寻路战术之首A*算法,为大家操演一番,不足之处还望不吝赐教。可以选择阅读下面的内容,或者先看看 寻路示例 、AS3类代码 及其 API文档。
牛刀小试 - A*寻路算法简介
eidiot挂帅出征,携令牌一枚,率人马若干,编制如下:
-
寻路元帅
寻路总指挥,执“行动令牌”一枚和“开启士兵名录”、“关闭将军名录”各一册。凭“行动令牌”调兵遣将。 -
预备士兵
由元帅或预备将军派往未探索区域,完成探索任务后授“开启”军衔,晋为“开启士兵”。发令派其出者为其“父将”。 -
开启士兵
前线待命。接到“行动令牌”后晋为“预备将军”执行探索任务。 -
预备将军
凭“行动令牌”派出预备士兵至周围未探索区域,并考察周围“开启士兵”状态,以“父将”之名节制所派士兵。归还“行动令牌”后授“关闭”军衔,晋为“关闭将军”。 -
关闭将军
后方待命。到达终点后依次报告“父将”直至元帅,寻路任务完成。
为协调行动,特颁军令如下:
- “预备士兵”只能由起点或“父将”所在格横、竖或斜向移动一格,直向(横、竖)移动一格走10步,斜向一格14步(斜向是直向1.414倍,取整数),抵达后不得再移动。
- 所有人员需记下派出自己的“父将”、从起点到所在位置所走步数(G)、预计到达终点共需步数(F)。
其中 F = G + H ,F 是从起点经过该点到终点的总路程,G 为起点到该点的“已走路程”,H 为该点到终点的“预计路程”。G 的计算是“父将”的 G 值加上“父将”位置到该位置所走步数,H 的计算是该点到终点“只走直路”所需路程。
看看战图更容易理解,从红色方格出发越过黄色障碍到达蓝色方格:
图例:
由图可形象看出何谓“开启士兵”、“关闭将军”:外围的绿色方格为“开启士兵”,“前线待命”,随时可向外继续探索。内围的紫色方格是“关闭将军”,从终点开始沿箭头寻其“父将”直至起点即得最终路径。
战前会议结束,拔营出征。
- 首先派出编号为0的“预备士兵”侦查起点,然后升其为“开启士兵”,列入“开启士兵名录”。
- 检查“开启士兵名录”,找出F值最低的“开启士兵”(只有一名人员,当然是0号),发出“行动令牌”派其执行探索任务。
- 0号“开启士兵”接到“行动令牌”,晋为“预备将军”,探索周围格子。
- 向周围8个格子分别派出编号为1到8的“预备士兵”,成为这八名“预备士兵”的“父将”。
- 八名“预备士兵”到达方格后计算G值和F值,报告0号“父将”,晋为“开启士兵”。
- 0号“预备将军”收到八名“开启士兵”的报告,归还“行动令牌”,晋为“关闭将军”。
- 元帅收回“行动令牌”,将0号加入“关闭将军名录”,1到8号加入“开启士兵名录”。
此过程结果如下(方格右上角数字是人员编号,左下角是G,右下角是H,左上角是F):
第一轮探索任务完成,元帅开始检查“开启士兵名录”。此时名录中有8名人员,其中1号F值最低为40(起点右移一格,G值为10,到终点平移3格,H值为30,F = G + H = 40),向其发出“行动令牌”。
- 1号“开启士兵”接到“行动令牌”,晋为“预备将军”,探索周围格子。
- 周围8个格子中有3格障碍,跳过。一格是“关闭将军”,跳过。其余四格是“开启士兵”,检查如果从该位置过去G值是否更低。以2号为例,如果从1号过去G值为 10 + 14 = 24 (1号的G值加上1号到2号的步数),而2号原来的G值是10,不做处理(如果此时发现新的G值更低,则更新2号的G值,并改2号的“父将”为1号)。其他类推。
- 1号检测完周围的方格,不需做任何处理,归还“行动令牌”,晋为“关闭将军”。
- 元帅收回“行动令牌”,将1号加入“关闭将军名录”。
此过程结果如下:
第二轮结束,元帅再次检查“开启士兵名录”。此时还有7名“开启士兵”,5号和8号的F值都为最低的54,选择不同寻路的结果也将不同。元帅选择了最后加入的8号“开启士兵”发出“行动令牌”,过程同上,不赘述,结果如下:
重复这个过程直到某位“关闭将军”站到了终点上(或者“开启士兵”探测到了终点,这样更快捷,但某些情况找到的路径不够短),亦即找到了路径;或是“开启士兵名录”已空,无法到达终点。
下面整理一下全过程并翻译成“标准语言”,首先是各名词:
- “开启士兵名录” - 开启列表 - open list
- “关闭将军名录” - 关闭列表 - closed list
- “父将” - 父节点 - parent square
- F - 路径评分
- G - 起点到该点移动损耗
- H - 该点到终点(启发式)预计移动损耗
寻路过程:
- 1, 将起点放入开启列表
- 2, 寻找开放列表中F值最低的节点作为当前节点
- 3, 将当前节节点切换到关闭列表
- 4, 如果当前节点是终点则路径被找到,寻路结束
- 5, 对于其周围8个节点:
- 如果不可通过或已在关闭列表,跳过,否则:
- 如果已在开放列表中,检查新路径是否更好。如果新G值更低则更新其G值并改当前节点为其父节点,否则跳过
- 如果是可通过区域则放入开启列表,计算这一点的F、G、H值,并记当前节点为其父节点
- 6, 如果开启列表空了,则无法到达目标,路径不存在。否则回到2
再翻译成“编程语言”?请看第三部分,锋芒毕露 - AS3代码和示例。
如虎添翼 - 使用二叉堆优化
如何让A*寻路更快?元帅三顾茅庐,请来南阳二叉堆先生帮忙优化寻找“开启士兵名录”中最低F值的过程,将寻路速度提高了2到3倍,而且越大的地图效果越明显。下面隆重介绍二叉堆先生:
下图是一个二叉堆的例子,形式上看,它从顶点开始,每个节点有两个子节点,每个子节点又各自有自己的两个子节点;数值上看,每个节点的两个子节点都比它大或和它相等。
在二叉堆里我们要求:
- 最小的元素在顶端
- 每个元素都比它的父节点大,或者和父节点相等。
只要满足这两个条件,其他的元素怎么排都行。如上面的例子,最小的元素10在最顶端,第二小的元素20在10的下面,但是第三小的元素24在20的下面,也就是第三层,更大的30反而在第二层。
这样一“堆”东西我们在程序中怎么用呢?幸运的是,二叉堆可以用一个简单的一维数组来存储,如下图所示。
假设一个元素的位置是n(第一个元素的位置为1,而不是通常数组的第一个索引0),那么它两个子节点分别是 n × 2 和 n × 2 + 1 ,父节点是n除以2取整。比如第3个元素(例中是20)的两个子节点位置是6和7,父节点位置是1。
对于二叉堆我们通常有三种操作:添加、删除和修改元素:
- 添加元素
首先把要添加的元素加到数组的末尾,然后和它的父节点(位置为当前位置除以2取整,比如第4个元素的父节点位置是2,第7个元素的父节点位置是3)比较,如果新元素比父节点元素小则交换这两个元素,然后再和新位置的父节点比较,直到它的父节点不再比它大,或者已经到达顶端,及第1的位置。 - 删除元素
删除元素的过程类似,只不过添加元素是“向上冒”,而删除元素是“向下沉”:删除位置1的元素,把最后一个元素移到最前面,然后和它的两个子节点比较,如果较小的子节点比它小就将它们交换,直到两个子节点都比它大。 - 修改元素
和添加元素完全一样的“向上冒”过程,只是要注意被修改的元素在二叉堆中的位置。
可以看出,使用二叉堆只需很少的几步就可以完成排序,很大程度上提高了寻路速度。
关于二叉堆先生需要了解的就是这么多了,下面来看看他怎么帮助元帅工作:
- 每次派出的“预备士兵”都会获得一个唯一的编号(ID),一直到寻路结束,它所有的数据包括位置、F值、G值、“父将”编号都将按这个ID存储。
- 每次有新的“开启士兵”加入,二叉堆先生将它的编号加入“开启士兵名录”并重新排序,使F值最低的ID始终排在最前面
- 当有“开启士兵”晋为“关闭将军”,删除“开启士兵名录”的第一个元素并重新排序
- 当某个“开启士兵”的F值被修改,更新其数据并重新排序
注意,“开启士兵名录”里存的只是人员的编号,数据全都另外存储。不太明白?没关系,元帅将在 第三部分 来次真刀实枪的大演兵。
锋芒毕露 - AS3代码和示例 地形数据不属于A*寻路的范围,这里定义一个 IMapTileModel 接口,由其它(模型)类来实现地图通路的判断。其它比如寻路超时的判断这里也不介绍,具体参考 AStar类及其测试代码。这里只介绍三部分主要内容: 数据存储 寻路过程 列表排序 数据存储 首先看看三个关键变量: private var m_openList : Array; //开放列表,存放节点ID private var m_openCount : int; //当前开放列表中节点数量 private var m_openId : int; //节点加入开放列表时分配的唯一ID(从0开始) 开放列表 m_openList 是个二叉堆(一维数组),F值最小的节点始终排在最前。为加快排序,开放列表中只存放节点ID ,其它数据放在各自的一维数组中: private var m_xList : Array; //节点x坐标 private var m_yList : Array; //节点y坐标 private var m_pathScoreList : Array; //节点路径评分F值 private var m_movementCostList : Array; //(从起点移动到)节点的移动耗费G值 private var m_fatherList : Array; //节点的父节点(ID) 这些数据列表都以节点ID为索引顺序存储。看看代码如何工作: //每次取出开放列表最前面的ID currId = this.m_openList[0]; //读取当前节点坐标 currNoteX = this.m_xList[currId]; currNoteY = this.m_yList[currId]; 还有一个很关键的变量: private var m_noteMap : Array; //节点(数组)地图,根据节点坐标记录节点开启关闭状态和ID 使用 m_noteMap 可以方便的存取任何位置节点的开启关闭状态,并可取其ID进而存取其它数据。m_noteMap 是个三维数组,第一维y坐标(第几行),第二维x坐标(第几列),第三维节点状态和ID。判断点(p_x, p_y)是否在开启列表中: return this.m_noteMap[p_y][p_x][NOTE_OPEN]; 寻路过程 AStar类 寻路的方法是 find() : /** * 开始寻路 * @param p_startX 起点X坐标 * @param p_startY 起点Y坐标 * @param p_endX 终点X坐标 * @param p_endY 终点Y坐标 * @return 找到的路径(二维数组 : [p_startX, p_startY], ... , [p_endX, p_endY]) */ public function find(p_startX : int, p_startY : int, p_endX : int, p_endY : int) : Array{/* 寻路 */} 注意这里返回数据的形式:从起点到终点的节点数组,其中每个节点为一维数组[x, y]的形式。为了加快速度,类里没有使用Object或是Point,节点坐标全部以数组形式存储。如节点note的x坐标为note[0],y坐标为note[1]。 下面开始寻路,第一步将起点添加到开启列表: this.openNote(p_startX, p_startY, 0, 0, 0); openNote() 方法将节点加入开放列表的同时分配一个唯一的ID、按此ID存储数据、对开启列表排序。接下来是寻路过程: while (this.m_openCount > 0) { //每次取出开放列表最前面的ID currId = this.m_openList[0]; //将编码为此ID的元素列入关闭列表 this.closeNote(currId); //如果终点被放入关闭列表寻路结束,返回路径 if (currNoteX == p_endX && currNoteY == p_endY) return this.getPath(p_startX, p_startY, currId); //...每轮寻路过程 } //开放列表已空,找不到路径 return null; 每轮的寻路: //获取周围节点,排除不可通过和已在关闭列表中的 aroundNotes = this.getArounds(currNoteX, currNoteY); //对于周围每个节点 for each (var note : Array in aroundNotes) { //计算F和G值 cost = this.m_movementCostList[currId] + ((note[0] == currNoteX || note[1] == currNoteY) ? COST_STRAIGHT : COST_DIAGONAL); score = cost + (Math.abs(p_endX - note[0]) + Math.abs(p_endY - note[1])) * COST_STRAIGHT; if (this.isOpen(note[0], note[1])) //如果节点已在开启列表中 { //测试节点的ID checkingId = this.m_noteMap[note[1]][note[0]][NOTE_ID]; //如果新的G值比节点原来的G值小,修改F,G值,换父节点 if(cost < this.m_movementCostList[checkingId]) { this.m_movementCostList[checkingId] = cost; this.m_pathScoreList[checkingId] = score; this.m_fatherList[checkingId] = currId; //对开启列表重新排序 this.aheadNote(this.getIndex(checkingId)); } } else //如果节点不在开放列表中 { //将节点放入开放列表 this.openNote(note[0], note[1], score, cost, currId); } } 从终点开始依次沿父节点回到到起点,返回找到的路径: /** * 获取路径 * @param p_startX 起始点X坐标 * @param p_startY 起始点Y坐标 * @param p_id 终点的ID * @return 路径坐标数组 */ private function getPath(p_startX : int, p_startY : int, p_id: int) : Array { var arr : Array = []; var noteX : int = this.m_xList[p_id]; var noteY : int = this.m_yList[p_id]; while (noteX != p_startX || noteY != p_startY) { arr.unshift([noteX, noteY]); p_id = this.m_fatherList[p_id]; noteX = this.m_xList[p_id]; noteY = this.m_yList[p_id]; } arr.unshift([p_startX, p_startY]); this.destroyLists(); return arr; } 列表排序 这部分看代码和注释就可以了,不多说: /** 将(新加入开放别表或修改了路径评分的)节点向前移动 */ private function aheadNote(p_index : int) : void { var father : int; var change : int; //如果节点不在列表最前 while(p_index > 1) { //父节点的位置 father = Math.floor(p_index / 2); //如果该节点的F值小于父节点的F值则和父节点交换 if (this.getScore(p_index) < this.getScore(father)) { change = this.m_openList[p_index - 1]; this.m_openList[p_index - 1] = this.m_openList[father - 1]; this.m_openList[father - 1] = change; p_index = father; } else { break; } } } /** 将(取出开启列表中路径评分最低的节点后从队尾移到最前的)节点向后移动 */ private function backNote() : void { //尾部的节点被移到最前面 var checkIndex : int = 1; var tmp : int; var change : int; while(true) { tmp = checkIndex; //如果有子节点 if (2 * tmp <= this.m_openCount) { //如果子节点的F值更小 if(this.getScore(checkIndex) > this.getScore(2 * tmp)) { //记节点的新位置为子节点位置 checkIndex = 2 * tmp; } //如果有两个子节点 if (2 * tmp + 1 <= this.m_openCount) { //如果第二个子节点F值更小 if(this.getScore(checkIndex) > this.getScore(2 * tmp + 1)) { //更新节点新位置为第二个子节点位置 checkIndex = 2 * tmp + 1; } } } //如果节点位置没有更新结束排序 if (tmp == checkIndex) { break; } //反之和新位置交换,继续和新位置的子节点比较F值 else { change = this.m_openList[tmp - 1]; this.m_openList[tmp - 1] = this.m_openList[checkIndex - 1]; this.m_openList[checkIndex - 1] = change; } } } 其中 getScore() 方法: /** * 获取某节点的路径评分F值 * @param p_index 节点在开启列表中的索引(从1开始) */ private function getScore(p_index : int) : int { //开启列表索引从1开始,ID从0开始,数组索引从0开始 return this.m_pathScoreList[this.m_openList[p_index - 1]]; }
发表评论
-
as3 Loader 加载资源后内存泄露无法释放的问题。
2014-06-21 10:30 681as3 Loader 加载资源后内存泄露无法释放的问题。 ... -
as3判断flash player版本的函数
2014-06-10 20:35 845//判断当前版本是否高于9.0.115.0为例子. pr ... -
CSS 中文字体的英文名称 (simhei, simsun) 宋体 微软雅黑
2014-04-03 15:25 1024华文细黑:STHeiti Light [STXihei]华文 ... -
as3.0的垃圾回收机制
2013-09-07 14:02 1510还是同样的博客,还是同样的作者(Daniel Sidhio ... -
AIR程序多开
2013-09-07 13:55 1008AIR应用通常不能像QQ那样能进行多开操作。为了让一个用AI ... -
starling性能优化总结
2013-07-22 14:06 1475在项目开发的过程中总结了一下starling的性能优化方案: ... -
AS3 Socket从零开始
2013-07-22 12:54 1105大家如果想学AS3 Socket直接在百度里搜一下,会找到很 ... -
绕开AS3安全沙箱 跨域加载SWF
2013-07-11 12:53 916AS3的安全沙箱的确是 ... -
解决AS3在ie中初始化时stageWidth和stageHeight为0
2013-06-14 09:23 1021先看下面的一段脚本,这是比较经典的初始化脚本: pac ... -
动态获取swc中的类
2013-05-25 10:32 957想通过代码生成,来获取swc中的类,并且可以作为普通类正常使 ... -
AS3 中字符串的format功能实现
2013-05-25 10:19 842使用C#的朋友都知道,string.Format();还是挺 ... -
总结调用Flash的几种方法
2013-05-02 16:18 1670一、Adobe 提供的方法 <object wi ... -
Flash3D错误集锦
2013-05-02 14:03 939VerifyError: Error #1014: 无法找到 ... -
使用scale拉伸之后的坐标问题
2013-04-12 09:38 1293最近发现论坛多了很多 ... -
30个实用的网页设计工具
2013-03-20 09:58 829作为一位网页设计师或开发者,你一直需要搜寻获取强大的网页设计 ... -
如何成为强大的程序员?
2013-03-11 11:27 729Aaron Stannard是新创公 ... -
漫谈重构
2013-03-11 11:09 864因为工作内容的原因, ... -
AS3使用谷歌API生成二维码
2012-12-10 16:24 1359二维码在新闻杂志,网站,网络广告,电视广告等地方随处可见 ... -
OOP的聚合原则
2012-12-10 16:21 933什么是聚合? 聚合可以很好地表达对象是什么和做 ... -
压缩速率追踪
2012-11-02 14:16 1457Flash Player 11.3添加了一个压缩和解压B ...
相关推荐
在AS3中实现A*寻路算法时,首先需要理解以下几个关键概念: 1. **图**:寻路问题通常表示为图,其中节点代表地图上的位置,边表示节点之间的可达性。 2. **启发式函数**:这是A*算法的关键部分,通常用曼哈顿距离...
《A*寻路在RPG游戏中的应用及AS3实现》 在电子游戏中,尤其是在角色扮演游戏(RPG)中,角色的移动路径规划是至关重要的一个环节。A*(A-Star)算法是一种广泛应用的最短路径搜索算法,因其高效性和准确性而备受...
在AS3.0中实现A*寻路算法,可以为基于Flex的应用提供动态的路径规划功能,例如在游戏地图上为角色寻找到达目标的最短路径。 首先,A*算法的基本步骤包括: 1. **初始化**:设置起始节点和目标节点,计算所有节点的...
2. **优先级队列**:A*算法依赖于优先级队列(如二叉堆)来存储待处理的节点,根据F值进行排序,保证每次取出的是F值最小的节点。 3. **启发式函数H(n)**:选择合适的启发式函数对算法性能至关重要。常见的启发式...
A*算法通过优先队列(如二叉堆)管理待处理节点,根据`f(n)`值排序,每次选择`f(n)`最小的节点进行扩展。 AS3.0是一种基于ActionScript的编程语言,主要应用于Adobe Flash平台,常用于网页游戏、动画和交互式应用的...
以下是关于AS3 A星寻路算法的详细解释: 1. **基本概念**: - **A\*算法**:A* 是一种启发式搜索算法,结合了Dijkstra算法的全局最优性和Greedy最佳优先搜索算法的速度。它通过使用一个评估函数来估计从起点到目标...
在AS3(ActionScript 3)这种面向对象的编程语言中,A星算法的实现可以帮助游戏中的角色或其他对象智能地移动。以下是关于AS3 A星寻路算法的详细解释: 1. **基本概念**: - **节点(Nodes)**:在A星算法中,地图...
这篇关于“AS3简单的寻路程序”的介绍将深入探讨如何在ActionScript 3 (AS3)中实现一个基本的寻路算法,该算法适用于二维网格地图,并能够处理不可通行和可通行的格子。 首先,我们需要理解AS3的基础。ActionScript...
本压缩包提供的资源着重于AS3中如何实现这些功能,特别是A*(A-Star)寻路算法的应用。 ActionScript3是Adobe Flash平台的主要编程语言,它支持面向对象编程,提供了强大的性能和灵活性,是制作互动内容和游戏的...
4. **优先队列**:在AS3中,可以使用二叉堆(Binary Heap)作为优先队列,实现插入、删除最小元素等操作。 5. **搜索过程**:从起点开始,将起点加入开放集合。每次从开放集合中取出f值最小的节点,计算其所有邻居...
总之,"FLASH AS3 寻路算法"是游戏开发中的一个重要技术,通过A*算法可以实现高效的路径搜索。在AS3中实现这一算法需要对面向对象编程、数据结构和算法有深入的理解。通过熟练掌握这些知识点,开发者可以创建出...
标题“AS3 A星算法以及实例”表明我们将探讨的是使用ActionScript 3(AS3)语言实现的A星算法。AS3是Adobe Flash平台的主要编程语言,常用于创建交互式网页内容、游戏和动画。 描述中提到这是一个包含源文件和源...
4. **优先队列**:A*算法使用优先队列(通常是二叉堆)来存储待处理的节点,根据f值(g值,即从起点到当前节点的实际代价,加上h值,即启发式函数的估算代价)进行排序。 5. **扩展节点**:在每一步,算法都会从...
在Flash中,我们可以使用ActionScript 3(AS3)编程语言来实现A*算法。首先,我们需要定义一个网格结构,每个节点代表地图上的一个位置。然后,为每个节点存储相关信息,如`g`值、`h`值和父节点信息。 3. **关键...
3. **优先级队列**:A*算法使用优先级队列(如二叉堆)来存储待检查的节点,依据f(n) = g(n) + h(n)排序,其中g(n)是实际代价,h(n)是启发式代价。 4. **扩展节点**:算法会逐步扩展具有最低f(n)值的节点,直到找到...
在"A_寻路(AS3)"的示例中,可能包含了AS3代码实现的详细步骤,包括节点表示、启发式函数的定义、开放列表和关闭列表的管理以及路径的查找和返回。通过阅读和理解这段代码,你可以深入学习如何在实际项目中应用A*...
AS3A星算法,全称为ActionScript 3版本的A*寻路算法,是一种在图形化网格中寻找从起点到终点最短路径的有效算法。它结合了Dijkstra算法的全局最优性和Best First Search的效率,通过引入启发式函数来估计从当前节点...
3. **优先队列**:A*算法使用优先队列(如二叉堆)来存储待处理的节点,根据F值进行排序,确保每次选择F值最小的节点进行扩展。 4. **开放集与关闭集**:算法维护两个集合,开放集包含待探索的节点,而关闭集包含已...
优先队列(Priority Queue)用于处理具有优先级的任务,如A*寻路算法。AS中可以借助PriorityQueue类实现,根据优先级进行元素排序。 在游戏开发中,正确选择和使用数据结构能够显著提高代码的效率和可维护性。例如...
为了实现寻路功能,通常会使用A*算法或者Dijkstra算法,这些算法通常处理的是四向或八向移动。在四向寻路中,角色只能向上、下、左、右四个方向移动;而在八向寻路中,增加了对角线方向。从提供的代码来看,这里的...