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 Vector3 WorldCoordinates; // 世界坐标 (中心点)
public float GCost; // 从起点到当前节点的实际代价
public float HCost; // 从当前节点到终点的估算代价 (启发式)
public float FCost => GCost + HCost; // 总代价
public PathNode Parent; // 父节点,用于回溯路径
public PathNodeStatus Status; // 新增状态属性
///
/// 构造一个新的PathNode实例。
///
/// 节点的格子坐标。
/// 节点的世界坐标。
public PathNode(Vector3Int cellCoords, Vector3 worldCoords)
{
CellCoordinates = cellCoords;
WorldCoordinates = worldCoords;
GCost = float.MaxValue; // 初始化 GCost 为最大值
HCost = 0;
Parent = null; // 确保 Parent 默认是 null
Status = PathNodeStatus.Unvisited; // 初始化状态
}
///
/// 判断两个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})";
}
///
/// 比较两个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);
}
}
///
/// 管理实体的路径生成和移动。
///
public class EntityPathManager
{
public bool IsPathComplete => _currentPath == null || _currentPath.Count == 0 || _pathIndex >= _currentPath.Count;
///
/// 最小优先队列实现,用于A*算法的OpenSet。
///
/// 队列中存储的元素类型,必须实现 IComparableT。
private class PriorityQueue where T : IComparable
{
private readonly List data; // 存储堆元素的列表
public int Count => data.Count; // 队列中的元素数量
public PriorityQueue()
{
data = new List();
}
///
/// 将元素添加到队列中。
///
/// 要添加的元素。
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;
}
}
private readonly Entity _entity; // 标记为 readonly
private List _currentPath;
private int _pathIndex;
private const float HEURISTIC_MULTIPLIER = 1.0f; // 启发式乘数
private const float IMPASSABLE_TRAVEL_COST_THRESHOLD = 1.0f; // 不可通行成本阈值
///
/// 构造一个新的EntityPathManager实例。
///
/// 要管理路径的实体。
public EntityPathManager(Entity entity)
{
_entity = entity;
_currentPath = new List();
_pathIndex = 0;
}
///
/// 生成从实体当前位置到目标坐标的路径。
///
/// 目标世界坐标。
public void GeneratePath(Vector2 targetCoordinate)
{
_currentPath.Clear();
_pathIndex = 0;
if (_entity == null)
{
Debug.LogError("实体在实体路径管理器中为空,无法生成路径。");
return;
}
// 假设 Program.Instance.GetDimension 和 currentDimension.landform 存在且有效
// 否则在实际项目中需要更严格的空值检查和错误处理
var currentDimension = Program.Instance.GetDimension(_entity.currentDimensionId);
if (currentDimension == null)
{
Debug.LogError($"未找到维度: {_entity.currentDimensionId}");
return;
}
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)
{
Debug.LogWarning($"目标位置 {targetCoordinate} 不可通行或超出地图边界,无法生成路径。");
_currentPath.Clear();
return;
}
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;
}
// 遍历邻居节点 (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);
}
}
}
}
// 如果OpenSet为空且没有找到路径
Debug.LogWarning($"未找到实体 {_entity.name} 到目标 {targetCoordinate} 的路径。可能目标不可达,或被完全包围。");
_currentPath.Clear();
}
///
/// 获取实体下一帧应该移动到的位置。
///
/// 实体当前的世界坐标。
/// 实体的移动速度(单位:世界单位/秒)。
/// 下一帧实体的位置。
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)
{
// 实体可以在当前帧内到达或超过当前路点。
// 直接移动到路点,并将路径索引前进到下一个。
Vector3 nextPosition = new Vector3(targetWaypoint2D.x, targetWaypoint2D.y, currentPosition.z);
_pathIndex++; // 前进到下一个路点
return nextPosition;
}
else
{
// 实体在当前帧内无法到达当前路点。
// 以最大移动距离向当前路点移动。
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) // 使用一个小的阈值判断是否“到达”路点
{
int lookAheadIndex = _pathIndex + 1;
if (lookAheadIndex < _currentPath.Count)
{
var nextWaypoint2D = _currentPath[lookAheadIndex];
return (nextWaypoint2D - currentPosition2D).normalized;
}
else
{
// 这是最后一个路点,或者没有下一个路点
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;
}
// 移除了 GetSmoothTravelCost 方法
}
}