183 lines
6.1 KiB
C#
183 lines
6.1 KiB
C#
![]() |
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;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|