代码git:
https://github.com/SYRollingStone/SiYangUnityAlgorithmsSamples
一、总结
AStar是一个评估每个节点代价获取最短路径的算法。代价F = G + H.G是从起点到当前节点的真实距离,H是当前节点到终点的启发式距离。
有2个关键数据结构,一个是开放列表,一个是关闭列表。关闭列表表示这个节点已经被选为 current 并扩展过(canonical A* 做法),一般就不再考虑它。开放列表存放还已发现但尚未被弹出并扩展的节点集合。每轮从 开放列表中取出 f 最小的 current,立刻移入 关闭列表,然后扩展它的邻居。
当启发函数 一致/可一致(consistent) 时,比如我这个简单例子是四向网格+曼哈顿就是,节点进 关闭列表后不需要 reopen;否则某些变体需要允许从 关闭列表 重新回到 开放列表。
有一个关键的判断,当前最小F值节点,如果他找到一个不在关闭列表的邻居,并且这个邻居的G值大于我当前最小F节点+一次移动代价,那么就说明当前路线到达邻居节点比以往的路线都要短,那么就更新邻居节点的GHF和父亲节点信息,此时邻居节点可能还不在 开放列表里,这时第一次发现它也会更新。
二、一个四向简单矩阵的的AStar代码
这是最简单的AStar算法演示,我的原点在左上(0,0),向右向下为正。
cs
using System.Collections.Generic;
using UnityEngine;
namespace Pathfinding.AStar
{
// 最简单的 四向移动矩阵
public class Dir4AStarAlgorithm
{
public int[,] matrix; // 矩阵
public Vector2Int start ; // 起点
public Vector2Int end ; // 终点
private List<AStarNode> openList = new List<AStarNode>(); // 开放列表
private HashSet<AStarNode> closedList = new HashSet<AStarNode>(); // 关闭列表 ,因为数据结构中的数据来自于不同对象,所以我没有重写Equals/GetHashCode
private AStarNode[,] nodes; // 存储所有节点的数组
private Vector2Int[] directions = new Vector2Int[]
{
new Vector2Int(0, -1), // 上
new Vector2Int(1, 0), // 右
new Vector2Int(0, 1), // 下
new Vector2Int(-1, 0), // 左
};
public Dir4AStarAlgorithm(int[,] matrixParam)
{
matrix = matrixParam;
int cols = matrix.GetLength(0);
int rows = matrix.GetLength(1);
nodes = new AStarNode[cols, rows];
// 初始化节点
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
nodes[j, i] = new AStarNode(j, i);
if (matrix[j, i] == 1)
{
start = new Vector2Int(j, i);
}
if (matrix[j, i] == 2)
{
end = new Vector2Int(j, i);
}
}
}
}
public void RunAStar()
{
AStarNode startNode = nodes[start.x, start.y];
AStarNode endNode = nodes[end.x, end.y];
startNode.g = 0;
startNode.h = CalculateHeuristic(startNode, endNode);
startNode.CalculateF();
openList.Add(startNode);
while (openList.Count > 0)
{
// 获取 f 值最小的节点
AStarNode currentFMinNode = GetNodeWithLowestF();
if (currentFMinNode == endNode)
{
// 找到目标,构造路径
TracePath(currentFMinNode);
return;
}
openList.Remove(currentFMinNode);
closedList.Add(currentFMinNode);
// 检查邻接节点
foreach (var dir in directions)
{
int newX = currentFMinNode.x + dir.x;
int newY = currentFMinNode.y + dir.y;
// 如果新位置不在矩阵范围内,跳过
if (newX < 0 || newX >= matrix.GetLength(0) || newY < 0 || newY >= matrix.GetLength(1))
continue;
// 如果是障碍,跳过
if (matrix[newX, newY] == 3)
continue;
AStarNode neighbor = nodes[newX, newY];
if (closedList.Contains(neighbor))
continue;
// 计算 g、h 和 f
float tentativeG = currentFMinNode.g + 1; // 假设每一步的代价为 1
if (tentativeG < neighbor.g)
{
neighbor.g = tentativeG;
neighbor.h = CalculateHeuristic(neighbor, endNode);
neighbor.CalculateF();
neighbor.parent = currentFMinNode;
if (!openList.Contains(neighbor))
{
openList.Add(neighbor);
}
}
}
}
}
// 使用曼哈顿距离作为启发式估算
float CalculateHeuristic(AStarNode node, AStarNode targetNode)
{
return Mathf.Abs(node.x - targetNode.x) + Mathf.Abs(node.y - targetNode.y);
}
AStarNode GetNodeWithLowestF()
{
AStarNode lowestNode = openList[0];
foreach (var node in openList)
{
if (node.f < lowestNode.f)
{
lowestNode = node;
}
}
return lowestNode;
}
void TracePath(AStarNode endNode)
{
AStarNode currentNode = endNode;
while (currentNode != null)
{
// 在控制台打印路径
Debug.Log($"路径: ({currentNode.x}, {currentNode.y})");
currentNode = currentNode.parent;
}
}
}
}
cs
namespace Pathfinding.AStar
{
public class AStarNode
{
public int x, y;
public float g, h, f;
public AStarNode parent;
public AStarNode(int x, int y)
{
this.x = x;
this.y = y;
this.g = float.MaxValue;
this.h = 0;
this.f = float.MaxValue;
this.parent = null;
}
public void CalculateF()
{
f = g + h;
}
}
}