472 lines
11 KiB
C#
472 lines
11 KiB
C#
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
|
||
} |