以前项目中用到了寻路,就写了个A*寻路的算法类,感觉还不错,这里分享出来。
这里有可运行的测试项目:https://github.com/LiuFeng1011/Test/tree/master/Assets/AStar
A*算法的原理网上有很多,这里只简单说一下我这个算法的逻辑:
*我的这个算法里面没有关闭列表,因为我会根据地图数据创建一个对应的节点数据的数组,每个节点数据记录自己当前的状态,是开启还是关闭的。节点数据只有在寻找周围点被找到的时候才会建立并放到数组中,这样在每次添加节点时,只要在数组中取出对应位置的节点数据,就能知道节点是开启的还是关闭的或者还没有被添加,不用去遍历open和close列表。
*开放列表直接使用List来保存,为了使列表保持有序,每次添加节点时直接把节点按照从小到大的顺序插入到对应位置。这里可以使用其它数据结构或者排序算法来进行优化。
*路径可以行进的方向使用一个数组来表示:
//附近的格子 4方向
int[,] nearArray = new int[,]{
{0, 1},
{1, 0},
{0, -1},
{-1, 0},
};
//附近的格子 8方向
int[,] nearArray= new int[,]{
{0, 1},
{1, 1},
{1, 0},
{1, -1},
{0, -1},
{-1, -1},
{-1, 0},
{-1, 1}
};
这里可以修改成任意个方向和距离,我只对1个距离以内的移动进行过测试。其它距离不能保证正确性。
*G和H值的计算可以使用曼哈顿距离或者欧式距离,曼哈顿距离效率上会更高。
float GetNodeG(MapNode parent,MapNode node){
//曼哈顿距离
float dis = Mathf.Abs(parent.p.x - node.p.x) + Mathf.Abs(parent.p.y - node.p.y);
//欧式距离
//float dis = Vector2.Distance(parent.p, node.p);
return parent.g + dis;
}
float GetNodeH( MapNode node)
{
//曼哈顿距离
return Mathf.Abs(endPosition.x - node.p.x) + Mathf.Abs(endPosition.y - node.p.y);
//欧式距离
//return Vector2.Distance(endPosition,node.p);
}
最终的结果只有在4方向时表现上会有些许不同:
左图为曼哈顿距离的结果,右图为欧式距离的结果,虽然移动距离是一样的,但是很明显欧式距离更符合我们的习惯。
*使用方式:
调用AStar类的StratAStar方法即可
/// <summary>
/// 开始寻路
/// </summary>
/// <returns>路径点数据,从起始点到结束点路径的有序vector数组.</returns>
/// <param name="map">地图数据 二维数组,0为可移动路径,1为不可移动路径.</param>
/// <param name="startPosition">开始位置.</param>
/// <param name="endPosition">结束位置.</param>
public List<Vector2> StratAStar(int[,] map,Vector2 startPosition,Vector2 endPosition)
*示例:
定义一个二维数据作为我们的地图数据:
int[,] map =
{
{0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,1,1,1,1,1,1,1,0,0,0,0},
{0,0,0,0,0,0,0,0,0,1,0,0,0,0},
{0,0,0,0,0,0,0,0,0,1,0,0,0,0},
{0,0,0,0,0,0,0,0,0,1,0,0,0,0},
{0,0,0,0,0,0,0,0,0,1,0,0,0,0},
{0,0,0,0,0,0,0,0,0,1,0,0,0,0},
{0,0,0,0,0,0,0,0,0,1,0,0,0,0},
{0,0,0,0,0,0,0,0,0,1,0,0,0,0},
{0,0,0,0,0,0,0,0,0,1,0,0,0,0},
{0,0,0,1,1,1,1,1,1,1,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};
设置起始和结束位置:
Vector2 startPosition = new Vector2(5,6);
Vector2 endPosition = new Vector2(8, 12);
创建AStar类并调用开始方法:
AStar astar = new AStar();
List<Vector2> path = astar.StratAStar(map,startPosition,endPosition);
看下效果:
下面使用随机数组:
int[, ] map = new int[mapWidth,mapHight];
设定起始点和结束点,然后随机向数组中填充数据
Vector2 startPosition = new Vector2(Random.Range(0, mapWidth),Random.Range(0, mapHight));
Vector2 endPosition = new Vector2(Random.Range(0, mapWidth), Random.Range(0, mapHight));
for (int i = 0; i < mapWidth;i ++){
for (int j = 0; j < mapHight; j++)
{
//如果是起点或者终点 跳过
if((i == (int)startPosition.x && j == (int)startPosition.y) ||
(i == (int)endPosition.x && j == (int)endPosition.x) ){
continue;
}
if (Random.Range(0f, 1f) < 0.2f){
map[i, j] = 1;
}
}
}
看下效果:
完整的AStar类:
using System.Collections.Generic;
using UnityEngine;
public class AStar
{
//节点
private class MapNode{
//节点状态
public enum enNodeState{
normal,//待机
open,//开启
close //关闭
}
public MapNode parent = null;//父节点
public float g, h, f;
public Vector2 p;//位置
public enNodeState state = enNodeState.normal;
}
//附近的格子 8方向
int[,] nearArray= new int[,]{
{0, 1},
{1, 1},
{1, 0},
{1, -1},
{0, -1},
{-1, -1},
{-1, 0},
{-1, 1}
};
//附近的格子 4方向
//int[,] nearArray = new int[,]{
// {0, 1},
// {1, 0},
// {0, -1},
// {-1, 0},
//};
Vector2 startPosition, endPosition;//起始点和结束点
//开放列表,在插入时根据MapNode的f值进行排序,即优先队列
List<MapNode> openList = new List<MapNode>();
//所有点
MapNode[,] mapList;
//向开放列表中加入节点,这里需要进行排序
void PushNode(MapNode node)
{
node.state = MapNode.enNodeState.open;
for (int i = 0; i < openList.Count; i++){
if (openList[i].f > node.f){
openList.Insert(i,node);
return;
}
}
openList.Add(node);
}
//创建节点时即对节点进行估价
MapNode CreateNode(Vector2 p,MapNode parent){
MapNode node = new MapNode();
node.parent = parent;
node.p = p;
//f = g+h
//g和h直接使用曼哈顿距离
//--------g------
if(parent != null){
node.g = GetNodeG(parent,node);
}else {
node.g = 0;
}
//--------h------
node.h =GetNodeH(node);
//--------f------
node.f = node.g + node.h;
//创建的节点加入到节点列表中
mapList[(int)node.p.x,(int)node.p.y] = node;
return node;
}
float GetNodeG(MapNode parent,MapNode node){
//曼哈顿距离
//float dis = Mathf.Abs(parent.p.x - node.p.x) + Mathf.Abs(parent.p.y - node.p.y);
//欧式距离
float dis = Vector2.Distance(parent.p, node.p);
return parent.g + dis;
}
float GetNodeH( MapNode node)
{
//曼哈顿距离
//return Mathf.Abs(endPosition.x - node.p.x) + Mathf.Abs(endPosition.y - node.p.y);
//欧式距离
return Vector2.Distance(endPosition,node.p);
}
/// <summary>
/// 开始寻路
/// </summary>
/// <returns>路径点数据,从起始点到结束点路径的有序vector数组.</returns>
/// <param name="map">地图数据 二维数组,0为可移动路径,1为不可移动路径.</param>
/// <param name="startPosition">开始位置.</param>
/// <param name="endPosition">结束位置.</param>
public List<Vector2> StratAStar(int[,] map,Vector2 startPosition,Vector2 endPosition){
mapList = new MapNode[map.GetLength(0),map.GetLength(1)];
//附近可移动点的数量
int nearcount = nearArray.GetLength(0);
this.startPosition = startPosition;
this.endPosition = endPosition;
//起始点加入开启列表
MapNode startNode = CreateNode(startPosition,null);
PushNode(startNode);
MapNode endNode = null;//目标节点
//开始寻找路径
while(openList.Count > 0){
//取出开启列表中f值最低的节点,由于我们在向开启列表中添加节点时已经进行了排序,所以这里直接取第0个值即可
MapNode node = openList[0];
//如果node为目标点则结束寻找
if(node.p.x == endPosition.x && node.p.y == endPosition.y){
endNode = node;
break;
}
//设置为关闭状态并从开启列表中移除
node.state = MapNode.enNodeState.close;
openList.RemoveAt(0);
//当前点坐标
//相邻格子加入到开启列表
for (int i = 0; i < nearcount; i ++){
Vector2 nearPosition = node.p - new Vector2(nearArray[i,0], nearArray[i,1]);
//位置是否超出范围
if((nearPosition.x < 0 || nearPosition.x >= map.GetLength(0)) ||
(nearPosition.y < 0 || nearPosition.y >= map.GetLength(1))){
continue;
}
//该位置是否可以移动
if(map[(int)nearPosition.x,(int)nearPosition.y] != 0){
continue;
}
//是否已经创建过这个点
MapNode nearNode = mapList[(int)nearPosition.x, (int)nearPosition.y];
if(nearNode != null){
//该节点已经创建过
//节点是否关闭
if(nearNode.state == MapNode.enNodeState.close){
continue;
}
//重新计算g
float newg = GetNodeG(node, nearNode);
if(newg < nearNode.g){
nearNode.parent = node;
nearNode.g = newg;
nearNode.f = nearNode.g + nearNode.h;
Debug.Log("==" + openList.Count);
//重新对开放列表排序
openList.Remove(nearNode);
Debug.Log(openList.Count);
}else{
continue;
}
}else{
//创建节点
nearNode = CreateNode(nearPosition, node);
}
PushNode(nearNode);
}
}
//路径数据
List<Vector2> ret = new List<Vector2>();
if (endNode == null)
{
Debug.Log("no path!");
return ret;
}
//将路径保存到数组中
while(endNode.parent != null){
ret.Insert(0,endNode.p);
endNode = endNode.parent;
}
return ret;
}
}