最近在学作游戏,需要了解一种很好很强大的,也很实用的地图有障碍物时寻找最隹路径的方法。
我发现这个方法不是只能用在游戏中,觉得机器人在室内或者室外自行移动,也完全适用这种算法。
下边开始学习:
在学习之前,要先了解一下A*算法中的专用术语:
1.节点:在基于区块的世界中这实际是一个区块。但,A*中没有使用区块(tile)、单元(cell)、点(potint),而是使用了术语节点(node)来指定所检查的路径,所以一个路径会包含起始节点,终止节点或目标节点,以及构成二者之间最隹路径的一个节点列表。
2.代价:这是根据各个节点对于路径的适宜度而为节点指定的等级值。较低代价的节点比较高代价的节点更可取。代价(cost)由两部分组成,从起始节点到达某个节点的代价,以及从该节点到达目标节点的估计代价。代价元素通常用变量f、g和h表示,下面将逐一介绍。
3.f:一个特定节点的总代价,定义为g+h。
4.g:从起始节点到达某个特定节点的代价。可以准确计算,因为你往往知道从起始节点到达该节点所选取的具体路径。
5.h:从一个特定节点到达终止节点的估计代价。这个估计通过一个启发函数完成。这只是一个估计,因为你不知道要选取的具体路径。这是你要确定的。
6.启发函数:估计从一个特定节点到达终止节点所需代价的函数。存在多种常用的启发函数,它们在速度、效率等方面会有不同的结果。
7.待查列表:已经访问并已指定代价的节点的列表。这个列表中的最低代价节点将用于下一次搜索迭代。
8.已查列表:所有相邻节点都已经访问的节点的列表。
9.父节点:迭代处理到一个节点时,其相邻的各个节点都会得到检查,并将该节点指定为父节点。所以到达目标节点时,可以沿着父节点链回溯到起始节点。由于父节点总是待查列表中的最低代价节点,所以肯定能得到最佳路径。
下边是一个面向对象的A*算法类:
第一个:Grid类,这个类中包含一个启始节点,一个终止节点,一个地图节点(node)数组,以及地图的行和列大小几个属性参数,包含的方法主要是对这些参数的初始化操作,和get,set方法。
package
{
/**
* Holds a two-dimensional array of Nodes methods to manipulate them, start node and end node for finding a path.
*/
public class Grid
{
private var _startNode:Node;
private var _endNode:Node;
private var _nodes:Array;
private var _numCols:int;
private var _numRows:int;
/**
* Constructor.
*/
public function Grid(numCols:int, numRows:int)
{
_numCols = numCols;
_numRows = numRows;
_nodes = new Array();
for(var i:int = 0; i < _numCols; i++)
{
_nodes[i] = new Array();
for(var j:int = 0; j < _numRows; j++)
{
_nodes[i][j] = new Node(i, j);
}
}
}
// public methods
/**
* Returns the node at the given coords.
* @param x The x coord.
* @param y The y coord.
*/
public function getNode(x:int, y:int):Node
{
return _nodes[x][y] as Node;
}
/**
* Sets the node at the given coords as the end node.
* @param x The x coord.
* @param y The y coord.
*/
public function setEndNode(x:int, y:int):void
{
_endNode = _nodes[x][y] as Node;
}
/**
* Sets the node at the given coords as the start node.
* @param x The x coord.
* @param y The y coord.
*/
public function setStartNode(x:int, y:int):void
{
_startNode = _nodes[x][y] as Node;
}
/**
* 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
{
_nodes[x][y].walkable = value;
}
// getters / setters
/**
* 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;
}
/**
* Returns the start node.
*/
public function get startNode():Node
{
return _startNode;
}
}
}
第二个类:Node类,这个类主要包含与单个节点有关的信息,如节点坐标,节点的三个代价f、g、h,是否为可通过节点,以及节点间单位代价和当前节点的父节点信息。所包含的方法也就是这些参数的初始化操作。
package
{
/**
* Represents a specific node evaluated as part of a pathfinding algorithm.
*/
public class Node
{
public var x:int;
public var y:int;
public var f:Number;
public var g:Number;
public var h:Number;
public var walkable:Boolean = true;
public var parent:Node;
public var costMultiplier:Number = 1.0;
public function Node(x:int, y:int)
{
this.x = x;
this.y = y;
}
}
}
第三个类,也是AStar算法中最主要的类,即,AStar类,这个类中包含的参数有,一个已查列表(数组),一个待查列表(数组),一个地图Grid实例,开始节点,终止节点,最佳路径(数组),启发函数(实际上是指向启发函数的指针),还有上下左右相邻节点代价和四个斜角的代价。包含的方法有从Gide实际地图中找到最佳路径,如果有则返回真,并在最佳路径参数中保存最佳路径。以及三个常见启发函数。这三个启发函数可以在参数中按程序需要选择设置(只要取掉相应启发函数注释就可以了)。
package
{
public class AStar
{
private var _open:Array;
private var _closed:Array;
private var _grid:Grid;
private var _endNode:Node;
private var _startNode:Node;
private var _path:Array;
// private var _heuristic:Function = manhattan;
// private var _heuristic:Function = euclidian;
private var _heuristic:Function = diagonal;
private var _straightCost:Number = 1.0;
private var _diagCost:Number = Math.SQRT2;
public function AStar()
{
}
public function findPath(grid:Grid):Boolean
{
_grid = grid;
_open = new Array();
_closed = new Array();
_startNode = _grid.startNode;
_endNode = _grid.endNode;
_startNode.g = 0;
_startNode.h = _heuristic(_startNode);
_startNode.f = _startNode.g + _startNode.h;
return search();
}
public function search():Boolean
{
var node:Node = _startNode;
while(node != _endNode)
{
var startX:int = Math.max(0, node.x - 1);
var endX:int = Math.min(_grid.numCols - 1, node.x + 1);
var startY:int = Math.max(0, node.y - 1);
var endY:int = Math.min(_grid.numRows - 1, node.y + 1);
for(var i:int = startX; i <= endX; i++)
{
for(var j:int = startY; j <= endY; j++)
{
var test:Node = _grid.getNode(i, j);
if(test == node ||
!test.walkable ||
!_grid.getNode(node.x, test.y).walkable ||
!_grid.getNode(test.x, node.y).walkable)
{
continue;
}
var cost:Number = _straightCost;
if(!((node.x == test.x) || (node.y == test.y)))
{
cost = _diagCost;
}
var g:Number = node.g + cost * test.costMultiplier;
var h:Number = _heuristic(test);
var f:Number = g + h;
if(isOpen(test) || isClosed(test))
{
if(test.f > f)
{
test.f = f;
test.g = g;
test.h = h;
test.parent = node;
}
}
else
{
test.f = f;
test.g = g;
test.h = h;
test.parent = node;
_open.push(test);
}
}
}
for(var o:int = 0; o < _open.length; o++)
{
}
_closed.push(node);
if(_open.length == 0)
{
trace("no path found");
return false
}
_open.sortOn("f", Array.NUMERIC);
node = _open.shift() as Node;
}
buildPath();
return true;
}
private function buildPath():void
{
_path = new Array();
var node:Node = _endNode;
_path.push(node);
while(node != _startNode)
{
node = node.parent;
_path.unshift(node);
}
}
public function get path():Array
{
return _path;
}
private function isOpen(node:Node):Boolean
{
for(var i:int = 0; i < _open.length; i++)
{
if(_open[i] == node)
{
return true;
}
}
return false;
}
private function isClosed(node:Node):Boolean
{
for(var i:int = 0; i < _closed.length; i++)
{
if(_closed[i] == node)
{
return true;
}
}
return false;
}
private function manhattan(node:Node):Number
{
return Math.abs(node.x - _endNode.x) * _straightCost + Math.abs(node.y + _endNode.y) * _straightCost;
}
private function euclidian(node:Node):Number
{
var dx:Number = node.x - _endNode.x;
var dy:Number = node.y - _endNode.y;
return Math.sqrt(dx * dx + dy * dy) * _straightCost;
}
private function diagonal(node:Node):Number
{
var dx:Number = Math.abs(node.x - _endNode.x);
var dy:Number = Math.abs(node.y - _endNode.y);
var diag:Number = Math.min(dx, dy);
var straight:Number = dx + dy;
return _diagCost * diag + _straightCost * (straight - 2 * diag);
}
public function get visited():Array
{
return _closed.concat(_open);
}
}
}