Files
MRFishingMaster/Assets/_FishingMaster/Scripts/Fish/Base/FishSpawner.cs
2026-02-26 16:43:29 +08:00

472 lines
11 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 DragonLi.Core;
using UnityEngine;
using FluffyUnderware.Curvy;
using Unity.VisualScripting;
public class FishSpawner : MonoBehaviour
{
public static FishSpawner Instance;
public readonly HashSet<Fish> AliveFishes = new HashSet<Fish>();
[Header("Spawn Rule")]
public float checkInterval = 1f;
[Header("Prefabs")]
public GameObject fishGroupPrefab;
public float fishSpeed;
public int maxFishCount;
private Dictionary<FishType, FishData> fishDataMap = new();
private Dictionary<FishType, FishSpawnState> spawnStates = new();
private float timer;
private float fishTideTimer;
// 高价值互斥锁
private bool highFishAlive;
private bool angelHorseAlive;
private const float MIX_MIDDLE_BASE_RATE = 0.3f;
void Awake()
{
Instance = this;
}
[Header("对象池")]
public FishObjectPool fishObjectPool;
#region Fish Register
public void RegisterFish(Fish fish)
{
if (fish == null) return;
AliveFishes.Add(fish);
}
public void UnregisterFish(Fish fish)
{
if (fish == null) return;
AliveFishes.Remove(fish);
}
#endregion
void Start()
{
fishSpeed = 1;
maxFishCount = 200;
// 初始化对象池
if (fishObjectPool != null)
{
fishObjectPool.InitializePools();
}
else
{
Debug.LogWarning("[FishSpawner] 未设置 FishObjectPool将直接实例化鱼");
}
}
void Update()
{
if(!GameManager.Ins.isGamePlay)
return;
if(GameManager.Ins.isGameEnd)
return;
timer += Time.deltaTime;
if (timer >= checkInterval&& !GameManager.Ins.isGameEnd)
{
timer = 0f;
TrySpawnFish();
}
if(!IsFishTide)
fishTideTimer += Time.deltaTime;
if (fishTideTimer >= 200&& !IsFishTide&& !GameManager.Ins.isGameEnd)
{
if (GameInit.Ins.self.handState == handState.Gun)
{
GameManager.Ins.StartFishTideEvent();
fishTideTimer = 0;
}
else
{
fishTideTimer = 0;
}
}
}
public void LoadFishData()
{
// 实际项目中从配置表加载
foreach (var item in GameManager.Ins.FishDataDic)
{
fishDataMap.Add((FishType)item.Key, item.Value);
}
foreach (var kv in fishDataMap)
spawnStates[kv.Key] = new FishSpawnState();
}
void TrySpawnFish()
{
if(IsFishTide)
return;
if(AliveFishes.Count>=maxFishCount)
return;
FishData data = GetFishByProbability();
if (data == null) return;
FishType type = (FishType)data.FishId;
FishValueLevel level = FishValueHelper.GetValueLevel(data);
// 特高价值唯一
if (level == FishValueLevel.UltraHigh && angelHorseAlive)
return;
// 高价值互斥
if (level == FishValueLevel.High && highFishAlive)
return;
FishSpawnState state = spawnStates[type];
if (Time.time < state.CooldownEndTime)
return;
int count = GetFishCount(data);
CurvySpline spline = GetSplineByType();
// 生成
if (count > 1)
{
if (level == FishValueLevel.Low)
SpawnLowValueGroup(data, count, spline, state);
else
SpawnNormalGroup(data, count, spline);
}
else
{
SpawnSingleFish(data, spline);
}
state.CooldownEndTime = Time.time + data.IntervalTime;
if (level == FishValueLevel.High)
highFishAlive = true;
if (level == FishValueLevel.UltraHigh)
angelHorseAlive = true;
}
#region Spawn Logic
void SpawnLowValueGroup(
FishData data,
int count,
CurvySpline spline,
FishSpawnState state)
{
List<FishData> list = new();
for (int i = 0; i < count; i++)
list.Add(data);
float rate = MIX_MIDDLE_BASE_RATE;
if (state.NoMiddleFishCount >= 3)
rate *= 2f;
bool hasMiddle = false;
if (Random.value < rate)
{
FishData middle = GetRandomMiddleValueFish();
if (middle != null)
{
list.Add(middle);
hasMiddle = true;
}
}
state.NoMiddleFishCount = hasMiddle ? 0 : state.NoMiddleFishCount + 1;
SpawnGroup(list, spline);
}
void SpawnNormalGroup(FishData data, int count, CurvySpline spline)
{
List<FishData> list = new();
for (int i = 0; i < count; i++)
list.Add(data);
SpawnGroup(list, spline);
}
void SpawnGroup(List<FishData> list, CurvySpline spline)
{
GameObject go = Instantiate(fishGroupPrefab);
go.GetComponent<FishGroup>().Init(list, spline);
}
void SpawnSingleFish(FishData data, CurvySpline spline)
{
GameObject go = null;
FishType fishType = (FishType)data.FishId;
// 尝试从对象池获取
if (fishObjectPool != null)
{
go = fishObjectPool.GetFish(fishType);
}
// 对象池未设置或获取失败,直接实例化
if (go == null)
{
go = Instantiate(GameManager.Ins.GetFishPre(data.FishId-1));
}
// 重置父对象(可能从对象池取出时带有父对象)
go.transform.SetParent(null);
go.GetComponent<Fish>().Init(data, spline);
}
public void CreateTypeFish(FishType fishType)
{
FishData data= fishDataMap[fishType];
CurvySpline spline = GetSplineByType();
SpawnSingleFish(data,spline);
FishValueLevel level = FishValueHelper.GetValueLevel(data);
if (level == FishValueLevel.High)
highFishAlive = true;
if (level == FishValueLevel.UltraHigh)
angelHorseAlive = true;
}
#endregion
#region Utils
FishData GetFishByProbability()
{
float total = 0;
foreach (var d in fishDataMap.Values)
total += d.Odds;
float r = Random.Range(0, total);
float cur = 0;
foreach (var d in fishDataMap.Values)
{
cur += d.Odds;
if (r <= cur)
return d;
}
return null;
}
int GetFishCount(FishData data)
{
if (data.FishCount == null || data.FishCount.Length < 2)
return 1;
return Random.Range(data.FishCount[0], data.FishCount[1] + 1);
}
CurvySpline GetSplineByType()
{
return GameInit.Ins.GetFishPath();
}
FishData GetRandomMiddleValueFish()
{
List<FishData> list = new();
foreach (var d in fishDataMap.Values)
{
if (FishValueHelper.GetValueLevel(d) == FishValueLevel.Middle)
list.Add(d);
}
return list.Count == 0 ? null : list[Random.Range(0, list.Count)];
}
public void OnHighFishRemoved(FishValueLevel level)
{
if (level == FishValueLevel.High)
highFishAlive = false;
if (level == FishValueLevel.UltraHigh)
angelHorseAlive = false;
}
#endregion
public int GetFishCount(FishType fishType)
{
int count = 0;
foreach (var item in AliveFishes)
{
if((FishType)item.Data.FishId==fishType)
count++;
}
return count;
}
public void SetFishSpeed(float curTime)
{
fishSpeed = 2;
foreach (var item in AliveFishes)
{
item.SetSpeed();
}
CoroutineTaskManager.Instance.WaitSecondTodo(() =>
{
fishSpeed = 1;
foreach (var item in AliveFishes)
{
item.SetSpeed();
}
}, curTime);
}
public IEnumerator GetAllFishes()
{
List<Fish> allFish = new List<Fish>();
foreach (var item in AliveFishes)
{
allFish.Add(item);
}
foreach (var fish in allFish)
{
fish.DieFish();
yield return new WaitForSeconds(0.01f);
}
}
public void DestroyAllFish()
{
List<Fish> allFish = new List<Fish>();
foreach (var item in AliveFishes)
{
if(item.gameObject!=null)
allFish.Add(item);
}
foreach (var fish in allFish)
{
if(fish==null)
continue;
if(fish.gameObject!=null)
Destroy(fish.gameObject);
}
}
#region
public bool IsFishTide { get; private set; }
[Header("鱼潮配置")]
[SerializeField] private int tideLaneCount = 5;
[SerializeField] private int fishPerLane = 30;
[SerializeField] private float fishTideAccelerateDuration = 3f;
[SerializeField] private float fishTideDisappearDelay = 2f;
private int activeFishTideLanes = 0;
private GameObject fishTideRoot;
/// <summary>
/// 开始鱼潮来袭
/// </summary>
public void StartFishTide()
{
if (IsFishTide) return;
IsFishTide = true;
activeFishTideLanes = tideLaneCount;
Debug.Log("[FishTide] 鱼潮来袭!所有鱼加速!");
// 加速所有现有鱼
AccelerateAndClearAllFishes();
// 延迟后生成鱼潮鱼群
CoroutineTaskManager.Instance.WaitSecondTodo(() =>
{
SpawnFishTide();
}, fishTideAccelerateDuration + fishTideDisappearDelay);
}
void AccelerateAndClearAllFishes()
{
fishSpeed = 0.2f;
foreach (var fish in AliveFishes)
{
fish.SetSpeed();
}
CoroutineTaskManager.Instance.WaitSecondTodo(() =>
{
Debug.Log("[FishTide] 旧鱼群消失!");
DestroyAllFish();
fishSpeed = 1f;
}, fishTideAccelerateDuration);
}
void SpawnFishTide()
{
fishTideRoot = new GameObject("FishTideRoot");
GameManager.Ins.PlaySound2D("鱼潮事件NPC语音");
for (int lane = 0; lane < tideLaneCount; lane++)
{
SpawnOneTideLane(lane);
}
}
void SpawnOneTideLane(int laneIndex)
{
GameObject laneObj = new GameObject($"FishTideLane_{laneIndex}");
laneObj.transform.SetParent(fishTideRoot.transform);
FishTideController controller = laneObj.AddComponent<FishTideController>();
controller.fishCount = fishPerLane;
controller.arcHeight = 5f + laneIndex * 0.5f;
controller.Init(laneIndex, tideLaneCount);
}
public void OnFishTideLaneComplete()
{
activeFishTideLanes--;
if (activeFishTideLanes <= 0)
{
EndFishTide();
}
}
void EndFishTide()
{
Debug.Log("[FishTide] 鱼潮结束!");
IsFishTide = false;
if (fishTideRoot != null)
{
Destroy(fishTideRoot);
fishTideRoot = null;
}
GameManager.Ins.Shop.HideFishFideTip();
fishSpeed = 1f;
}
#endregion
}