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

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开始寻路.

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值