using System; using System.Collections.Generic; using UnityEngine; public sealed class AStar { /// /// 八个方向(根据实际情况制定方向规则) /// private static readonly int[ , ] m_Direction = new int[8, 2]{{1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1}, {0, -1}, {1, -1}}; private static readonly Dictionary> m_DistanceCalculators = new Dictionary>() { { GlobalEnum.DistanceCalculationMethod.Manhattan, (v1, v2) => ManhattanDistance(v1, v2) }, { GlobalEnum.DistanceCalculationMethod.Euclidean, (v1, v2) => EuclideanDistance(v1, v2, true) }, { GlobalEnum.DistanceCalculationMethod.Chebyshev, (v1, v2) => ChebyshevDistance(v1, v2) }, }; /// /// 曼哈顿距离 /// /// 坐标1 /// 坐标2 /// 两个坐标之间的曼哈顿距离 private static float ManhattanDistance(Vector2 v1, Vector2 v2) { return Mathf.Abs(v1.x - v2.x) + Mathf.Abs(v1.y - v2.y); } /// /// 欧几里得距离 /// /// 坐标1 /// 坐标2 /// 欧几里得距离是否需要开根(不开根性能更好, 但会导致算法无法找到最优路径) /// 两个坐标之间的欧几里得距离或欧几里得距离的平方 private static float EuclideanDistance(Vector2 v1, Vector2 v2, bool isSquareRootRequired = true) { Vector2 difference = v1 - v2; float euclideanDistance = Vector2.Dot(difference, difference); return isSquareRootRequired ? Mathf.Sqrt(euclideanDistance) : euclideanDistance; } /// /// 切比雪夫距离 /// /// 坐标1 /// 坐标2 /// 两个坐标之间的切比雪夫距离 private static float ChebyshevDistance(Vector2 v1, Vector2 v2) { return Mathf.Max(Mathf.Abs(v1.x - v2.x), Mathf.Abs(v1.y - v2.y)); } /// /// 待评估的节点 /// private readonly PriorityQueue m_OpenList = new PriorityQueue(); /// /// 已评估的节点 /// private readonly HashSet m_CloseList = new HashSet(); /// /// 通过 A* 获取两点之间的最短路径 /// /// 起始位置 /// 终点位置 /// 寻路图 /// 通过 A* 算出的最短路径, 如果找不到最短路径, 返回 null public List FindPath2D(Vector2 startPosition, Vector2 endPosition, AStarNode[ , ] map) { // 判断起始点和终点是否合法 // 是否在地图外(根据实际情况制定) float startPositionX = startPosition.x; float startPositionY = startPosition.y; float endPositionX = endPosition.x; float endPositionY = endPosition.y; float rangeX = map[map.GetLength(0) - 1, 0].x; float rangeY = map[0, map.GetLength(1) - 1].y; if ( startPositionX < map[0, 0].x || startPositionX > rangeX || startPositionY < map[0, 0].y || startPositionY > rangeY || endPositionX < map[0, 0].x || endPositionX > rangeX || endPositionY < map[0, 0].y || endPositionY > rangeY ) { return null; } (int startX, int startY) = WorldPositionToAStarPosition(startPosition); (int endX, int endY) = WorldPositionToAStarPosition(endPosition); AStarNode startNode = map[startX, startY]; AStarNode endNode = map[endX, endY]; // 是否是阻挡(不可通过) if (startNode.nodeStatu == GlobalEnum.NodeStatu.Stop || endNode.nodeStatu == GlobalEnum.NodeStatu.Stop || startNode == endNode) { return null; } startNode.father = null; startNode.g = 0; startNode.h = CalculateH(startNode, endNode, GlobalEnum.DistanceCalculationMethod.Euclidean); startNode.f = startNode.g + startNode.h; m_OpenList.Enqueue(startNode, startNode); while (!m_OpenList.IsEmpty) { AStarNode currentNode = m_OpenList.Dequeue(); // 如果找到终点,构建路径并返回 if (currentNode == endNode) { List result = new List() { currentNode }; AStarNode pathNode = currentNode; while (pathNode.father != null) { pathNode = pathNode.father; result.Add(pathNode); } result.Reverse(); return result; } m_CloseList.Add(currentNode); // 获取当前节点的坐标 (int currentX, int currentY) = WorldPositionToAStarPosition(currentNode.position); // 评估所有邻居节点 for (int i = 0; i < 8; ++i) { int x = currentX + m_Direction[i, 0]; int y = currentY + m_Direction[i, 1]; EvaluateNeighbor(x, y, i, currentNode, endNode, map); } } // 没有找到路径 return null; } /// /// 查找邻近节点并将合法的节点添加到 m_OpenList /// /// 当前节点的x轴坐标 /// 当前节点的y轴坐标 /// 方向数组的下标 /// 当前节点的父节点 /// 终点 /// 寻路图 private void EvaluateNeighbor(int x, int y, int directionIndex, AStarNode father, AStarNode end, AStarNode[ , ] map) { // 判断邻近节点是否合法 // 1. 是否在地图外 if (x < 0 || x >= map.GetLength(1) || y < 0 || y >= map.GetLength(0)) { return; } // 2. 是否是阻挡(不可通过)并且是否在开启或关闭列表中 AStarNode node = map[x, y]; if (node.nodeStatu == GlobalEnum.NodeStatu.Stop || m_OpenList.Contains(node) || m_CloseList.Contains(node)) { return; } // 3. 角色斜向走时(如果允许),需要判断斜向走时两边的障碍物会不会挡住角色 if ( m_Direction[directionIndex, 0] == 1 && m_Direction[directionIndex, 1] == 1 && map[x - 1, y].nodeStatu == GlobalEnum.NodeStatu.Stop && map[x, y - 1].nodeStatu == GlobalEnum.NodeStatu.Stop ) { return; } else if ( m_Direction[directionIndex, 0] == -1 && m_Direction[directionIndex, 1] == 1 && map[x + 1, y].nodeStatu == GlobalEnum.NodeStatu.Stop && map[x, y - 1].nodeStatu == GlobalEnum.NodeStatu.Stop) { return; } else if ( m_Direction[directionIndex, 0] == -1 && m_Direction[directionIndex, 1] == -1 && map[x + 1, y].nodeStatu == GlobalEnum.NodeStatu.Stop && map[x, y + 1].nodeStatu == GlobalEnum.NodeStatu.Stop) { return; } else if ( m_Direction[directionIndex, 0] == 1 && m_Direction[directionIndex, 1] == -1 && map[x - 1, y].nodeStatu == GlobalEnum.NodeStatu.Stop && map[x, y + 1].nodeStatu == GlobalEnum.NodeStatu.Stop) { return; } node.father = father; node.g = CalculateG(father, node, GlobalEnum.DistanceCalculationMethod.Euclidean); node.h = CalculateH(node, end, GlobalEnum.DistanceCalculationMethod.Euclidean); node.f = node.g + node.h; m_OpenList.Enqueue(node, node); } /// /// 计算当前节点的g值 /// /// 当前节点的父节点 /// 当前节点 /// 节点间的距离计算方式 /// 当前节点的g值 private static float CalculateG(AStarNode father, AStarNode currentNode, GlobalEnum.DistanceCalculationMethod methodName = GlobalEnum.DistanceCalculationMethod.Manhattan) { return father.g + m_DistanceCalculators[methodName](father.position, currentNode.position); } /// /// 计算当前节点的h值 /// /// 当前节点 /// 终点 /// 节点间的距离计算方式 /// 当前节点的h值 private static float CalculateH(AStarNode currentNode, AStarNode endNode, GlobalEnum.DistanceCalculationMethod methodName = GlobalEnum.DistanceCalculationMethod.Manhattan) { return m_DistanceCalculators[methodName](currentNode.position, endNode.position); } /// /// 将世界坐标转化为 A* 算法中寻路图的下标(下标的转换需根据实际情况制定) /// /// 世界坐标 /// 转换后的 x,y 坐标 private static (int, int) WorldPositionToAStarPosition(Vector2 worldPosition) { return ((int)worldPosition.x, (int)worldPosition.y); } }