using System; using System.Collections.Generic; using UnityEngine; namespace Entity { public enum PathNodeStatus // 新增枚举 { Unvisited, InOpenSet, InClosedSet } /// /// 表示寻路算法中的一个节点。 /// public class PathNode : IComparable // 实现 IComparable 接口 { public Vector3Int CellCoordinates; // 格子坐标 public float GCost; // 从起点到当前节点的实际代价 public float HCost; // 从当前节点到终点的估算代价 (启发式) public PathNode Parent; // 父节点,用于回溯路径 public PathNodeStatus Status; // 新增状态属性 public Vector3 WorldCoordinates; // 世界坐标 (中心点) /// /// 构造一个新的PathNode实例。 /// /// 节点的格子坐标。 /// 节点的世界坐标。 public PathNode(Vector3Int cellCoords, Vector3 worldCoords) { CellCoordinates = cellCoords; WorldCoordinates = worldCoords; GCost = float.MaxValue; // 初始化 GCost 为最大值 HCost = 0; Parent = null; // 确保 Parent 默认是 null Status = PathNodeStatus.Unvisited; // 初始化状态 } public float FCost => GCost + HCost; // 总代价 /// /// 比较两个PathNode实例,用于排序(优先队列)。 /// 优先比较 FCost,FCost 相等时优先比较 HCost(避免路径过长)。 /// public int CompareTo(PathNode other) { if (other == null) return 1; var fCostComparison = FCost.CompareTo(other.FCost); if (fCostComparison != 0) return fCostComparison; // FCost 相等时,HCost 小的优先级更高(更接近目标) return HCost.CompareTo(other.HCost); } /// /// 判断两个PathNode实例是否相等,基于它们的格子坐标。 /// /// 要比较的对象。 /// 如果相等则返回true,否则返回false。 public override bool Equals(object obj) { if (obj is PathNode other) return CellCoordinates.Equals(other.CellCoordinates); return false; } /// /// 获取PathNode实例的哈希码,基于其格子坐标。 /// /// PathNode实例的哈希码。 public override int GetHashCode() { return CellCoordinates.GetHashCode(); } /// /// 返回表示当前节点的字符串。 /// /// 节点的字符串表示。 public override string ToString() { return $"Node({CellCoordinates}, F:{FCost:F1}, G:{GCost:F1}, H:{HCost:F1}, Status:{Status})"; } } /// /// 管理实体的路径生成和移动。 /// public class EntityPathManager { private const float HEURISTIC_MULTIPLIER = 1.0f; // 启发式乘数 private const float IMPASSABLE_TRAVEL_COST_THRESHOLD = 1.0f; // 不可通行成本阈值 private readonly Entity _entity; // 标记为 readonly private List _currentPath; private int _pathIndex; /// /// 构造一个新的EntityPathManager实例。 /// /// 要管理路径的实体。 public EntityPathManager(Entity entity) { _entity = entity; _currentPath = new List(); _pathIndex = 0; } public bool IsPathComplete => _currentPath == null || _currentPath.Count == 0 || _pathIndex >= _currentPath.Count; /// /// 生成从实体当前位置到目标坐标的路径。 /// /// 目标世界坐标。 public bool GeneratePath(Vector2 targetCoordinate) { _currentPath.Clear(); _pathIndex = 0; if (!_entity) { Debug.LogWarning("实体在实体路径管理器中为空,无法生成路径。"); return false; } // 假设 Program.Instance.GetDimension 和 currentDimension.landform 存在且有效 // 否则在实际项目中需要更严格的空值检查和错误处理 var currentDimension = Program.Instance.GetDimension(_entity.currentDimensionId); if (!currentDimension) { Debug.LogWarning($"未找到维度: {_entity.currentDimensionId}"); return false; } var landform = currentDimension.landform; // 起点和终点的格子坐标 var startCell = landform.GetCellCoordinates(_entity.Position); var targetWorldPosition3D = new Vector3(targetCoordinate.x, targetCoordinate.y, _entity.Position.z); var targetCell = landform.GetCellCoordinates(targetWorldPosition3D); var entitySize = new Vector3Int(Mathf.CeilToInt(_entity.Size.x), Mathf.CeilToInt(_entity.Size.y), 1); // A* 算法 var openSet = new PriorityQueue(); // 使用优先队列 var allNodes = new Dictionary(); // 用于快速查找已创建的节点 var startNode = new PathNode(startCell, _entity.Position) { GCost = 0, HCost = CalculateHeuristic(_entity.Position, targetCoordinate), Status = PathNodeStatus.InOpenSet // 设置起始节点状态 }; openSet.Enqueue(startNode); allNodes.Add(startCell, startNode); // 检查目标点是否可通行 // 如果目标点在地图边界外,landform.GetTravelCostForArea 应该返回一个不可通行值 var targetCellTravelCost = landform.GetTravelCostForArea(targetCell, entitySize); if (targetCellTravelCost >= IMPASSABLE_TRAVEL_COST_THRESHOLD) { _currentPath.Clear(); return false; } while (openSet.Count > 0) { var currentNode = openSet.Dequeue(); // 从优先队列中取出FCost最小的节点 // 如果这个节点已经以最优路径处理过 (通过另一个更优的路径被Dequeued并Closed) if (currentNode.Status == PathNodeStatus.InClosedSet) continue; // 跳过旧的或重复的队列项 // 将当前节点标记为已处理 currentNode.Status = PathNodeStatus.InClosedSet; // 如果找到目标节点 if (currentNode.CellCoordinates == targetCell) { _currentPath = ReconstructPath(currentNode); return true; } // 遍历邻居节点 (8个方向,包括对角线) foreach (var neighborCellOffset in new[] { new Vector3Int(0, 1, 0), new Vector3Int(0, -1, 0), new Vector3Int(1, 0, 0), new Vector3Int(-1, 0, 0), new Vector3Int(1, 1, 0), new Vector3Int(1, -1, 0), new Vector3Int(-1, 1, 0), new Vector3Int(-1, -1, 0) }) { var neighborCell = currentNode.CellCoordinates + neighborCellOffset; // 获取邻居节点的世界坐标中心点 var neighborWorldPosition = landform.GetWorldCoordinates(neighborCell); // 获取邻居区域的通行成本,考虑到实体大小 var travelCost = landform.GetTravelCostForArea(neighborCell, entitySize); // 如果邻居不可通行,则跳过 if (travelCost >= IMPASSABLE_TRAVEL_COST_THRESHOLD) continue; PathNode neighborNode; // 尝试从 allNodes 字典中获取邻居节点 if (!allNodes.TryGetValue(neighborCell, out neighborNode)) { // 如果节点未被发现,则创建它并添加到 allNodes 字典 neighborNode = new PathNode(neighborCell, neighborWorldPosition); allNodes.Add(neighborCell, neighborNode); } // 【逻辑修改1】A* 状态管理一致性改进 // 如果邻居节点已经在closedSet中,则意味着我们已经以最优路径处理过它,跳过。 // 此处不进行“重新开启已关闭节点”的操作。 if (neighborNode.Status == PathNodeStatus.InClosedSet) continue; // 从当前节点到邻居节点的实际代价 // Distance 乘以 (1 + travelCost) 作为难度系数 var newGCost = currentNode.GCost + Vector3.Distance(currentNode.WorldCoordinates, neighborNode.WorldCoordinates) * (1f + travelCost); // 如果找到更短的路径,则更新邻居节点信息 if (newGCost < neighborNode.GCost) // 路径更优 { neighborNode.GCost = newGCost; neighborNode.HCost = CalculateHeuristic(neighborNode.WorldCoordinates, targetCoordinate); neighborNode.Parent = currentNode; // 【逻辑修改1】A* 状态管理一致性改进 // 只有当邻居节点是Unvisited时,才将其加入OpenSet。 // 如果节点已经是InOpenSet,它的GCost已经被更新,优先队列会处理其优先级变化。 // 如果节点是InClosedSet,则由于前面的continue语句,不会到达此处。 if (neighborNode.Status == PathNodeStatus.Unvisited) { neighborNode.Status = PathNodeStatus.InOpenSet; openSet.Enqueue(neighborNode); } } } } _currentPath.Clear(); return false; } /// /// 获取实体下一帧应该移动到的位置。 /// /// 实体当前的世界坐标。 /// 实体的移动速度(单位:世界单位/秒)。 /// 下一帧实体的位置。 public Vector3 GetNextPosition(Vector3 currentPosition, float moveSpeed) { if (IsPathComplete) return currentPosition; // 没有路径或路径已完成,停留在原地 var targetWaypoint2D = _currentPath[_pathIndex]; var currentPosition2D = new Vector2(currentPosition.x, currentPosition.y); // 计算到当前目标路点的距离 var distanceToWaypoint = Vector2.Distance(currentPosition2D, targetWaypoint2D); // 计算实体在当前帧内可以移动的最大距离 var maxMoveDistance = moveSpeed * Time.deltaTime; if (distanceToWaypoint <= maxMoveDistance) { // 实体可以在当前帧内到达或超过当前路点。 // 直接移动到路点,并将路径索引前进到下一个。 var nextPosition = new Vector3(targetWaypoint2D.x, targetWaypoint2D.y, currentPosition.z); _pathIndex++; // 前进到下一个路点 return nextPosition; } // 实体在当前帧内无法到达当前路点。 // 以最大移动距离向当前路点移动。 var direction = (targetWaypoint2D - currentPosition2D).normalized; var newPosition2D = currentPosition2D + direction * maxMoveDistance; return new Vector3(newPosition2D.x, newPosition2D.y, currentPosition.z); } // 【新增函数】获取实体下一帧的方向 /// /// 获取实体应该移动的方向(2D)。 /// /// 实体的当前世界坐标。 /// 标准化后的移动方向Vector2,如果没有路径或路径完成则返回Vector2.zero。 public Vector2 GetNextDirection(Vector3 currentPosition) { if (IsPathComplete) return Vector2.zero; // 没有路径或路径已完成,没有移动方向 var targetWaypoint2D = _currentPath[_pathIndex]; var currentPosition2D = new Vector2(currentPosition.x, currentPosition.y); var direction = targetWaypoint2D - currentPosition2D; // 如果实体当前已非常接近目标路点,预判性地查看下一个路点的方向 // 这样做有助于动画平滑过渡,避免在到达路点瞬间方向变为零 if (direction.sqrMagnitude < 0.001f) // 使用一个小的阈值判断是否“到达”路点 { var lookAheadIndex = _pathIndex + 1; if (lookAheadIndex < _currentPath.Count) { var nextWaypoint2D = _currentPath[lookAheadIndex]; return (nextWaypoint2D - currentPosition2D).normalized; } // 这是最后一个路点,或者没有下一个路点 return Vector2.zero; } return direction.normalized; } /// /// 计算从当前节点到目标节点的启发式代价。 /// /// 当前世界坐标。 /// 目标世界坐标。 /// 启发式代价。 private float CalculateHeuristic(Vector3 currentWorldPosition, Vector2 targetWorldPosition) { return Vector2.Distance(new Vector2(currentWorldPosition.x, currentWorldPosition.y), targetWorldPosition) * HEURISTIC_MULTIPLIER; } /// /// 从终点回溯并重建路径。 /// /// 路径的终点节点。 /// 世界坐标点的路径列表。 private List ReconstructPath(PathNode endNode) { var path = new List(); var currentNode = endNode; while (currentNode != null) { path.Add(new Vector2(currentNode.WorldCoordinates.x, currentNode.WorldCoordinates.y)); currentNode = currentNode.Parent; } path.Reverse(); // 将路径反转,使其从起点到终点 return path; } /// /// 最小优先队列实现,用于A*算法的OpenSet。 /// /// 队列中存储的元素类型,必须实现 IComparableT。 private class PriorityQueue where T : IComparable { private readonly List data; // 存储堆元素的列表 public PriorityQueue() { data = new List(); } public int Count => data.Count; // 队列中的元素数量 /// /// 将元素添加到队列中。 /// /// 要添加的元素。 public void Enqueue(T item) { data.Add(item); var ci = data.Count - 1; // child index while (ci > 0) { var pi = (ci - 1) / 2; // parent index if (data[ci].CompareTo(data[pi]) >= 0) // 如果子节点不比父节点小,则满足堆属性 break; (data[ci], data[pi]) = (data[pi], data[ci]); ci = pi; } } /// /// 移除并返回队列中最小的元素。 /// /// 最小的元素。 public T Dequeue() { // 假设队列不为空,调用前应检查 Count > 0 var li = data.Count - 1; // last index var frontItem = data[0]; // 最小元素 data[0] = data[li]; // 将最后一个元素移到根部 data.RemoveAt(li); // 移除最后一个元素 --li; // last index (after removal) var pi = 0; // parent index while (true) { var ci = pi * 2 + 1; // child index (left child) if (ci > li) break; // 如果左子节点超出范围,说明没有子节点了 var rc = ci + 1; // right child index if (rc <= li && data[rc].CompareTo(data[ci]) < 0) // 如果右子节点存在且比左子节点小 ci = rc; // 选择右子节点作为要比较的子节点 if (data[pi].CompareTo(data[ci]) <= 0) break; // 如果父节点不比选中的子节点大,则满足堆属性 (data[pi], data[ci]) = (data[ci], data[pi]); pi = ci; } return frontItem; } } // 移除了 GetSmoothTravelCost 方法 } }