361 lines
12 KiB
C#
361 lines
12 KiB
C#
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
/// <summary>
|
||
/// 鱼潮来袭控制器 - 不使用CurvySpline,直接控制鱼的移动
|
||
/// 从玩家右前方移动到左前方,彩虹弧度路径
|
||
/// 持续1分钟,分批生成鱼
|
||
/// </summary>
|
||
public class FishTideController : MonoBehaviour
|
||
{
|
||
[Header("路径配置")]
|
||
[Tooltip("起始点偏移(玩家右前方)")]
|
||
public Vector3 startOffset = new Vector3(8f, 3f, 8f);
|
||
|
||
[Tooltip("结束点偏移(玩家左前方)")]
|
||
public Vector3 endOffset = new Vector3(-8f, 3f, 8f);
|
||
|
||
[Tooltip("彩虹弧度高度")]
|
||
public float arcHeight = 8f;
|
||
|
||
[Header("移动配置")]
|
||
[Tooltip("鱼移动速度(固定)")]
|
||
public float moveSpeed = 0.8f;
|
||
|
||
[Tooltip("鱼是否始终面向前方")]
|
||
public bool alwaysFaceForward = true;
|
||
|
||
[Header("生成配置")]
|
||
[Tooltip("每条线路的鱼数量")]
|
||
public int fishCount = 30;
|
||
|
||
[Tooltip("每条鱼生成间隔(秒)")]
|
||
public float spawnInterval = 1.5f;
|
||
|
||
[Tooltip("线路垂直间距")]
|
||
public float laneVerticalSpacing = 1.5f;
|
||
|
||
[Tooltip("鱼潮持续时间(秒)")]
|
||
public float fishTideDuration = 60f;
|
||
|
||
private List<FishTideInfo> tideFishes = new List<FishTideInfo>();
|
||
private List<FishTideInfo> activeFishes = new List<FishTideInfo>(); // 正在游动的鱼
|
||
private Vector3 startPoint;
|
||
private Vector3 endPoint;
|
||
private Vector3 controlPoint; // 贝塞尔曲线控制点
|
||
private int laneIndex; // 线路索引
|
||
private float totalPathLength;
|
||
private Transform playerTransform;
|
||
|
||
// 生成控制
|
||
private int spawnedCount = 0;
|
||
private float spawnTimer = 0f;
|
||
private float tideTimer = 0f;
|
||
private bool isSpawning = false;
|
||
private bool isTideActive = false;
|
||
|
||
/// <summary>
|
||
/// 存储鱼潮中鱼的信息
|
||
/// </summary>
|
||
private class FishTideInfo
|
||
{
|
||
public Fish fish; // Fish组件(用于捕捉)
|
||
public Transform transform; // Transform(用于移动)
|
||
public float currentT; // 当前在贝塞尔曲线上的位置(0-1)
|
||
public Vector3 baseScale; // 基础缩放
|
||
public float swimOffset; // 游动动画偏移
|
||
public bool hasStarted; // 是否已经开始移动
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化鱼潮线路
|
||
/// </summary>
|
||
public void Init(int laneIdx, int totalLanes)
|
||
{
|
||
laneIndex = laneIdx;
|
||
playerTransform = GameInit.Ins.playerCam.transform;
|
||
|
||
// 计算路径点(基于玩家位置)
|
||
CalculatePathPoints(totalLanes);
|
||
|
||
// 开始生成鱼
|
||
isSpawning = true;
|
||
isTideActive = true;
|
||
}
|
||
|
||
void CalculatePathPoints(int totalLanes)
|
||
{
|
||
Vector3 playerPos = playerTransform.position;
|
||
Vector3 playerForward = playerTransform.forward;
|
||
Vector3 playerRight = playerTransform.right;
|
||
|
||
// 基础起始点和结束点(右前方到左前方)- 路线更长
|
||
Vector3 baseStart = playerPos + playerForward * startOffset.z + playerRight * startOffset.x;
|
||
Vector3 baseEnd = playerPos + playerForward * endOffset.z + playerRight * endOffset.x;
|
||
|
||
// 根据线路索引添加垂直偏移(上下分布,形成彩虹效果)
|
||
float verticalOffset = (laneIndex - (totalLanes - 1) * 0.5f) * laneVerticalSpacing;
|
||
|
||
startPoint = baseStart + Vector3.up * verticalOffset;
|
||
endPoint = baseEnd + Vector3.up * verticalOffset;
|
||
|
||
// 计算贝塞尔曲线控制点(形成彩虹弧度)
|
||
Vector3 midPoint = (startPoint + endPoint) * 0.5f;
|
||
// 控制点在中间上方,形成拱形
|
||
controlPoint = midPoint + Vector3.up * arcHeight + playerForward * (startOffset.z + endOffset.z) * 0.15f;
|
||
|
||
// 计算路径总长度(近似)
|
||
totalPathLength = ApproximateBezierLength(startPoint, controlPoint, endPoint, 30);
|
||
|
||
Debug.Log($"[FishTide] Lane {laneIndex}: Path length = {totalPathLength:F2}, Start={startPoint}, End={endPoint}");
|
||
}
|
||
|
||
float ApproximateBezierLength(Vector3 p0, Vector3 p1, Vector3 p2, int segments)
|
||
{
|
||
float length = 0f;
|
||
Vector3 prevPoint = p0;
|
||
|
||
for (int i = 1; i <= segments; i++)
|
||
{
|
||
float t = i / (float)segments;
|
||
Vector3 point = QuadraticBezier(p0, p1, p2, t);
|
||
length += Vector3.Distance(prevPoint, point);
|
||
prevPoint = point;
|
||
}
|
||
|
||
return length;
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
if (!isTideActive) return;
|
||
|
||
tideTimer += Time.deltaTime;
|
||
|
||
// 生成阶段 - 在持续时间内分批生成鱼
|
||
if (isSpawning)
|
||
{
|
||
spawnTimer += Time.deltaTime;
|
||
|
||
// 使用配置的生成间隔
|
||
if (spawnTimer >= spawnInterval && spawnedCount < fishCount)
|
||
{
|
||
SpawnOneFish();
|
||
spawnTimer = 0f;
|
||
}
|
||
|
||
// 生成完毕或时间到了,停止生成
|
||
if (spawnedCount >= fishCount || tideTimer >= fishTideDuration)
|
||
{
|
||
isSpawning = false;
|
||
Debug.Log($"[FishTide] Lane {laneIndex} 生成完毕,共生成 {spawnedCount} 条鱼");
|
||
}
|
||
}
|
||
|
||
// 更新所有活跃鱼的移动
|
||
UpdateFishMovement();
|
||
|
||
// 检查鱼潮是否结束(所有鱼都已游完或被捕捉)
|
||
if (!isSpawning && activeFishes.Count == 0 && tideFishes.Count == 0)
|
||
{
|
||
isTideActive = false;
|
||
Destroy(gameObject);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取鱼潮随机鱼数据 - 低价值鱼 + 除海龟外的中价值鱼
|
||
/// </summary>
|
||
FishData GetRandomFishTideFish()
|
||
{
|
||
List<FishData> validFishes = new List<FishData>();
|
||
|
||
foreach (var item in GameManager.Ins.FishDataDic)
|
||
{
|
||
FishData data = item.Value;
|
||
FishType fishType = (FishType)data.FishId;
|
||
FishValueLevel level = FishValueHelper.GetValueLevel(data);
|
||
|
||
// 低价值鱼全部加入
|
||
if (level == FishValueLevel.Low)
|
||
{
|
||
validFishes.Add(data);
|
||
}
|
||
// 中价值鱼但排除海龟
|
||
else if (level == FishValueLevel.Middle && fishType != FishType.Turtle)
|
||
{
|
||
validFishes.Add(data);
|
||
}
|
||
}
|
||
|
||
if (validFishes.Count > 0)
|
||
{
|
||
return validFishes[Random.Range(0, validFishes.Count)];
|
||
}
|
||
|
||
// 如果没有符合条件的鱼,返回任意鱼
|
||
foreach (var item in GameManager.Ins.FishDataDic)
|
||
{
|
||
return item.Value;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
void SpawnOneFish()
|
||
{
|
||
// 随机获取一条鱼(低价值鱼 + 除海龟外的中价值鱼)
|
||
FishData fishData = GetRandomFishTideFish();
|
||
if (fishData == null)
|
||
{
|
||
Debug.LogError("[FishTide] 无法获取鱼数据!");
|
||
return;
|
||
}
|
||
|
||
// 在起点生成鱼
|
||
Vector3 spawnPos = startPoint;
|
||
|
||
// 实例化鱼
|
||
GameObject fishGO = Instantiate(GameManager.Ins.fishPres[fishData.FishId - 1], spawnPos, Quaternion.identity);
|
||
fishGO.transform.SetParent(transform);
|
||
|
||
// 获取Fish组件(用于捕捉功能)
|
||
Fish fishComponent = fishGO.GetComponent<Fish>();
|
||
if (fishComponent != null)
|
||
{
|
||
// 使用反射设置Data(因为Data是private set)
|
||
typeof(Fish).GetProperty("Data")?.SetValue(fishComponent, fishData);
|
||
|
||
// 禁用SplineController(如果存在)
|
||
var splineCtrl = fishGO.GetComponent<FluffyUnderware.Curvy.Controllers.SplineController>();
|
||
if (splineCtrl != null)
|
||
{
|
||
splineCtrl.enabled = false;
|
||
}
|
||
|
||
// 注册到FishSpawner(使其可以被捕捉)
|
||
FishSpawner.Instance?.RegisterFish(fishComponent);
|
||
}
|
||
|
||
// 创建鱼潮信息 - 使用统一固定速度
|
||
FishTideInfo info = new FishTideInfo
|
||
{
|
||
fish = fishComponent,
|
||
transform = fishGO.transform,
|
||
currentT = 1f, // 从起点开始(t=1是起点)
|
||
baseScale = fishGO.transform.localScale,
|
||
swimOffset = Random.Range(0f, Mathf.PI * 2f),
|
||
hasStarted = true
|
||
};
|
||
|
||
// 设置初始朝向(面向移动方向,取反切线方向以修复朝向)
|
||
Vector3 tangent = GetBezierTangent(startPoint, controlPoint, endPoint, 1f);
|
||
if (tangent != Vector3.zero)
|
||
{
|
||
// 取反切线方向,让鱼面向正确的移动方向
|
||
info.transform.rotation = Quaternion.LookRotation(-tangent);
|
||
}
|
||
|
||
tideFishes.Add(info);
|
||
activeFishes.Add(info);
|
||
spawnedCount++;
|
||
}
|
||
|
||
void UpdateFishMovement()
|
||
{
|
||
for (int i = activeFishes.Count - 1; i >= 0; i--)
|
||
{
|
||
var info = activeFishes[i];
|
||
|
||
// 如果鱼已经被捕捉或销毁,从列表移除
|
||
if (info.fish == null || info.fish.state != FishState.Alive)
|
||
{
|
||
activeFishes.RemoveAt(i);
|
||
tideFishes.Remove(info);
|
||
continue;
|
||
}
|
||
|
||
// 沿贝塞尔曲线移动(t从1到0,从起点到终点)- 使用统一速度
|
||
float distanceThisFrame = moveSpeed * Time.deltaTime;
|
||
float tDelta = distanceThisFrame / totalPathLength;
|
||
|
||
info.currentT -= tDelta;
|
||
info.currentT = Mathf.Clamp01(info.currentT);
|
||
|
||
// 计算位置
|
||
Vector3 position = QuadraticBezier(startPoint, controlPoint, endPoint, info.currentT);
|
||
info.transform.position = position;
|
||
|
||
// 计算朝向 - 始终面向前方(移动方向)
|
||
Vector3 tangent = GetBezierTangent(startPoint, controlPoint, endPoint, info.currentT);
|
||
if (tangent != Vector3.zero && alwaysFaceForward)
|
||
{
|
||
// 取反切线方向,让鱼面向正确的移动方向
|
||
Quaternion targetRotation = Quaternion.LookRotation(-tangent);
|
||
info.transform.rotation = Quaternion.Slerp(info.transform.rotation, targetRotation, Time.deltaTime * 5f);
|
||
}
|
||
|
||
// 添加轻微摆动动画
|
||
float swimAnim = Mathf.Sin(Time.time * 8f + info.swimOffset) * 0.03f;
|
||
info.transform.localScale = info.baseScale * (1f + swimAnim);
|
||
|
||
// 到达终点(t接近0)
|
||
if (info.currentT <= 0.01f)
|
||
{
|
||
OnFishReachEnd(info);
|
||
activeFishes.RemoveAt(i);
|
||
tideFishes.Remove(info);
|
||
}
|
||
}
|
||
}
|
||
|
||
void OnFishReachEnd(FishTideInfo info)
|
||
{
|
||
// 注销并销毁鱼
|
||
if (info.fish != null)
|
||
{
|
||
FishSpawner.Instance?.UnregisterFish(info.fish);
|
||
Destroy(info.fish.gameObject);
|
||
}
|
||
}
|
||
|
||
void OnDestroy()
|
||
{
|
||
// 清理剩余的鱼
|
||
foreach (var info in tideFishes)
|
||
{
|
||
if (info.fish != null)
|
||
{
|
||
FishSpawner.Instance?.UnregisterFish(info.fish);
|
||
if (info.fish.gameObject != null)
|
||
{
|
||
Destroy(info.fish.gameObject);
|
||
}
|
||
}
|
||
}
|
||
tideFishes.Clear();
|
||
activeFishes.Clear();
|
||
|
||
// 通知FishSpawner鱼潮结束
|
||
FishSpawner.Instance?.OnFishTideLaneComplete();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 二次贝塞尔曲线计算
|
||
/// </summary>
|
||
public static Vector3 QuadraticBezier(Vector3 p0, Vector3 p1, Vector3 p2, float t)
|
||
{
|
||
float u = 1f - t;
|
||
return u * u * p0 + 2f * u * t * p1 + t * t * p2;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取贝塞尔曲线上的切线方向(用于鱼的朝向)
|
||
/// </summary>
|
||
public static Vector3 GetBezierTangent(Vector3 p0, Vector3 p1, Vector3 p2, float t)
|
||
{
|
||
float u = 1f - t;
|
||
return (2f * u * (p1 - p0) + 2f * t * (p2 - p1)).normalized;
|
||
}
|
||
} |