Files
Gen_Hack-and-Slash-Roguelit…/Client/Assets/Scripts/Utils/Pathfinder.cs

183 lines
6.1 KiB
C#
Raw Normal View History

using System.Collections.Generic;
using UnityEngine;
namespace Utils
{
public static class Pathfinder
{
public static List<Vector2> FindPath(Entity.Entity entity, Vector2 target, float maxDistance)
{
Vector2 start = entity.Position;
// 计算起点和终点所在的瓦片坐标
var startTile = GetTileCoord(start);
var endTile = GetTileCoord(target);
// 如果超出最大距离,直接返回直线路径或空路径
if (Vector2.Distance(start, target) > maxDistance)
{
return new List<Vector2> { start, target };
}
// A*算法数据结构
var cameFrom = new Dictionary<Vector2Int, Vector2Int>();
var gScore = new Dictionary<Vector2Int, float>();
var fScore = new Dictionary<Vector2Int, float>();
var openSet = new List<Vector2Int>();
// 初始化
gScore[startTile] = 0;
fScore[startTile] = Heuristic(startTile, endTile);
openSet.Add(startTile);
var closestNode = startTile;
var closestDist = Vector2.Distance(start, target);
while (openSet.Count > 0)
{
// 获取fScore最小的节点
var current = openSet[0];
foreach (var node in openSet)
{
if (fScore.GetValueOrDefault(node, float.MaxValue) <
fScore.GetValueOrDefault(current, float.MaxValue))
{
current = node;
}
}
// 检查是否到达目标
if (current == endTile)
{
return ReconstructPath(cameFrom, current, start, target);
}
openSet.Remove(current);
// 检查最大距离限制
var currentDist = Vector2.Distance(
new Vector2(current.x, current.y),
target);
if (currentDist < closestDist)
{
closestDist = currentDist;
closestNode = current;
}
if (gScore[current] > maxDistance)
{
return ReconstructPath(cameFrom, closestNode, start, target);
}
// 遍历邻居8方向
for (var dx = -1; dx <= 1; dx++)
{
for (var dy = -1; dy <= 1; dy++)
{
if (dx == 0 && dy == 0) continue;
var neighbor = new Vector2Int(current.x + dx, current.y + dy);
// 跳过不可通行区域
if (!Map.MapGenerator.Instance.CanPassThrough(neighbor.x, neighbor.y))
{
continue;
}
// 计算移动成本
var moveCost = GetMovementCost(current, neighbor);
var tentativeGScore = gScore[current] + moveCost;
// 跳过超出最大距离的路径
if (tentativeGScore > maxDistance)
{
continue;
}
// 发现新节点或找到更好路径
if (tentativeGScore < gScore.GetValueOrDefault(neighbor, float.MaxValue))
{
cameFrom[neighbor] = current;
gScore[neighbor] = tentativeGScore;
fScore[neighbor] = tentativeGScore + Heuristic(neighbor, endTile);
if (!openSet.Contains(neighbor))
{
openSet.Add(neighbor);
}
}
}
}
}
// 无法找到完整路径时返回局部最优解
return ReconstructPath(cameFrom, closestNode, start, target);
}
// 获取瓦片坐标每个瓦片覆盖±0.5范围)
private static Vector2Int GetTileCoord(Vector2 position)
{
return new Vector2Int(
Mathf.RoundToInt(position.x),
Mathf.RoundToInt(position.y)
);
}
// 计算启发式估值(欧几里得距离)
private static float Heuristic(Vector2Int a, Vector2Int b)
{
return Vector2.Distance(
new Vector2(a.x, a.y),
new Vector2(b.x, b.y)
);
}
// 获取移动成本
private static float GetMovementCost(Vector2Int from, Vector2Int to)
{
// 计算基础距离(正交=1对角=√2
var distance = (from.x == to.x || from.y == to.y) ? 1f : 1.4142f;
// 应用目标瓦片的速度削减率
var costModifier = Map.MapGenerator.Instance.GetTileCost(to.x, to.y);
// 成本 = 距离 × (1 + 速度削减率)
return distance * (1 + costModifier);
}
// 重建路径
private static List<Vector2> ReconstructPath(
Dictionary<Vector2Int, Vector2Int> cameFrom,
Vector2Int current,
Vector2 start,
Vector2 end)
{
// 构建瓦片路径
var tilePath = new List<Vector2Int>();
tilePath.Add(current);
while (cameFrom.ContainsKey(current))
{
current = cameFrom[current];
tilePath.Add(current);
}
tilePath.Reverse();
// 转换为实际坐标路径
var path = new List<Vector2>();
path.Add(start); // 添加精确起点
// 添加路径点(瓦片中心)
foreach (var tile in tilePath)
{
path.Add(new Vector2(tile.x, tile.y));
}
path.Add(end); // 添加精确终点
return path;
}
}
}