AS3 AStar算法(1)
最近再次看了一下AStar算法,并把理论转化成了代码。后来在一个2.5D的格子上测试了一把,哈哈,很不错。
先说理论: A-Star算法是一种静态路网中求解最短路最有效的方法。简单的说,就是从起点开始,计算出经过周围节点的代价。找到一条代价最小的通向终点的路径。整个过程就是不断把周围代价最小的节点作为新的起点,最后达终点,同时找到最佳路径。 上面说的节点,在网格中就是一个方格,不过也可以是其他形状,比如六边形。另外一个就是代价,代价包含两个方面的意思:从起点到当前点的代价和当前点到终点的估计代价。这两个代价值加起来就是当前点的总代价了。 通常用公式表示为:f = g + h. g就是从起点到当前点的代价。 h是当前点到终点的估计代价,是通过估价函数计算出来的。这个函数可以说是整个算法的关键了,因为算法的不同直接影响最终结果,以及算法的效率。这里有一个简单而有效算法,对于简单的网格来说就是计算当前网格到终点的距离:Math.sqrt(dx*dx+dy*dy);
搜索过程是这样的: 有两个数组,openList(带考察表)存放已经估价的节点,其中代价最小的节点是下一次计算的起点。closedList(已考察表)存放从待考察表中取出的代价最小的节点,在对其周围的各个节点进行估价后就将其放入closedList。 对于每个节点都会有一个父节点,以一个点计算周围节点时,这个点就是其它节点的父节点。到达终点时,就通过每一个点的父节点一直找到起点。路径也就找到了。
计算代价: 对于一个不再边上的节点,他周围会有8个节点,可以看成他到周围8个点的代价都是1。精确点,到上下左右4个点的代价是1,到左上左下右上右下的1.414就是“根号2”,这个值就是前面说的g。大概就是下面这个样子 2.8 2.4 2 2.4 2.8 2.4 1.4 1 1.4 2.4 2 1 0 1 2 2.4 1.4 1 1.4 2.4 2.8 2.4 2 2.4 2.8 对于h,需要用一个估价函数来计算,前面有说到一个简单算法就是计算直线距离。假设终点是(50,50),对于点(20,30)来说我们用前面提到的估价函数来计算的话就是这样: dx = 50 - 20 = 30; dy = 50 - 30 = 20; h = Math.sqrt(dx * dx + dy * dy) = 36.1
理论好像差不多了。接下来就是代码来。
AS3 AStar算法(2)
前面说了理论,该开始写代码来。首先,构造一个节点类AStarNode: package com.cyy.astar { /** * ... * @author Will Chen * @version 1.0 * @email c_youyou@163.com * @msn chenyouyou@live.cn * @description ... */ public class AStarNode { public var x:int; public var y:int; public var f:Number; public var g:Number; public var h:Number; public var parent:AStarNode; public var isEmpty:Boolean = true; public function AStarNode(x:int, y:int) { this.x = x; this.y = y; } } }
接着是网格类AStarGrid: package com.cyy.astar { /** * ... * @author Will Chen * @version 1.0 * @email c_youyou@163.com * @msn chenyouyou@live.cn * @description ... */ public class AStarGrid { public var startNode:AStarNode; public var endNode:AStarNode; public var nodes:Array; public var columnNum:int; public var rowNum:int; public function AStarGrid(columnNum:int, rowNum:int) { this.columnNum = columnNum; this.rowNum = rowNum; nodes = new Array(); for(var i:int = 0; i < columnNum; i++) { nodes[i] = new Array(); for(var j:int = 0; j < rowNum; j++) { nodes[i][j] = new AStarNode(i, j); } } } public function getNode(x:int, y:int):AStarNode { return nodes[x][y]; } public function setEndNode(x:int, y:int):AStarNode { if (x < this.columnNum && y < this.rowNum) { endNode = nodes[x][y]; } else { endNode = null; } return endNode; } public function setStartNode(x:int, y:int):AStarNode { if (x < this.columnNum && y < this.rowNum) { startNode = nodes[x][y]; } else { startNode = null; } return startNode; } public function setNodeEmpty(x:int, y:int, value:Boolean):void { nodes[x][y].isEmpty = value; } } }
然后就是AStar: package com.cyy.astar { /** * ... * @author Will Chen * @version 1.0 * @email c_youyou@163.com * @msn chenyouyou@live.cn * @description ... */ public class AStar { private var openList:Array; private var closedList:Array; private var _path:Array; private var grid:AStarGrid; private var endNode:AStarNode; private var startNode:AStarNode; private var costOne:Number = 1; private var costSqrt:Number = Math.SQRT2; public function AStar() { } public function findPath(grid:AStarGrid):Boolean { this.grid = grid; openList = new Array(); closedList = new Array(); grid.startNode.g = 0; grid.startNode.h = getH(grid.startNode); grid.startNode.f = grid.startNode.g + grid.startNode.h; return begin(); } private function begin():Boolean { var node:AStarNode = grid.startNode; while (node != grid.endNode) { var beginX:int = node.x == 0 ? 0 : node.x - 1; var endX:int = node.x == grid.columnNum - 1 ? grid.columnNum - 1 : node.x + 1; var beginY:int = node.y == 0 ? 0 : node.y - 1; var endY:int = node.y == grid.rowNum - 1 ? grid.rowNum - 1 : node.y + 1; for (var i:int = beginX; i <= endX; i++) { for (var j:int = beginY; j <= endY; j++) { var currentNode:AStarNode = grid.getNode(i, j); if (currentNode == node || !currentNode.isEmpty || (!grid.getNode(node.x, currentNode.y).isEmpty && !grid.getNode(currentNode.x, node.y).isEmpty)) { continue; } var cost:Number = costOne; if (!((node.x == currentNode.x) || (node.y == currentNode.y))) { cost = costSqrt; } var g:Number = node.g + cost; var h:Number = getH(currentNode); var f:Number = g + h; if (openList.indexOf(currentNode) == -1 && closedList.indexOf(currentNode) == -1) { currentNode.f = f; currentNode.g = g; currentNode.h = h; currentNode.parent = node; openList.push(currentNode); } } } closedList.push(node); if (openList.length == 0) { return false } openList.sortOn("f", Array.NUMERIC); node = openList.shift(); } _path = new Array(); node = grid.endNode; _path.push(node); while (node != grid.startNode) { node = node.parent; _path.unshift(node); } return true; } private function getH(node:AStarNode):Number { var dx:Number = node.x - grid.endNode.x; var dy:Number = node.y - grid.endNode.y; return Math.sqrt(dx * dx + dy * dy); } public function get path():Array { return _path; } } }
OK,完成,核心代码就这么多了,不过还有很多地方可以优化。 下次再写一个测试用的类
AS3 AStar算法(3)
开始测试。。。不多说,看代码: package { import com.cyy.astar.AStar; import com.cyy.astar.AStarGrid; import com.cyy.astar.AStarNode; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; /** * ... * @author Will Chen * @version 1.0 * @email c_youyou@163.com * @msn chenyouyou@live.cn * @description ... */ public class AStarTest extends Sprite { private var player:Sprite; private var grid:AStarGrid; private var index:int; private var path:Array; private var speed:Number = 0.5; public function AStarTest() { player = new Sprite(); player.graphics.beginFill(0x0000ff); player.graphics.drawCircle(0, 0, 5); player.graphics.endFill(); player.x = 310; player.y = 310; addChild(player); grid = new AStarGrid(50, 50); for (var i:int = 0; i < 500; i++) { grid.setNodeEmpty(Math.floor(Math.random() * 50), Math.floor(Math.random() * 50), false); } drawGrid(); stage.addEventListener(MouseEvent.CLICK, onStageClickHandler); } private function drawGrid():void { graphics.clear(); for (var i:int = 0; i < grid.columnNum; i++) { for (var j:int = 0; j < grid.rowNum; j++) { graphics.lineStyle(0); graphics.beginFill(getColor(grid.getNode(i, j))); graphics.drawRect(i * 20, j * 20, 20, 20); graphics.endFill(); } } } private function getColor(node:AStarNode):uint { if (!node.isEmpty) { return 0x000000; } if (node == grid.startNode || node == grid.endNode) { return 0x00ff00; } return 0xffffff; } private function onStageClickHandler(event:MouseEvent):void { var posX:int = Math.floor(mouseX / 20); var posY:int = Math.floor(mouseY / 20); grid.setEndNode(posX, posY); posX = Math.floor(player.x / 20); posY = Math.floor(player.y / 20); grid.setStartNode(posX, posY); drawGrid(); getPath(); } private function getPath():void { var astar:AStar = new AStar(); if (astar.findPath(grid)) { path = astar.path; index = 1; addEventListener(Event.ENTER_FRAME, onEnterFrameHandler); } else { trace("not found the path!!!"); } } private function onEnterFrameHandler(e:Event):void { var pathX:Number = path[index].x * 20 + 20 / 2; var pathY:Number = path[index].y * 20 + 20 / 2; var dx:Number = pathX - player.x; var dy:Number = pathY - player.y; var temp:Number = Math.sqrt(dx * dx + dy * dy); if (temp < 1) { index++; if (index == path.length) { removeEventListener(Event.ENTER_FRAME, onEnterFrameHandler); } } else { player.x += dx * speed; player.y += dy * speed; } } } } 直接编译就能跑了,貌似还不错,呵呵