您的位置:首页 > 其它

AS3 高级动画教程学习之:寻路(A*算法)

2012-06-29 14:53 609 查看
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开始寻路.

最后运行结果: 红色为起始节点,黄色为终点节点,蓝色为路径过程节点。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: