A_star运算法则
相关专业术语。
节点(node):本质上就是方形网格里的某一个方格(yujjj注:为什么不把他们描述为方格?因为在一些时候划分的节点不一定是方形的,矩形、六角形、或其它任意形状,本书中只讨论方格)。由此可以看出,路径将会由起点节点,终点节点,还有从起点到终点经过的节点组成。
代价(cost):这是对节点优劣分级的值。代价小的节点肯定比代价大节点更好。代价由两部分组成:从起点到达当前点的代价和从这个点到终点的估计代价。代价一般由变量f,g和h,具体如下。
f:特定节点的全部代价。由g+h决定。
g:从起点到当前点的代价。它是确定的,因为你肯定知道从起点到这一点的实际路径。
h:从当前点到终点的估计代价。是用估价函数(heuristic function)计算的。它只能一个估算,因为你不知道具体的路线——你将会找出的那一条。
估价函数(heuristic):计算从当前点到终点估计代价的公式。通常有很多这样的公式,但他们的运算结果,速度等都有差异(yujjj注:估价公式计算的估计值越接近实际值,需要计算的节点越少;估价公式越简单,每个节点的计算速度越快)。
待考察表(open list):一组已经估价的节点。表里代价最小的节点将是下一次的计算的起点。
已考察表(closed list):从待考察表中取代价最小的节点作为起点,对它周围8个方向的节点进行估价,然后把它放入“已考察表”。
父节点(parent node):以一个点计算周围节点时,这个点就是其它节点的父节点。当我们到达终点节点,你可以一个一个找出父节点直到起点节点。因为父节点总是带考察表里的小代价节点,这样可以确保你找出最佳路线。
现在我们来看以下具体的运算方法:
1. 添加起点节点到待考察表
2. 主循环
a. 找到待考察表里的最小代价的节点,设为当前节点。
b. 如果当前点是终点节点,你已经找到路径了。跳到第四步。
c. 考察每一个邻节点(直角坐标网格里,有8个这样的节点 )对于每一个邻节点: (1).如果是不能通过的节点,或者已经在带考察表或已考察表中,跳过,继续下一节点,否则继
续。
(2).计算它的代价
(3).把当前节点定义为这个点的父节点添加到待考察表
(4).添加当前节点到已考察表
3. 更新待考察表,重复第二步。
4. 你已经到达终点,创建路径列表并添加终点节点
5. 添加终点节点的父节点到路径列表
6. 重复添加父节点直到起点节点。路径列表就由一组节点构成了最佳路径
*上边内容摘自:《Flash actionScript 3.0高级动画教程》第四章 寻路
结合 AS3 高级动画教程学习之:等角投影笔记 的所创建的地图信息,学习A*算法 寻找最佳路径。首先得添加 Node, Grid , AStar三个类
Node类
package com.hope.isometric
{
/**
* Represents a specific node evaluated as part of p pathfinding algorithm.
* @author huilin
*
*/
public class Node
{
public var x:Number;
public var y:Number;
public var f:Number;
public var g:Number;
public var h:Number;
public var walkable: Boolean;
public var parent:Node;
public var costMultiplier:Number = 1.0;
private var _object: IsoObject = null;
public function Node( x: int, y: int )
{
this.x = x;
this.y = y;
}
static public function create( x: int, y: int ): Node
{
return new Node( x, y );
}
public function set object( value: IsoObject ): void
{
_object = value;
}
public function get object(): IsoObject
{
return _object;
}
}
}
Node 类网格节点数据对象,相比《AS3 高级动画教程》我们添加 _object属性,用于记录此节点的图形。
Grid类:
package com.hope.isometric
{
import mx.utils.object_proxy;
/**
* Holds a two-dimensional array of Nodes method to manipulate them start node and end node for
* find a path.
*
* @author huilin
*
*/
public class Grid
{
private var _startNode: Node;
private var _endNode: Node;
[ArrayElementType("Node")]
private var _grid: Array;
private var _numCols: int;
private var _numRows: int;
public function Grid( numCols: int, numRows: int)
{
_numCols = numCols;
_numRows = numRows;
init();
}
static public function create( numCols: int, numRows: int ): Grid
{
return new Grid( numCols, numRows );
}
/**
* Init Grid with Nodes
*
*/
private function init(): void
{
_grid = new Array();
for( var i: int = 0; i < _numCols; i++ )
{
_grid[ i ] = new Array();
for( var j:int = 0; j < _numRows; j++ )
{
_grid[i][j] = Node.create( i, j );
}
}
}
/**
*
* @param x The x coordinate.
* @param y The y coordinate.
* @return The Node at the given coordinate.
*
*/
public function getNode( x: int, y:int ): Node
{
return _grid[x][y] as Node;
}
/**
* Sets the Node at the given coordinate as startNode.
* @param x The x coordinate.
* @param y The y coordinate.
*
*/
public function setStartNode( x: int, y: int ): void
{
_startNode = _grid[x][y];
}
/**
* Sets the Node at the given coordinate as endNode.
* @param x The x coordinate.
* @param y The y coordinate.
*
*/
public function setEndNode( x: int, y: int ): void
{
_endNode = _grid[x][y];
}
/**
* Sets the node at the given coords as walkable or not.
* @param x The x coord.
* @param y The y coord.
*/
public function setWalkable(x:int, y:int, value:Boolean):void
{
_grid[x][y].walkable = value;
}
// getters / setters
/**
* Returns the start node.
*/
public function get startNode():Node
{
return _startNode;
}
/**
* Returns the end node.
*/
public function get endNode():Node
{
return _endNode;
}
/**
* Returns the number of columns in the grid.
*/
public function get numCols():int
{
return _numCols;
}
/**
* Returns the number of rows in the grid.
*/
public function get numRows():int
{
return _numRows;
}
public function setNodeObject( x: int, y: int, value: IsoObject ): void
{
setWalkable( x, y, value.walkable );
_grid[x][y].object = value;
}
}
}
Grid类使用二维数据(_grid )保存所有网格节点的数据信息。
AStar类
package com.hope.isometric
{
/**
* A*算法
* @author huilin
*
*/
import com.hope.isometric.Node;
public class AStar
{
[ArrayElementType("Node")]
private var _openList: Array; //待考察表
[ArrayElementType("Node")]
private var _closedList: Array; //已考察表
private var _grid: Grid;
private var _startNode: Node;
private var _endNode: Node;
[ArrayElementType("Node")]
private var _path: Array;
private var _heuristic: Function = diagonal;
private var _straightCost: Number = 1.0;
private var _diagCost: Number = Math.SQRT2;
public function AStar()
{
}
static public function create(): AStar
{
return new AStar();
}
public function findPath( grid: Grid ): Boolean
{
_grid = grid;
_openList = new Array();
_closedList = new Array();
_startNode = _grid.startNode;
_endNode = _grid.endNode;
_startNode.g = 0;
_startNode.h = _heuristic( _startNode );
_startNode.f = _startNode.g + _startNode.h;
return search();
}
private function search(): Boolean
{
var currentNode: Node = _startNode;
while( currentNode != _endNode )
{
//_openList = []; //当前待考察列表
var startX: int = Math.max( 0, currentNode.x - 1 );
var endX: int = Math.min( _grid.numCols - 1, currentNode.x + 1 );
var startY: int = Math.max( 0, currentNode.y -1 );
var endY: int = Math.min( _grid.numRows - 1, currentNode.y + 1 );
//check Nodes from ( startX, startY ) to (endX, endY );
for( var i:int = startX; i <= endX; i++ )
{
trace( "...................................." + i );
for( var j:int = startY; j <= endY; j++ )
{
var node: Node = _grid.getNode( i, j );
trace( "node: " + node.x + " " + node.y )
if ( validNode( node, currentNode ) )
{
var cost: Number = _diagCost;
if ( currentNode.x == node.x || currentNode.y == node.y )
{
cost = _straightCost;
}
var g: Number = currentNode.g + cost * node.costMultiplier;
var h: Number = _heuristic( node );
var f: Number = g + h;
if( isOpen( node ) || isClosed( node ) )
{
if ( node.f > f )
{
node.f = f;
node.g = g;
node.h = h;
node.parent = currentNode
}
}else
{
node.f = f;
node.g = g;
node.h = h;
node.parent = currentNode;
_openList.push( node );
}
}
}
}
_closedList.push( currentNode ); //已考察列表
if ( _openList.length == 0 )
{
trace( "no path found!" );
return false;
}
_openList.sortOn( "f", Array.NUMERIC );
currentNode = _openList.shift() as Node;
trace( "find node: " + currentNode.x + " " + currentNode.y + " f:" + currentNode.f )
}
buildPath();
return true;
}
private function validNode( node: Node, currentNode: Node ): Boolean
{
if( currentNode == node || !node.walkable ) return false;
if ( !_grid.getNode( currentNode.x, node.y ).walkable ) return false;
if ( !_grid.getNode( node.x, currentNode.y ).walkable ) return false;
return true;
}
private function isOpen( node: Node ): Boolean
{
return _openList.indexOf( node ) > 0 ? true : false;
}
private function isClosed( node: Node ): Boolean
{
return _closedList.indexOf( node ) > 0 ? true : false;
}
private function buildPath(): void
{
trace( "buildPath" );
_path = new Array();
var node: Node = _endNode;
_path.push( node );
while( node != _startNode )
{
node = node.parent;
_path.unshift( node );
}
}
private function manhattan( node: Node ): Number
{
return Math.abs( _endNode.x - node.x )*_straightCost + Math.abs( _endNode.y - node.y )*_straightCost;
}
private function euclidian( node: Node ): Number
{
var dx: Number = _endNode.x - node.x;
var dy: Number = _endNode.y - node.y;
return Math.sqrt(dx*dx + dy*dy ) * _straightCost;
}
private function diagonal( node: Node ): Number
{
var dx: Number = Math.abs( _endNode.x - node.x );
var dy: Number = Math.abs( _endNode.y - node.y );
var diag: Number = Math.min( dx, dy );
var straight: Number = dx + dy;
return _diagCost * diag + _straightCost * ( straight - 2*diag );
}
//
///setter / getter
public function get grid(): Grid
{
return _grid;
}
public function set grid( value: Grid ): void
{
_grid = value;
}
public function get openList() : Array
{
return _openList;
}
public function set openList( value: Array ): void
{
_openList = value;
}
public function get closedList() : Array
{
return _closedList;
}
public function set closedList( value: Array ): void
{
_closedList = value;
}
public function get path(): Array
{
return _path;
}
}
}
主角登场,AStar类, 从开始节点开始,一次一次的循环,查询当前节点的相领节点。当当前节点为终点节点时,整个计算结束,退出循环。
当前节点的相邻节点
修改IsoWorld类:
package com.hope.isometric
{
import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.utils.Dictionary;
// http://www.java3z.com/cwbwebhome/article/article2/2825.html
public class IsoWorld extends Sprite
{
private var grid: Grid;
private var cellSize: Number;
private var floor: Sprite;
private var world: Sprite;
[ArrayElementType("IsoObject")]
private var objectList: Array;
private var numCols: int = 10;
private var numRows: int = 10;
public function IsoWorld()
{
}
static public function create(): IsoWorld
{
return new IsoWorld();
}
/**
*
* @param size The tile size to user when making the world
* @return A fully populated IsoWorld instance
*
*/
public function makeWorld( size: Number, mapGrid: Array, tileTypes: Dictionary ): void
{
//cellSize = size;
numCols = mapGrid.length;
numRows = mapGrid[0].length;
createGrid( size );
for( var i:int = 0; i < mapGrid.length; i++ )
{
for( var j:int = 0; j < mapGrid[i].length; j++ )
{
var cellType: String = mapGrid[i][j] as String;
var cell: Object = tileTypes[ cellType ];
var tile: IsoObject = IsoObject.create( cell.type );
tile.draw(size, parseInt(cell.color), parseInt(cell.height));
tile.walkable = cell.walkable == "true" ? true:false;
tile.position = new Point3D( j * 20, 0, i * 20 );
grid.setNodeObject( j, i, tile );
addChild( tile );
//trace( "tile type:" + cell.type + " x:" + i + " y:" + j + " walkable:" + tile.walkable + " " + grid.getNode(j,i).walkable );
}
}
}
private function createGrid( size: Number ): void
{
cellSize = size;
grid = Grid.create( numCols, numRows );
}
public function setPosition( x: Number, y: Number ): void
{
this.x = x;
this.y = y;
}
public function findPath( startNode: Node, endNode: Node ): void
{
var astar: AStar = AStar.create();
grid.setStartNode( startNode.x, startNode.y );
grid.setEndNode( endNode.x, endNode.y );
if ( astar.findPath( grid ))
{
trace( "find Path..." );
for( var i:int = 0; i < astar.path.length; i++ )
{
var node: Node = astar.path[i] as Node;
trace( "node " + i + " [ " + node.x + "," + node.y + " ]" );
var tile: IsoObject = node.object;
var color: uint = 0x0000ff;
if ( i == 0 ) //startNode
color = 0xff0000;
else if( i == astar.path.length - 1 ) //endNode
color = 0xffff00;
tile.draw( tile.size, color, tile.height );
}
}
else
{
trace( "not found path..." );
}
}
}
}
在IsoWorld添加 createGrid,findPath函数.
并在makeWorld 调用 createGrid访求,并通过grid.setNodeObject( )访使得网格节点与图形关联.
修改IsoWorldTest类
package com.hope.isometric
{
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.net.URLRequest;
public class IsoWorldTest extends Sprite
{
private var world: IsoWorld;
private var mapLoader: MapLoader;
private var astar: AStar;
public function IsoWorldTest()
{
if( stage )
init();
else
addEventListener( Event.ADDED_TO_STAGE, init );
}
private function init( event: Event = null ): void
{
removeEventListener( Event.ADDED_TO_STAGE, init );
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
loadMap();
}
private function loadMap(): void
{
mapLoader = new MapLoader();
mapLoader.addEventListener( Event.COMPLETE, onMapComplete );
mapLoader.loadMap( "map.txt" );
}
private function onMapComplete( event: Event ): void
{
trace( "load complete" );
world = IsoWorld.create();
world.makeWorld( 20, mapLoader.mapGrid, mapLoader.tileTypes );
addChild( world );
world.setPosition( 500, 200 );
world.findPath( new Node( 18, 15 ), new Node( 6, 6) );
}
}
}
修改测试类IsoWorldTest,在onMapComplete中添加
world.findPath( new Node( 18, 15 ), new Node( 6, 6) );
一行代码,定义的开始节点,终点节点。调用findPath开始寻路.
最后运行结果: 红色为起始节点,黄色为终点节点,蓝色为路径过程节点。