Files
MRFishingMaster/Assets/_FishingMaster/Scripts/Fish/Base/FishTideController.cs

361 lines
12 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;
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;
}
}