388 lines
11 KiB
C#
388 lines
11 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using Mirror;
|
|
|
|
public class ArrowPathIndicator : NetworkBehaviour
|
|
{
|
|
[Header("箭头设置")]
|
|
public GameObject arrowPrefab; // 单个箭头预制体
|
|
public int maxArrows = 20; // 最大箭头数量
|
|
public float arrowSpacing = 2.0f; // 箭头间距
|
|
public float arrowHeight = 1.5f; // 箭头高度
|
|
public LayerMask obstacleMask = 1; // 障碍物层
|
|
|
|
[Header("路径平滑")]
|
|
public int pathSmoothingIterations = 5; // 路径平滑迭代次数
|
|
public float smoothFactor = 0.5f; // 平滑因子
|
|
|
|
[Header("动画设置")]
|
|
public float arrowMoveSpeed = 1.0f; // 箭头移动速度
|
|
public float arrowBobHeight = 0.2f; // 箭头上下浮动高度
|
|
public float arrowBobSpeed = 2.0f; // 箭头浮动速度
|
|
|
|
// 内部变量
|
|
private List<GameObject> arrowInstances = new List<GameObject>();
|
|
private List<Vector3> currentPath = new List<Vector3>();
|
|
private List<Vector3> smoothedPath = new List<Vector3>();
|
|
private Vector3 lastStartPoint = Vector3.zero;
|
|
private Vector3 lastEndPoint = Vector3.zero;
|
|
private float updateCooldown = 0.5f;
|
|
private float lastUpdateTime = 0f;
|
|
private float arrowBobOffset = 0f;
|
|
|
|
void Awake()
|
|
{
|
|
// 初始化箭头池
|
|
InitializeArrowPool();
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
// 更新箭头浮动动画
|
|
if (arrowInstances.Count > 0)
|
|
{
|
|
UpdateArrowAnimation();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 初始化箭头对象池
|
|
/// </summary>
|
|
private void InitializeArrowPool()
|
|
{
|
|
// 清除现有箭头
|
|
foreach (GameObject arrow in arrowInstances)
|
|
{
|
|
if (arrow != null)
|
|
Destroy(arrow);
|
|
}
|
|
arrowInstances.Clear();
|
|
|
|
// 创建新的箭头池
|
|
for (int i = 0; i < maxArrows; i++)
|
|
{
|
|
if (arrowPrefab != null)
|
|
{
|
|
GameObject arrow = Instantiate(arrowPrefab, transform);
|
|
arrow.SetActive(false);
|
|
arrowInstances.Add(arrow);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置路径
|
|
/// </summary>
|
|
public void SetPath(Vector3 start, Vector3 end)
|
|
{
|
|
// 检查更新冷却
|
|
if (Time.time - lastUpdateTime < updateCooldown &&
|
|
Vector3.Distance(start, lastStartPoint) < 1.0f &&
|
|
Vector3.Distance(end, lastEndPoint) < 1.0f)
|
|
{
|
|
return;
|
|
}
|
|
|
|
lastStartPoint = start;
|
|
lastEndPoint = end;
|
|
lastUpdateTime = Time.time;
|
|
|
|
// 计算路径
|
|
CalculatePath(start, end);
|
|
|
|
// 平滑路径
|
|
smoothedPath = SmoothPath(currentPath);
|
|
|
|
// 更新箭头显示
|
|
UpdateArrowsOnPath();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算路径
|
|
/// </summary>
|
|
private void CalculatePath(Vector3 start, Vector3 end)
|
|
{
|
|
currentPath.Clear();
|
|
currentPath.Add(start);
|
|
|
|
// 检查直接路径是否畅通
|
|
if (IsDirectPathClear(start, end))
|
|
{
|
|
// 直接路径,添加中间点
|
|
AddPointsAlongLine(start, end, arrowSpacing);
|
|
}
|
|
else
|
|
{
|
|
// 需要绕行,寻找绕过点
|
|
Vector3 bypassPoint = FindBypassPoint(start, end);
|
|
|
|
if (bypassPoint != start)
|
|
{
|
|
// 从起点到绕过点
|
|
AddPointsAlongLine(start, bypassPoint, arrowSpacing);
|
|
// 从绕过点到终点
|
|
AddPointsAlongLine(bypassPoint, end, arrowSpacing);
|
|
}
|
|
else
|
|
{
|
|
// 找不到绕过点,使用直线
|
|
AddPointsAlongLine(start, end, arrowSpacing);
|
|
}
|
|
}
|
|
|
|
currentPath.Add(end);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查直接路径是否畅通
|
|
/// </summary>
|
|
private bool IsDirectPathClear(Vector3 start, Vector3 end)
|
|
{
|
|
Vector3 direction = (end - start).normalized;
|
|
float distance = Vector3.Distance(start, end);
|
|
|
|
// 分段检查
|
|
int segments = Mathf.CeilToInt(distance / 1.0f);
|
|
for (int i = 0; i <= segments; i++)
|
|
{
|
|
float t = (float)i / segments;
|
|
Vector3 checkPoint = Vector3.Lerp(start, end, t);
|
|
|
|
if (Physics.CheckSphere(checkPoint, 0.5f, obstacleMask))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 在直线上添加等间距点
|
|
/// </summary>
|
|
private void AddPointsAlongLine(Vector3 start, Vector3 end, float spacing)
|
|
{
|
|
Vector3 direction = (end - start).normalized;
|
|
float distance = Vector3.Distance(start, end);
|
|
int points = Mathf.FloorToInt(distance / spacing);
|
|
|
|
for (int i = 1; i <= points; i++)
|
|
{
|
|
float t = (float)i / (points + 1);
|
|
Vector3 point = Vector3.Lerp(start, end, t);
|
|
currentPath.Add(point);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 寻找绕过障碍物的点
|
|
/// </summary>
|
|
private Vector3 FindBypassPoint(Vector3 start, Vector3 end)
|
|
{
|
|
Vector3 direction = (end - start).normalized;
|
|
float distance = Vector3.Distance(start, end);
|
|
|
|
// 尝试多个方向
|
|
Vector3[] testDirections = {
|
|
Vector3.Cross(direction, Vector3.up).normalized, // 右侧
|
|
-Vector3.Cross(direction, Vector3.up).normalized, // 左侧
|
|
(direction + Vector3.up * 0.5f).normalized, // 前上方
|
|
(direction - Vector3.up * 0.5f).normalized // 前下方
|
|
};
|
|
|
|
float[] testDistances = { 2f, 3f, 4f };
|
|
|
|
foreach (Vector3 testDir in testDirections)
|
|
{
|
|
foreach (float testDist in testDistances)
|
|
{
|
|
Vector3 testPoint = start + testDir * testDist;
|
|
|
|
// 检查测试点是否可行
|
|
if (!Physics.CheckSphere(testPoint, 0.5f, obstacleMask))
|
|
{
|
|
// 检查从起点到测试点,以及从测试点到终点是否都畅通
|
|
if (IsDirectPathClear(start, testPoint) &&
|
|
IsDirectPathClear(testPoint, end))
|
|
{
|
|
return testPoint;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return start; // 没找到绕过点
|
|
}
|
|
|
|
/// <summary>
|
|
/// 平滑路径
|
|
/// </summary>
|
|
private List<Vector3> SmoothPath(List<Vector3> path)
|
|
{
|
|
if (path.Count < 3)
|
|
return new List<Vector3>(path);
|
|
|
|
List<Vector3> smoothed = new List<Vector3>(path);
|
|
|
|
for (int iteration = 0; iteration < pathSmoothingIterations; iteration++)
|
|
{
|
|
List<Vector3> newPath = new List<Vector3>();
|
|
newPath.Add(smoothed[0]); // 保持起点不变
|
|
|
|
// 对中间点进行平滑
|
|
for (int i = 1; i < smoothed.Count - 1; i++)
|
|
{
|
|
Vector3 prev = smoothed[i - 1];
|
|
Vector3 current = smoothed[i];
|
|
Vector3 next = smoothed[i + 1];
|
|
|
|
// 使用加权平均进行平滑
|
|
Vector3 smoothedPoint = current * (1 - smoothFactor * 2) +
|
|
(prev + next) * smoothFactor;
|
|
newPath.Add(smoothedPoint);
|
|
}
|
|
|
|
newPath.Add(smoothed[smoothed.Count - 1]); // 保持终点不变
|
|
smoothed = newPath;
|
|
}
|
|
|
|
return smoothed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 更新路径上的箭头
|
|
/// </summary>
|
|
private void UpdateArrowsOnPath()
|
|
{
|
|
// 隐藏所有箭头
|
|
foreach (GameObject arrow in arrowInstances)
|
|
{
|
|
if (arrow != null)
|
|
arrow.SetActive(false);
|
|
}
|
|
|
|
if (smoothedPath.Count < 2)
|
|
return;
|
|
|
|
// 计算需要在哪些位置显示箭头
|
|
List<Vector3> arrowPositions = new List<Vector3>();
|
|
|
|
// 沿路径等间距取样
|
|
float accumulatedDistance = 0f;
|
|
for (int i = 0; i < smoothedPath.Count - 1; i++)
|
|
{
|
|
Vector3 segmentStart = smoothedPath[i];
|
|
Vector3 segmentEnd = smoothedPath[i + 1];
|
|
float segmentLength = Vector3.Distance(segmentStart, segmentEnd);
|
|
|
|
// 在当前段内放置箭头
|
|
while (accumulatedDistance < segmentLength)
|
|
{
|
|
float t = accumulatedDistance / segmentLength;
|
|
Vector3 arrowPos = Vector3.Lerp(segmentStart, segmentEnd, t);
|
|
arrowPositions.Add(arrowPos);
|
|
|
|
accumulatedDistance += arrowSpacing;
|
|
}
|
|
|
|
accumulatedDistance -= segmentLength;
|
|
}
|
|
|
|
// 限制箭头数量
|
|
int arrowCount = Mathf.Min(arrowPositions.Count, maxArrows);
|
|
|
|
// 激活并放置箭头
|
|
for (int i = 0; i < arrowCount; i++)
|
|
{
|
|
if (i < arrowInstances.Count)
|
|
{
|
|
GameObject arrow = arrowInstances[i];
|
|
|
|
// 设置位置
|
|
Vector3 arrowPos = arrowPositions[i];
|
|
arrowPos.y = arrowHeight;
|
|
arrow.transform.position = arrowPos;
|
|
|
|
// 设置旋转(朝向路径方向)
|
|
if (i < arrowPositions.Count - 1)
|
|
{
|
|
Vector3 lookDirection = (arrowPositions[i + 1] - arrowPositions[i]).normalized;
|
|
if (lookDirection.magnitude > 0.01f)
|
|
{
|
|
Quaternion lookRotation = Quaternion.LookRotation(lookDirection);
|
|
arrow.transform.rotation = lookRotation;
|
|
}
|
|
}
|
|
else if (i > 0)
|
|
{
|
|
// 最后一个箭头使用前一个方向
|
|
Vector3 lookDirection = (arrowPositions[i] - arrowPositions[i - 1]).normalized;
|
|
if (lookDirection.magnitude > 0.01f)
|
|
{
|
|
Quaternion lookRotation = Quaternion.LookRotation(lookDirection);
|
|
arrow.transform.rotation = lookRotation;
|
|
}
|
|
}
|
|
|
|
arrow.SetActive(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 更新箭头动画
|
|
/// </summary>
|
|
private void UpdateArrowAnimation()
|
|
{
|
|
arrowBobOffset += Time.deltaTime * arrowBobSpeed;
|
|
|
|
for (int i = 0; i < arrowInstances.Count; i++)
|
|
{
|
|
GameObject arrow = arrowInstances[i];
|
|
if (arrow != null && arrow.activeSelf)
|
|
{
|
|
// 上下浮动
|
|
Vector3 pos = arrow.transform.position;
|
|
float bob = Mathf.Sin(arrowBobOffset + i * 0.5f) * arrowBobHeight;
|
|
pos.y = arrowHeight + bob;
|
|
arrow.transform.position = pos;
|
|
|
|
// 可选:添加旋转动画
|
|
arrow.transform.Rotate(0, arrowMoveSpeed * Time.deltaTime * 30f, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 显示指引
|
|
/// </summary>
|
|
public void ShowPath()
|
|
{
|
|
gameObject.SetActive(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 隐藏指引
|
|
/// </summary>
|
|
public void HidePath()
|
|
{
|
|
gameObject.SetActive(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 清理资源
|
|
/// </summary>
|
|
public void Cleanup()
|
|
{
|
|
foreach (GameObject arrow in arrowInstances)
|
|
{
|
|
if (arrow != null)
|
|
Destroy(arrow);
|
|
}
|
|
arrowInstances.Clear();
|
|
|
|
if (gameObject != null)
|
|
Destroy(gameObject);
|
|
}
|
|
} |