简易的A*算法 自动寻路
参考:
路径计算方式(详见参考:堪称最好最全的A*算法详解(译文)):
1. 曼哈顿距离,横向和纵向直线距离,仅限于横向纵向移动
2. 对角线距离,对角线 + 直线,可以横向、纵向、对角线方向移动
3. 欧几里得距离,任意角度直线,任意方向移动
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public class AStartTest
{
AStarNode[,] nodeMap = new AStarNode[10, 10];
void CheckAndFindPath(AStarNode startNode, AStarNode endNode)
{
//计算路径
List<AStarNode> pathNodes = FindNodePath(startNode, endNode, nodeMap);
if (pathNodes == null || pathNodes.Count == 0)
return;
//计算路径折点
List<AStarNode> pathPoints = GetPathPoint(pathNodes);
}
//计算路径折点
List<AStarNode> GetPathPoint(List<AStarNode> path)
{
List<AStarNode> tmpPointList = new List<AStarNode>();
//无折点
if (path.Count <= 2)
return tmpPointList;
//当前节点与前一节点的位置关系(X坐标相同或Y坐标相同)
bool lastDirIsX = path[1].pos.x == path[0].pos.x;
//计算折点
for (int i = 2; i < path.Count; i++)
{
//若与前一节点X坐标相同
if (path[i].pos.x == path[i - 1].pos.x)
{
//前两个节点时Y坐标相同,即为折点
if (!lastDirIsX)
{
//记录折点,记录当前节点关系
tmpPointList.Add(path[i - 1]);
lastDirIsX = true;
}
}
else
{
if (lastDirIsX)
{
tmpPointList.Add(path[i - 1]);
lastDirIsX = false;
}
}
}
//路径最后节点也视为折点
tmpPointList.Add(path[path.Count - 1]);
//
return tmpPointList;
}
#region --- A*算法 ---
//计算最短有效路径
List<AStarNode> openList = new List<AStarNode>();
List<AStarNode> closeList = new List<AStarNode>();
List<AStarNode> aroundNodes;
List<AStarNode> FindNodePath(AStarNode startNode, AStarNode endNode, AStarNode[,] allNodes)
{
//计算范围内的节点
openList.Clear();
//不在计算范围内的节点
closeList.Clear();
//添加起点
openList.Add(startNode);
AStarNode curNode;
//从起点开始循环判断
while (openList.Count > 0)
{
//初始当前位置
curNode = openList[0];
//计算最优当前位置
for (int i = 0; i < openList.Count; i++)
{
//F:从起点到目标点的移动步数
//H:从当前位置到目标位置的移动步数
if (openList[i].CostF < curNode.CostF && openList[i].costH < curNode.costH)
{
curNode = openList[i];
}
}
//锁定当前位置节点
openList.Remove(curNode);
closeList.Add(curNode);
////已经计算到目标点
//if (curNode.Equals(endNode))
//{
// //返回最优路径
// return GetPathWithNode(startNode, endNode);
// }
//未计算到目标点, 继续
//获取当前点的周围节点, 在周围节点中查找下一步的最优节点
aroundNodes = GetAroundNodes(curNode, allNodes);
for (int i = 0; i < aroundNodes.Count; i++)
{
//计算到目标点
if (aroundNodes[i].Equals(endNode))
{
//设置上一节点
aroundNodes[i].lastNode = curNode;
//返回最优路径
return GetPathWithNode(startNode, endNode);
}
//不是目标点, 继续计算, 剔除周围节点不可通过、在不可计算范围内的节点
if (!aroundNodes[i].isWall && !closeList.Contains(aroundNodes[i]))
{
//计算 G H F
//F:从起点到目标点的移动步数
//G:从起点到当前位置的移动步数
int newCostG = curNode.costG + GetNodesDistance(curNode, aroundNodes[i]);
if (newCostG <= aroundNodes[i].costG || !openList.Contains(aroundNodes[i]))
{
//刷新赋值
aroundNodes[i].costG = newCostG;
//H:从当前位置到目标位置的移动步数
aroundNodes[i].costH = GetNodesDistance(aroundNodes[i], endNode);
//设置上级节点
aroundNodes[i].lastNode = curNode;
//添加到计算范围内
if (!openList.Contains(aroundNodes[i]))
{
openList.Add(aroundNodes[i]);
}
}
}
}
}
return null;
}
//计算距离
int GetNodesDistance(AStarNode startNode, AStarNode endNode)
{
return Mathf.Abs(startNode.pos.x - endNode.pos.x) + Mathf.Abs(startNode.pos.y - endNode.pos.y);
}
//周围节点只取上下左右四个, 不取对角线(根据实际需求设置周围节点)
Vector2Int[] aroundPos = { new Vector2Int(0, 1), new Vector2Int(0, -1), new Vector2Int(1, 0), new Vector2Int(-1, 0) };
//获取周围Node
List<AStarNode> tmpAroundList = new List<AStarNode>();
List<AStarNode> GetAroundNodes(AStarNode curNode, AStarNode[,] allNodes)
{
tmpAroundList.Clear();
for (int i = 0; i < aroundPos.Length; i++)
{
//计算周围节点坐标
int x = curNode.pos.x + aroundPos[i].x;
int y = curNode.pos.y + aroundPos[i].y;
//剔除不在取值范围内的数据
if (x >= 0 && x < allNodes.GetLength(0) && y >= 0 && y < allNodes.GetLength(1))
{
if (allNodes[x, y] != null)
tmpAroundList.Add(allNodes[x, y]);
}
}
return tmpAroundList;
}
//获取路径(包含起点)
List<AStarNode> tmpNodePath = new List<AStarNode>();
List<AStarNode> GetPathWithNode(AStarNode startNode, AStarNode endNode)
{
tmpNodePath.Clear();
if (endNode != null)
{
//逆向查找路径
AStarNode temp = endNode;
while (temp != startNode)
{
tmpNodePath.Add(temp);
temp = temp.lastNode;
}
tmpNodePath.Add(startNode);
//路径数据反向
tmpNodePath.Reverse();
}
return tmpNodePath;
}
#endregion
}
public class AStarNode
{
//A*算法节点类
//是否能通过
public bool isWall;
//位置坐标
public Vector2Int pos;
//上个节点
public AStarNode lastNode;
//从起点到当前位置的移动步数
public int costG;
//从当前位置到目标位置的移动步数
public int costH;
//从起点到目标点的移动步数
public int CostF
{
get { return costG + costH; }
}
public AStarNode(bool _isWall, Vector2Int _pos)
{
isWall = _isWall;
pos = _pos;
}
//重写Equals
public override bool Equals(object obj)
{
if (obj is AStarNode)
{
AStarNode objNode = (AStarNode)obj;
return objNode.pos == pos;
}
return false;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}