508 lines
14 KiB
C#
508 lines
14 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using DragonLi.Core;
|
||
using FluffyUnderware.Curvy;
|
||
using UnityEngine;
|
||
using Random = UnityEngine.Random;
|
||
|
||
/// <summary>
|
||
/// 雕像数据
|
||
/// </summary>
|
||
[Serializable]
|
||
public class StatueData
|
||
{
|
||
public int statueIndex; // 雕像序号(0-2)
|
||
public string statueName; // 雕像名称
|
||
public int[] requiredFragments; // 所需碎片序号数组
|
||
public bool isCompleted; // 是否已完成
|
||
public StatueAssembler assembler; // 石像拼凑器引用
|
||
}
|
||
|
||
/// <summary>
|
||
/// 漂流瓶管理器 - 管理海底雕像事件
|
||
/// 每60秒生成3个漂流瓶,玩家收集碎片集齐3个雕像
|
||
/// </summary>
|
||
public class DriftBottleManager : MonoBehaviour
|
||
{
|
||
public static DriftBottleManager Instance { get; private set; }
|
||
|
||
[Header("生成配置")]
|
||
[Tooltip("生成间隔(秒)")]
|
||
public float spawnInterval = 60f;
|
||
|
||
[Tooltip("每次生成数量")]
|
||
public int spawnCountPerWave = 3;
|
||
|
||
[Tooltip("漂流瓶预制体")]
|
||
public GameObject driftBottlePrefab;
|
||
|
||
[Header("碎片飞行效果")]
|
||
[Tooltip("碎片飞行特效预制体")]
|
||
public GameObject fragmentFlyPrefab;
|
||
|
||
|
||
[Header("雕像配置")]
|
||
[Tooltip("雕像总数")]
|
||
public int totalStatues = 3;
|
||
|
||
[Tooltip("每个雕像所需碎片数")]
|
||
public int fragmentsPerStatue = 10;
|
||
|
||
[Tooltip("雕像名称列表")]
|
||
public string[] statueNames = { "龙王雕像", "美人鱼雕像", "三叉戟雕像" };
|
||
|
||
[Header("石像位置")]
|
||
[Tooltip("3个石像的位置(Transform)")]
|
||
public StatueAssembler[] statueTransforms;
|
||
|
||
[Tooltip("石像显示位置偏移")]
|
||
public Vector3 statueShowOffset = new Vector3(0, 0, 5f);
|
||
|
||
[Header("调试")]
|
||
public bool debugMode = false;
|
||
|
||
// 私有变量
|
||
private float spawnTimer = 0f;
|
||
private bool isEventActive = false;
|
||
private int currentFragmentIndex = 0; // 当前碎片序号(0-29)
|
||
private HashSet<int> collectedFragments = new HashSet<int>(); // 已收集的碎片
|
||
private List<StatueData> statues = new List<StatueData>(); // 雕像数据列表
|
||
private HashSet<DriftBottle> activeBottles = new HashSet<DriftBottle>(); // 活跃漂流瓶
|
||
private bool allStatuesCompleted = false;
|
||
|
||
// 事件
|
||
public event Action<int, int> OnFragmentCollected; // 参数:碎片序号,雕像序号
|
||
public event Action<int> OnStatueCompleted; // 参数:雕像序号
|
||
public event Action OnAllStatuesCompleted; // 所有雕像完成
|
||
public event Action<int, int, int> OnSpawnWave; // 参数:当前波次,总波次,生成数量
|
||
|
||
private void Awake()
|
||
{
|
||
Instance = this;
|
||
}
|
||
|
||
private void Start()
|
||
{
|
||
InitializeStatues();
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
if (!isEventActive || allStatuesCompleted) return;
|
||
|
||
spawnTimer += Time.deltaTime;
|
||
|
||
if (spawnTimer >= spawnInterval)
|
||
{
|
||
spawnTimer = 0f;
|
||
TrySpawnBottles();
|
||
}
|
||
|
||
// 调试快捷键
|
||
if (debugMode && Input.GetKeyDown(KeyCode.B))
|
||
{
|
||
TrySpawnBottles();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化雕像数据
|
||
/// </summary>
|
||
private void InitializeStatues()
|
||
{
|
||
statues.Clear();
|
||
int totalFragments = totalStatues * fragmentsPerStatue;
|
||
|
||
for (int i = 0; i < totalStatues; i++)
|
||
{
|
||
StatueData statue = new StatueData
|
||
{
|
||
statueIndex = i,
|
||
statueName = statueNames.Length > i ? statueNames[i] : $"雕像{i + 1}",
|
||
requiredFragments = new int[fragmentsPerStatue],
|
||
isCompleted = false
|
||
};
|
||
|
||
// 填充所需碎片序号
|
||
for (int j = 0; j < fragmentsPerStatue; j++)
|
||
{
|
||
statue.requiredFragments[j] = i * fragmentsPerStatue + j;
|
||
}
|
||
|
||
statues.Add(statue);
|
||
}
|
||
|
||
// 注册石像拼凑器
|
||
if (statueTransforms != null)
|
||
{
|
||
foreach (StatueAssembler assembler in statueTransforms)
|
||
{
|
||
if (assembler != null)
|
||
{
|
||
SetStatueAssembler(assembler.statueIndex, assembler);
|
||
}
|
||
}
|
||
}
|
||
|
||
Debug.Log($"[DriftBottleManager] 初始化完成,共{totalStatues}个雕像,{totalFragments}个碎片");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置石像拼凑器
|
||
/// </summary>
|
||
public void SetStatueAssembler(int statueIndex, StatueAssembler assembler)
|
||
{
|
||
if (statueIndex >= 0 && statueIndex < statues.Count)
|
||
{
|
||
statues[statueIndex].assembler = assembler;
|
||
|
||
// 注册完成事件
|
||
assembler.OnStatueCompleted += OnStatueAssembleCompleted;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 启动雕像事件
|
||
/// </summary>
|
||
public void StartStatueEvent()
|
||
{
|
||
if (allStatuesCompleted)
|
||
{
|
||
Debug.Log("[DriftBottleManager] 所有雕像已集齐,事件不再启动");
|
||
return;
|
||
}
|
||
|
||
isEventActive = true;
|
||
spawnTimer = 0f;
|
||
Debug.Log("[DriftBottleManager] 海底雕像事件已启动!");
|
||
|
||
// 立即生成第一波
|
||
//TrySpawnBottles();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止雕像事件
|
||
/// </summary>
|
||
public void StopStatueEvent()
|
||
{
|
||
isEventActive = false;
|
||
Debug.Log("[DriftBottleManager] 海底雕像事件已停止");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 尝试生成漂流瓶
|
||
/// </summary>
|
||
private void TrySpawnBottles()
|
||
{
|
||
if (driftBottlePrefab == null)
|
||
{
|
||
Debug.LogError("[DriftBottleManager] 漂流瓶预制体未设置!");
|
||
return;
|
||
}
|
||
|
||
// 计算还需要生成多少碎片
|
||
int totalFragments = totalStatues * fragmentsPerStatue;
|
||
int remainingFragments = totalFragments - currentFragmentIndex;
|
||
|
||
if (remainingFragments <= 0)
|
||
{
|
||
Debug.Log("[DriftBottleManager] 所有碎片已生成完毕");
|
||
return;
|
||
}
|
||
|
||
// 计算本次生成数量
|
||
int spawnCount = Mathf.Min(spawnCountPerWave, remainingFragments);
|
||
|
||
// 获取路径
|
||
CurvySpline[] availablePaths = GameInit.Ins.GetIndexPath(3);
|
||
if (availablePaths.Length == 0)
|
||
{
|
||
Debug.LogError("[DriftBottleManager] 没有可用的路径!");
|
||
return;
|
||
}
|
||
// 生成漂流瓶,分散在不同路径
|
||
for (int i = 0; i < spawnCount; i++)
|
||
{
|
||
int fragIndex = currentFragmentIndex++;
|
||
int statueIdx = fragIndex / fragmentsPerStatue;
|
||
|
||
// 选择路径(分散在不同路径)
|
||
CurvySpline path = availablePaths[i % availablePaths.Length];
|
||
|
||
SpawnBottle(path, fragIndex, statueIdx);
|
||
}
|
||
|
||
// 触发事件
|
||
int currentWave = (currentFragmentIndex - 1) / spawnCountPerWave + 1;
|
||
int totalWaves = Mathf.CeilToInt((float)totalFragments / spawnCountPerWave);
|
||
OnSpawnWave?.Invoke(currentWave, totalWaves, spawnCount);
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成单个漂流瓶
|
||
/// </summary>
|
||
private void SpawnBottle(CurvySpline path, int fragIndex, int statueIdx)
|
||
{
|
||
if (driftBottlePrefab == null) return;
|
||
|
||
if (path == null)
|
||
{
|
||
Debug.LogError("[DriftBottleManager] SpawnBottle 失败:path 为 null!");
|
||
return;
|
||
}
|
||
|
||
// 使用路径对象的位置作为生成点(路径生成器已将路径对象设置在正确位置)
|
||
Vector3 spawnPos = path.transform.position;
|
||
|
||
// 在路径起点生成漂流瓶
|
||
GameObject bottleGO = Instantiate(driftBottlePrefab, spawnPos, Quaternion.identity);
|
||
DriftBottle bottle = bottleGO.GetComponent<DriftBottle>();
|
||
if (bottle != null)
|
||
{
|
||
bottle.Init(path, fragIndex, statueIdx);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取可用路径列表 - 每次生成都是全新的随机路径
|
||
/// </summary>
|
||
|
||
/// <summary>
|
||
/// 漂流瓶被捕获 - 创建飞行碎片效果
|
||
/// </summary>
|
||
public void OnBottleCaptured(DriftBottle bottle, int fragIndex, int statueIdx)
|
||
{
|
||
if (collectedFragments.Contains(fragIndex))
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 记录收集
|
||
collectedFragments.Add(fragIndex);
|
||
|
||
// 获取漂流瓶位置
|
||
Vector3 bottlePosition = bottle != null ? bottle.transform.position : Vector3.zero;
|
||
|
||
// 创建碎片飞行效果
|
||
CreateFragmentFlyEffect(bottlePosition, fragIndex, statueIdx);
|
||
|
||
// 触发事件
|
||
OnFragmentCollected?.Invoke(fragIndex, statueIdx);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建碎片飞行效果
|
||
/// </summary>
|
||
private void CreateFragmentFlyEffect(Vector3 startPos, int fragIndex, int statueIdx)
|
||
{
|
||
if (fragmentFlyPrefab == null)
|
||
{
|
||
// 如果没有飞行特效预制体,直接添加到石像
|
||
AddFragmentToStatue(fragIndex, statueIdx);
|
||
return;
|
||
}
|
||
|
||
// 获取目标石像
|
||
Transform targetStatue = GetStatueTransform(statueIdx);
|
||
if (targetStatue == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 实例化飞行碎片
|
||
GameObject flyObj = Instantiate(fragmentFlyPrefab, startPos, Quaternion.identity);
|
||
FragmentFlyEffect flyEffect = flyObj.GetComponent<FragmentFlyEffect>();
|
||
|
||
if (flyEffect != null)
|
||
{
|
||
flyEffect.Initialize(startPos, targetStatue, fragIndex, statueIdx, OnFragmentArrived);
|
||
}
|
||
else
|
||
{
|
||
// 如果没有FragmentFlyEffect组件,直接添加
|
||
AddFragmentToStatue(fragIndex, statueIdx);
|
||
Destroy(flyObj);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 碎片到达回调
|
||
/// </summary>
|
||
private void OnFragmentArrived(int fragIndex, int statueIdx)
|
||
{
|
||
AddFragmentToStatue(fragIndex, statueIdx);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加碎片到石像
|
||
/// </summary>
|
||
private void AddFragmentToStatue(int fragIndex, int statueIdx)
|
||
{
|
||
if (statueIdx >= statues.Count) return;
|
||
|
||
StatueData statue = statues[statueIdx];
|
||
if (statue.assembler != null)
|
||
{
|
||
statue.assembler.AddFragment(fragIndex);
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"[DriftBottleManager] 石像{statueIdx}的拼凑器未设置!");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取石像位置
|
||
/// </summary>
|
||
private Transform GetStatueTransform(int statueIdx)
|
||
{
|
||
if (statueTransforms != null && statueIdx < statueTransforms.Length && statueTransforms[statueIdx] != null)
|
||
{
|
||
return statueTransforms[statueIdx].transform;
|
||
}
|
||
|
||
// 如果没有设置,使用默认位置
|
||
if (GameInit.Ins?.playerCam != null)
|
||
{
|
||
Vector3 playerPos = GameInit.Ins.playerCam.transform.position;
|
||
Vector3 playerForward = GameInit.Ins.playerCam.transform.forward;
|
||
|
||
// 在玩家前方显示石像
|
||
float offsetX = (statueIdx - 1) * 5f; // 三个石像水平分布
|
||
Vector3 statuePos = playerPos + playerForward * statueShowOffset.z + Vector3.right * offsetX;
|
||
statuePos.y += statueShowOffset.y;
|
||
|
||
GameObject tempObj = new GameObject($"StatueTarget_{statueIdx}");
|
||
tempObj.transform.position = statuePos;
|
||
return tempObj.transform;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 石像拼凑完成回调
|
||
/// </summary>
|
||
private void OnStatueAssembleCompleted(int statueIdx)
|
||
{
|
||
if (statueIdx >= statues.Count) return;
|
||
|
||
statues[statueIdx].isCompleted = true;
|
||
Debug.Log($"[DriftBottleManager] {statues[statueIdx].statueName} 拼凑完成!");
|
||
|
||
// 触发事件
|
||
OnStatueCompleted?.Invoke(statueIdx);
|
||
|
||
// 检查是否所有雕像都完成了
|
||
CheckAllStatuesCompleted();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查是否所有雕像都完成
|
||
/// </summary>
|
||
private void CheckAllStatuesCompleted()
|
||
{
|
||
foreach (var statue in statues)
|
||
{
|
||
if (!statue.isCompleted)
|
||
return;
|
||
}
|
||
|
||
// 所有雕像完成
|
||
allStatuesCompleted = true;
|
||
isEventActive = false;
|
||
|
||
Debug.Log("[DriftBottleManager] 所有雕像已集齐!海底雕像事件结束!");
|
||
|
||
OnAllStatuesCompleted?.Invoke();
|
||
|
||
// 播放完成音效
|
||
GameManager.Ins?.PlaySound2D("雕像事件完成");
|
||
}
|
||
|
||
#region 注册管理
|
||
|
||
public void RegisterBottle(DriftBottle bottle)
|
||
{
|
||
if (bottle != null)
|
||
activeBottles.Add(bottle);
|
||
}
|
||
|
||
public void UnregisterBottle(DriftBottle bottle)
|
||
{
|
||
if (bottle != null)
|
||
activeBottles.Remove(bottle);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 公共查询方法
|
||
|
||
/// <summary>
|
||
/// 获取已收集碎片数量
|
||
/// </summary>
|
||
public int GetCollectedFragmentCount()
|
||
{
|
||
return collectedFragments.Count;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指定雕像的收集进度
|
||
/// </summary>
|
||
public int GetStatueProgress(int statueIdx)
|
||
{
|
||
if (statueIdx >= statues.Count) return 0;
|
||
|
||
if (statues[statueIdx].assembler != null)
|
||
{
|
||
return statues[statueIdx].assembler.GetProgress();
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指定雕像是否完成
|
||
/// </summary>
|
||
public bool IsStatueCompleted(int statueIdx)
|
||
{
|
||
if (statueIdx >= statues.Count) return false;
|
||
return statues[statueIdx].isCompleted;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前目标雕像序号
|
||
/// </summary>
|
||
public int GetCurrentTargetStatueIndex()
|
||
{
|
||
for (int i = 0; i < statues.Count; i++)
|
||
{
|
||
if (!statues[i].isCompleted)
|
||
return i;
|
||
}
|
||
return -1; // 全部完成
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取雕像数据列表
|
||
/// </summary>
|
||
public List<StatueData> GetStatues() => statues;
|
||
|
||
/// <summary>
|
||
/// 是否所有雕像都完成
|
||
/// </summary>
|
||
public bool IsAllStatuesCompleted() => allStatuesCompleted;
|
||
|
||
/// <summary>
|
||
/// 获取下一次生成倒计时
|
||
/// </summary>
|
||
public float GetNextSpawnCountdown()
|
||
{
|
||
if (!isEventActive || allStatuesCompleted) return -1;
|
||
return Mathf.Max(0, spawnInterval - spawnTimer);
|
||
}
|
||
|
||
#endregion
|
||
}
|