330 lines
13 KiB
C#
330 lines
13 KiB
C#
using System;
|
||
using System.Collections;
|
||
using UnityEngine;
|
||
using UnityEngine.SceneManagement;
|
||
using UnityEngine.UI;
|
||
using IndianOceanAssets.Engine2_5D;
|
||
using Architecture.Core;
|
||
|
||
namespace GameFramework
|
||
{
|
||
/// <summary>
|
||
/// 全局游戏状态管理器(持久化单例)。
|
||
/// 维护 GameState,提供 GameOver / Win 触发入口与对应事件。
|
||
/// </summary>
|
||
public class GameManager : PersistentSingleton<GameManager>
|
||
{
|
||
[Header("SO 事件通道(替代 static Action 事件,Inspector 拖入对应资产)")]
|
||
[SerializeField] private GameEvent onGameOverEvent;
|
||
[SerializeField] private GameEvent onGameWinEvent;
|
||
|
||
[Header("SO 事件通道(玩家死亡事件,替代 HealthSystem 直接调用 GameOver)")]
|
||
[SerializeField] private GameEvent onPlayerDiedEvent;
|
||
|
||
[Header("运行时集合(替代 FindObjectsOfType<EnemyAI>,需敌人 Prefab 挂 RuntimeSetRegistrar 指向该集合)")]
|
||
[SerializeField] private TransformRuntimeSet enemiesSet;
|
||
[SerializeField] private EnemyManager enemyManagerRef;
|
||
|
||
private void OnEnable()
|
||
{
|
||
if (onPlayerDiedEvent != null)
|
||
onPlayerDiedEvent.Register(OnPlayerDied);
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
if (onPlayerDiedEvent != null)
|
||
onPlayerDiedEvent.Unregister(OnPlayerDied);
|
||
}
|
||
|
||
/// <summary>玩家死亡事件回调:触发失败流程(设置状态 + 过场 + 广播 OnGameOver)。</summary>
|
||
private void OnPlayerDied() => GameOver();
|
||
|
||
public static GameState GameState
|
||
{
|
||
get => Instance != null ? Instance.gameState : GameState.Playing;
|
||
set { if (Instance != null) Instance.gameState = value; }
|
||
}
|
||
|
||
[SerializeField] private GameState gameState = GameState.Playing;
|
||
|
||
[Header("过场动画 - 失败(光圈缩小)")]
|
||
[Tooltip("光源缩小的持续时间(秒)")]
|
||
[SerializeField] private float lightShrinkDuration = 2f;
|
||
|
||
[Tooltip("失败后黑色遮罩淡出持续时间(秒)")]
|
||
[SerializeField] private float lostFadeOutDuration = 1f;
|
||
|
||
[Header("过场动画 - 胜利(光源扩大 + 切场景)")]
|
||
[Tooltip("光源扩大的持续时间(秒)")]
|
||
[SerializeField] private float lightExpandDuration = 1.5f;
|
||
|
||
[Tooltip("光源扩大目标半径")]
|
||
[SerializeField] private float lightExpandTargetRadius = 20f;
|
||
|
||
[Tooltip("淡出持续时间(秒)")]
|
||
[SerializeField] private float fadeOutDuration = 1f;
|
||
|
||
/// <summary>触发游戏失败:设状态 + 触发事件 + 播放过场动画。</summary>
|
||
public static void GameOver()
|
||
{
|
||
if (Instance == null) return;
|
||
GameState = GameState.GameOver;
|
||
Instance.onGameOverEvent?.Raise();
|
||
Instance.StartCoroutine(Instance.DeathTransition());
|
||
}
|
||
|
||
/// <summary>触发游戏胜利:设状态 + 触发事件 + 播放过场动画。</summary>
|
||
public static void Win()
|
||
{
|
||
if (Instance == null) return;
|
||
GameState = GameState.Victory;
|
||
Instance.onGameWinEvent?.Raise();
|
||
Instance.StartCoroutine(Instance.VictoryTransition());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 失败过场协程:禁用控制 → 光源缩小至 0 → 淡出黑色 → 显示失败叠加层。
|
||
/// 不切换场景,留在 Gameplay。
|
||
/// </summary>
|
||
private IEnumerator DeathTransition()
|
||
{
|
||
// 确保从暂停状态恢复(玩家可能在暂停时死亡)
|
||
Time.timeScale = 1f;
|
||
|
||
// 等待一帧,让 HUD 先更新血量显示
|
||
yield return null;
|
||
|
||
// 1. 禁用所有敌人 AI 和控制
|
||
DisableAllEnemyAI();
|
||
|
||
// 2. 查找玩家并禁用一切控制
|
||
var player = GameObject.FindGameObjectWithTag("Player");
|
||
if (player != null)
|
||
{
|
||
// 移动/翻滚/输入
|
||
var playerController = player.GetComponent<PlayerController>();
|
||
if (playerController != null) playerController.enabled = false;
|
||
|
||
// 近战攻击(点击)
|
||
var swordAttack = player.GetComponent<IndianOceanAssets.Engine2_5D.SwordAttack>();
|
||
if (swordAttack != null) swordAttack.enabled = false;
|
||
|
||
// 远程攻击(点击)
|
||
var projectileShooter = player.GetComponent<IndianOceanAssets.Engine2_5D.ProjectileShooter>();
|
||
if (projectileShooter != null) projectileShooter.enabled = false;
|
||
|
||
// 技能:唤魂灯笼
|
||
var lanternSystem = player.GetComponent<IndianOceanAssets.Engine2_5D.SpiritLanternSystem>();
|
||
if (lanternSystem != null) lanternSystem.enabled = false;
|
||
|
||
// 相机跟随
|
||
var camFollow = Camera.main != null ? Camera.main.GetComponent<CameraFollow>() : null;
|
||
if (camFollow != null) camFollow.enabled = false;
|
||
|
||
// 3. 光源缩小至完全不可见
|
||
var lightSource = player.GetComponent<LightSource>();
|
||
if (lightSource != null)
|
||
{
|
||
float startRadius = lightSource.Radius;
|
||
float startIntensity = lightSource.Intensity;
|
||
float elapsed = 0f;
|
||
|
||
while (elapsed < lightShrinkDuration)
|
||
{
|
||
elapsed += Time.unscaledDeltaTime;
|
||
float t = Mathf.Clamp01(elapsed / lightShrinkDuration);
|
||
float smoothT = t * t * (3f - 2f * t); // SmoothStep
|
||
lightSource.SetRadius(Mathf.Lerp(startRadius, 0.1f, smoothT));
|
||
lightSource.SetIntensity(Mathf.Lerp(startIntensity, 0f, smoothT));
|
||
yield return null;
|
||
}
|
||
|
||
lightSource.SetRadius(0.1f);
|
||
lightSource.SetIntensity(0f);
|
||
}
|
||
else
|
||
{
|
||
yield return new WaitForSecondsRealtime(lightShrinkDuration);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
yield return new WaitForSecondsRealtime(lightShrinkDuration);
|
||
}
|
||
|
||
// 4. 淡出黑色 + 显示失败叠加层(不切场景)
|
||
yield return StartCoroutine(ShowLostOverlay());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 禁用场景中所有敌人相关的 AI 和控制组件。
|
||
/// </summary>
|
||
private void DisableAllEnemyAI()
|
||
{
|
||
int disabled = 0;
|
||
|
||
// 优先用 Enemies 运行时集合(敌人 Prefab 需挂 RuntimeSetRegistrar 并指向该集合,
|
||
// 在 OnEnable/OnDisable 时自动注册/注销自身 Transform)
|
||
if (enemiesSet != null)
|
||
{
|
||
foreach (var t in enemiesSet.Items)
|
||
{
|
||
if (t == null) continue;
|
||
var ai = t.GetComponent<IndianOceanAssets.Engine2_5D.EnemyAI>();
|
||
if (ai != null) { ai.enabled = false; disabled++; }
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 过渡期兜底:尚未接入 Enemies 集合时回退(Prefab 接入 RuntimeSetRegistrar 后可删除此分支)
|
||
var enemyAIs = FindObjectsOfType<IndianOceanAssets.Engine2_5D.EnemyAI>();
|
||
foreach (var ai in enemyAIs) ai.enabled = false;
|
||
disabled = enemyAIs.Length;
|
||
}
|
||
|
||
// 禁用 EnemyManager(停止生成/管理逻辑)
|
||
if (enemyManagerRef != null)
|
||
enemyManagerRef.enabled = false;
|
||
|
||
Debug.Log($"[GameManager] 已禁用 {disabled} 个 EnemyAI + EnemyManager");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 胜利过场协程:光源扩大 → 淡出 → 跳转 Scoring。
|
||
/// </summary>
|
||
private IEnumerator VictoryTransition()
|
||
{
|
||
// 胜利使用光源扩散 + 淡出 + 切场景(与失败不同)
|
||
yield return null;
|
||
|
||
var player = GameObject.FindGameObjectWithTag("Player");
|
||
if (player != null)
|
||
{
|
||
var playerController = player.GetComponent<PlayerController>();
|
||
if (playerController != null) playerController.enabled = false;
|
||
|
||
var camFollow = Camera.main != null ? Camera.main.GetComponent<CameraFollow>() : null;
|
||
if (camFollow != null) camFollow.enabled = false;
|
||
|
||
var lightSource = player.GetComponent<LightSource>();
|
||
if (lightSource != null)
|
||
{
|
||
float startRadius = lightSource.Radius;
|
||
float elapsed = 0f;
|
||
|
||
while (elapsed < lightExpandDuration)
|
||
{
|
||
elapsed += Time.unscaledDeltaTime;
|
||
float t = Mathf.Clamp01(elapsed / lightExpandDuration);
|
||
float smoothT = t * t * (3f - 2f * t);
|
||
lightSource.SetRadius(Mathf.Lerp(startRadius, lightExpandTargetRadius, smoothT));
|
||
yield return null;
|
||
}
|
||
|
||
lightSource.SetRadius(lightExpandTargetRadius);
|
||
}
|
||
else
|
||
{
|
||
yield return new WaitForSecondsRealtime(lightExpandDuration);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
yield return new WaitForSecondsRealtime(lightExpandDuration);
|
||
}
|
||
|
||
yield return StartCoroutine(FadeOutAndLoad());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 失败叠加层:淡入黑色 → 显示"你已迷失"UI → 黑色稍微淡出露出暗黑场景。
|
||
/// 不切换场景,留在 Gameplay。
|
||
/// </summary>
|
||
private IEnumerator ShowLostOverlay()
|
||
{
|
||
// 创建全屏黑色遮罩
|
||
var canvasObj = new GameObject("FadeOverlay");
|
||
var canvas = canvasObj.AddComponent<Canvas>();
|
||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||
canvas.sortingOrder = 99999;
|
||
canvasObj.AddComponent<CanvasScaler>();
|
||
|
||
var overlay = canvasObj.AddComponent<Image>();
|
||
overlay.color = new Color(0, 0, 0, 0);
|
||
|
||
// 淡入到全黑
|
||
float elapsed = 0f;
|
||
while (elapsed < fadeOutDuration)
|
||
{
|
||
elapsed += Time.unscaledDeltaTime;
|
||
float t = Mathf.Clamp01(elapsed / fadeOutDuration);
|
||
overlay.color = new Color(0, 0, 0, t);
|
||
yield return null;
|
||
}
|
||
overlay.color = new Color(0, 0, 0, 1);
|
||
|
||
yield return null;
|
||
|
||
// 失败叠加层("你已迷失……")由 GameLostOverlay 订阅 OnGameOver 事件自显示,此处不再直接调用。
|
||
|
||
// 等待一帧让 UI 渲染
|
||
yield return null;
|
||
|
||
// 黑色遮罩稍微淡出,露出一点暗黑场景(配合叠加层的暗色蒙版)
|
||
elapsed = 0f;
|
||
while (elapsed < lostFadeOutDuration)
|
||
{
|
||
elapsed += Time.unscaledDeltaTime;
|
||
float t = Mathf.Clamp01(elapsed / lostFadeOutDuration);
|
||
overlay.color = new Color(0, 0, 0, Mathf.Lerp(1f, 0.4f, t));
|
||
yield return null;
|
||
}
|
||
overlay.color = new Color(0, 0, 0, 0.4f);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建全屏黑色遮罩并淡出,然后加载 Scoring 场景(仅胜利时使用)。
|
||
/// </summary>
|
||
private IEnumerator FadeOutAndLoad()
|
||
{
|
||
// 创建全屏黑色遮罩
|
||
var canvasObj = new GameObject("FadeOverlay");
|
||
var canvas = canvasObj.AddComponent<Canvas>();
|
||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||
canvas.sortingOrder = 99999;
|
||
canvasObj.AddComponent<CanvasScaler>();
|
||
|
||
var overlay = canvasObj.AddComponent<Image>();
|
||
overlay.color = new Color(0, 0, 0, 0);
|
||
|
||
// 淡出到黑色
|
||
float elapsed = 0f;
|
||
while (elapsed < fadeOutDuration)
|
||
{
|
||
elapsed += Time.unscaledDeltaTime;
|
||
float t = Mathf.Clamp01(elapsed / fadeOutDuration);
|
||
overlay.color = new Color(0, 0, 0, t);
|
||
yield return null;
|
||
}
|
||
overlay.color = new Color(0, 0, 0, 1);
|
||
|
||
// 等待一帧确保淡出渲染完成
|
||
yield return null;
|
||
|
||
// 加载 Scoring 场景
|
||
Debug.Log("[GameManager] 过场完成,正在加载 Scoring 场景...");
|
||
SceneManager.LoadScene("Scoring");
|
||
}
|
||
}
|
||
|
||
public enum GameState
|
||
{
|
||
Playing,
|
||
Paused,
|
||
GameOver,
|
||
Victory,
|
||
Scoring
|
||
}
|
||
}
|