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

183 lines
6.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}
}