mirror of
http://47.107.252.169:3000/Roguelite-Game-Developing-Team/Gen_Hack-and-Slash-Roguelite.git
synced 2025-11-20 12:37:12 +08:00
(client) feat:健康给予,路径优化,结算界面,商店界面 (#60)
Co-authored-by: m0_75251201 <m0_75251201@noreply.gitcode.com> Reviewed-on: http://47.107.252.169:3000/Roguelite-Game-Developing-Team/Gen_Hack-and-Slash-Roguelite/pulls/60
This commit is contained in:
@@ -12,20 +12,19 @@ namespace Entity
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示寻路算法中的一个节点。
|
||||
/// 表示寻路算法中的一个节点。
|
||||
/// </summary>
|
||||
public class PathNode : IComparable<PathNode> // 实现 IComparable 接口
|
||||
{
|
||||
public Vector3Int CellCoordinates; // 格子坐标
|
||||
public Vector3 WorldCoordinates; // 世界坐标 (中心点)
|
||||
public float GCost; // 从起点到当前节点的实际代价
|
||||
public float HCost; // 从当前节点到终点的估算代价 (启发式)
|
||||
public float FCost => GCost + HCost; // 总代价
|
||||
public PathNode Parent; // 父节点,用于回溯路径
|
||||
public PathNodeStatus Status; // 新增状态属性
|
||||
public Vector3 WorldCoordinates; // 世界坐标 (中心点)
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的PathNode实例。
|
||||
/// 构造一个新的PathNode实例。
|
||||
/// </summary>
|
||||
/// <param name="cellCoords">节点的格子坐标。</param>
|
||||
/// <param name="worldCoords">节点的世界坐标。</param>
|
||||
@@ -39,22 +38,35 @@ namespace Entity
|
||||
Status = PathNodeStatus.Unvisited; // 初始化状态
|
||||
}
|
||||
|
||||
public float FCost => GCost + HCost; // 总代价
|
||||
|
||||
/// <summary>
|
||||
/// 判断两个PathNode实例是否相等,基于它们的格子坐标。
|
||||
/// 比较两个PathNode实例,用于排序(优先队列)。
|
||||
/// 优先比较 FCost,FCost 相等时优先比较 HCost(避免路径过长)。
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断两个PathNode实例是否相等,基于它们的格子坐标。
|
||||
/// </summary>
|
||||
/// <param name="obj">要比较的对象。</param>
|
||||
/// <returns>如果相等则返回true,否则返回false。</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is PathNode other)
|
||||
{
|
||||
return CellCoordinates.Equals(other.CellCoordinates);
|
||||
}
|
||||
if (obj is PathNode other) return CellCoordinates.Equals(other.CellCoordinates);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取PathNode实例的哈希码,基于其格子坐标。
|
||||
/// 获取PathNode实例的哈希码,基于其格子坐标。
|
||||
/// </summary>
|
||||
/// <returns>PathNode实例的哈希码。</returns>
|
||||
public override int GetHashCode()
|
||||
@@ -63,57 +75,295 @@ namespace Entity
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回表示当前节点的字符串。
|
||||
/// 返回表示当前节点的字符串。
|
||||
/// </summary>
|
||||
/// <returns>节点的字符串表示。</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Node({CellCoordinates}, F:{FCost:F1}, G:{GCost:F1}, H:{HCost:F1}, Status:{Status})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 比较两个PathNode实例,用于排序(优先队列)。
|
||||
/// 优先比较 FCost,FCost 相等时优先比较 HCost(避免路径过长)。
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 管理实体的路径生成和移动。
|
||||
/// 管理实体的路径生成和移动。
|
||||
/// </summary>
|
||||
public class EntityPathManager
|
||||
{
|
||||
|
||||
public bool IsPathComplete => _currentPath == null || _currentPath.Count == 0 || _pathIndex >= _currentPath.Count;
|
||||
|
||||
private const float HEURISTIC_MULTIPLIER = 1.0f; // 启发式乘数
|
||||
private const float IMPASSABLE_TRAVEL_COST_THRESHOLD = 1.0f; // 不可通行成本阈值
|
||||
|
||||
|
||||
private readonly Entity _entity; // 标记为 readonly
|
||||
private List<Vector2> _currentPath;
|
||||
private int _pathIndex;
|
||||
|
||||
/// <summary>
|
||||
/// 最小优先队列实现,用于A*算法的OpenSet。
|
||||
/// 构造一个新的EntityPathManager实例。
|
||||
/// </summary>
|
||||
/// <param name="entity">要管理路径的实体。</param>
|
||||
public EntityPathManager(Entity entity)
|
||||
{
|
||||
_entity = entity;
|
||||
_currentPath = new List<Vector2>();
|
||||
_pathIndex = 0;
|
||||
}
|
||||
|
||||
public bool IsPathComplete =>
|
||||
_currentPath == null || _currentPath.Count == 0 || _pathIndex >= _currentPath.Count;
|
||||
|
||||
/// <summary>
|
||||
/// 生成从实体当前位置到目标坐标的路径。
|
||||
/// </summary>
|
||||
/// <param name="targetCoordinate">目标世界坐标。</param>
|
||||
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<PathNode>(); // 使用优先队列
|
||||
var allNodes = new Dictionary<Vector3Int, PathNode>(); // 用于快速查找已创建的节点
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取实体下一帧应该移动到的位置。
|
||||
/// </summary>
|
||||
/// <param name="currentPosition">实体当前的世界坐标。</param>
|
||||
/// <param name="moveSpeed">实体的移动速度(单位:世界单位/秒)。</param>
|
||||
/// <returns>下一帧实体的位置。</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
// 【新增函数】获取实体下一帧的方向
|
||||
/// <summary>
|
||||
/// 获取实体应该移动的方向(2D)。
|
||||
/// </summary>
|
||||
/// <param name="currentPosition">实体的当前世界坐标。</param>
|
||||
/// <returns>标准化后的移动方向Vector2,如果没有路径或路径完成则返回Vector2.zero。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算从当前节点到目标节点的启发式代价。
|
||||
/// </summary>
|
||||
/// <param name="currentWorldPosition">当前世界坐标。</param>
|
||||
/// <param name="targetWorldPosition">目标世界坐标。</param>
|
||||
/// <returns>启发式代价。</returns>
|
||||
private float CalculateHeuristic(Vector3 currentWorldPosition, Vector2 targetWorldPosition)
|
||||
{
|
||||
return Vector2.Distance(new Vector2(currentWorldPosition.x, currentWorldPosition.y), targetWorldPosition) *
|
||||
HEURISTIC_MULTIPLIER;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从终点回溯并重建路径。
|
||||
/// </summary>
|
||||
/// <param name="endNode">路径的终点节点。</param>
|
||||
/// <returns>世界坐标点的路径列表。</returns>
|
||||
private List<Vector2> ReconstructPath(PathNode endNode)
|
||||
{
|
||||
var path = new List<Vector2>();
|
||||
var currentNode = endNode;
|
||||
while (currentNode != null)
|
||||
{
|
||||
path.Add(new Vector2(currentNode.WorldCoordinates.x, currentNode.WorldCoordinates.y));
|
||||
currentNode = currentNode.Parent;
|
||||
}
|
||||
|
||||
path.Reverse(); // 将路径反转,使其从起点到终点
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 最小优先队列实现,用于A*算法的OpenSet。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">队列中存储的元素类型,必须实现 IComparableT。</typeparam>
|
||||
private class PriorityQueue<T> where T : IComparable<T>
|
||||
{
|
||||
private readonly List<T> data; // 存储堆元素的列表
|
||||
|
||||
public int Count => data.Count; // 队列中的元素数量
|
||||
|
||||
public PriorityQueue()
|
||||
{
|
||||
data = new List<T>();
|
||||
}
|
||||
|
||||
public int Count => data.Count; // 队列中的元素数量
|
||||
|
||||
/// <summary>
|
||||
/// 将元素添加到队列中。
|
||||
/// 将元素添加到队列中。
|
||||
/// </summary>
|
||||
/// <param name="item">要添加的元素。</param>
|
||||
public void Enqueue(T item)
|
||||
@@ -131,7 +381,7 @@ namespace Entity
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除并返回队列中最小的元素。
|
||||
/// 移除并返回队列中最小的元素。
|
||||
/// </summary>
|
||||
/// <returns>最小的元素。</returns>
|
||||
public T Dequeue()
|
||||
@@ -158,284 +408,11 @@ namespace Entity
|
||||
(data[pi], data[ci]) = (data[ci], data[pi]);
|
||||
pi = ci;
|
||||
}
|
||||
|
||||
return frontItem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private readonly Entity _entity; // 标记为 readonly
|
||||
private List<Vector2> _currentPath;
|
||||
private int _pathIndex;
|
||||
|
||||
private const float HEURISTIC_MULTIPLIER = 1.0f; // 启发式乘数
|
||||
private const float IMPASSABLE_TRAVEL_COST_THRESHOLD = 1.0f; // 不可通行成本阈值
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的EntityPathManager实例。
|
||||
/// </summary>
|
||||
/// <param name="entity">要管理路径的实体。</param>
|
||||
public EntityPathManager(Entity entity)
|
||||
{
|
||||
_entity = entity;
|
||||
_currentPath = new List<Vector2>();
|
||||
_pathIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成从实体当前位置到目标坐标的路径。
|
||||
/// </summary>
|
||||
/// <param name="targetCoordinate">目标世界坐标。</param>
|
||||
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<PathNode>(); // 使用优先队列
|
||||
var allNodes = new Dictionary<Vector3Int, PathNode>(); // 用于快速查找已创建的节点
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取实体下一帧应该移动到的位置。
|
||||
/// </summary>
|
||||
/// <param name="currentPosition">实体当前的世界坐标。</param>
|
||||
/// <param name="moveSpeed">实体的移动速度(单位:世界单位/秒)。</param>
|
||||
/// <returns>下一帧实体的位置。</returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 【新增函数】获取实体下一帧的方向
|
||||
/// <summary>
|
||||
/// 获取实体应该移动的方向(2D)。
|
||||
/// </summary>
|
||||
/// <param name="currentPosition">实体的当前世界坐标。</param>
|
||||
/// <returns>标准化后的移动方向Vector2,如果没有路径或路径完成则返回Vector2.zero。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算从当前节点到目标节点的启发式代价。
|
||||
/// </summary>
|
||||
/// <param name="currentWorldPosition">当前世界坐标。</param>
|
||||
/// <param name="targetWorldPosition">目标世界坐标。</param>
|
||||
/// <returns>启发式代价。</returns>
|
||||
private float CalculateHeuristic(Vector3 currentWorldPosition, Vector2 targetWorldPosition)
|
||||
{
|
||||
return Vector2.Distance(new Vector2(currentWorldPosition.x, currentWorldPosition.y), targetWorldPosition) *
|
||||
HEURISTIC_MULTIPLIER;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从终点回溯并重建路径。
|
||||
/// </summary>
|
||||
/// <param name="endNode">路径的终点节点。</param>
|
||||
/// <returns>世界坐标点的路径列表。</returns>
|
||||
private List<Vector2> ReconstructPath(PathNode endNode)
|
||||
{
|
||||
var path = new List<Vector2>();
|
||||
var currentNode = endNode;
|
||||
while (currentNode != null)
|
||||
{
|
||||
path.Add(new Vector2(currentNode.WorldCoordinates.x, currentNode.WorldCoordinates.y));
|
||||
currentNode = currentNode.Parent;
|
||||
}
|
||||
|
||||
path.Reverse(); // 将路径反转,使其从起点到终点
|
||||
return path;
|
||||
}
|
||||
|
||||
// 移除了 GetSmoothTravelCost 方法
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user