using System; using System.Collections; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using IndianOceanAssets.Engine2_5D; namespace GameFramework { /// /// 全局游戏状态管理器(持久化单例)。 /// 维护 GameState,提供 GameOver / Win 触发入口与对应事件。 /// public class GameManager : PersistentSingleton { /// 游戏结束(失败)时触发。 public static Action onGameOver; /// 游戏胜利时触发。 public static Action onGameWin; public static GameState GameState { get => Instance.gameState; set => 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; /// 触发游戏失败:设状态 + 触发事件 + 播放过场动画。 public static void GameOver() { if (Instance == null) return; GameState = GameState.GameOver; onGameOver?.Invoke(); Instance.StartCoroutine(Instance.DeathTransition()); } /// 触发游戏胜利:设状态 + 触发事件 + 播放过场动画。 public static void Win() { if (Instance == null) return; GameState = GameState.Victory; onGameWin?.Invoke(); Instance.StartCoroutine(Instance.VictoryTransition()); } /// /// 失败过场协程:禁用控制 → 光源缩小至 0 → 淡出黑色 → 显示失败叠加层。 /// 不切换场景,留在 Gameplay。 /// private IEnumerator DeathTransition() { // 等待一帧,让 HUD 先更新血量显示 yield return null; // 1. 禁用所有敌人 AI 和控制 DisableAllEnemyAI(); // 2. 查找玩家并禁用一切控制 var player = GameObject.FindGameObjectWithTag("Player"); if (player != null) { // 移动/翻滚/输入 var playerController = player.GetComponent(); if (playerController != null) playerController.enabled = false; // 近战攻击(点击) var swordAttack = player.GetComponent(); if (swordAttack != null) swordAttack.enabled = false; // 远程攻击(点击) var projectileShooter = player.GetComponent(); if (projectileShooter != null) projectileShooter.enabled = false; // 技能:唤魂灯笼 var lanternSystem = player.GetComponent(); if (lanternSystem != null) lanternSystem.enabled = false; // 相机跟随 var camFollow = Camera.main != null ? Camera.main.GetComponent() : null; if (camFollow != null) camFollow.enabled = false; // 3. 光源缩小至完全不可见 var lightSource = player.GetComponent(); 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()); } /// /// 禁用场景中所有敌人相关的 AI 和控制组件。 /// private void DisableAllEnemyAI() { // 禁用所有 EnemyAI var enemyAIs = FindObjectsOfType(); foreach (var ai in enemyAIs) ai.enabled = false; // 禁用 EnemyManager(停止生成/管理逻辑) var enemyManager = FindObjectOfType(); if (enemyManager != null) enemyManager.enabled = false; Debug.Log($"[GameManager] 已禁用 {enemyAIs.Length} 个 EnemyAI + EnemyManager"); } /// /// 胜利过场协程:光源扩大 → 淡出 → 跳转 Scoring。 /// private IEnumerator VictoryTransition() { // 胜利使用光源扩散 + 淡出 + 切场景(与失败不同) yield return null; var player = GameObject.FindGameObjectWithTag("Player"); if (player != null) { var playerController = player.GetComponent(); if (playerController != null) playerController.enabled = false; var camFollow = Camera.main != null ? Camera.main.GetComponent() : null; if (camFollow != null) camFollow.enabled = false; var lightSource = player.GetComponent(); 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()); } /// /// 失败叠加层:淡入黑色 → 显示"你已迷失"UI → 黑色稍微淡出露出暗黑场景。 /// 不切换场景,留在 Gameplay。 /// private IEnumerator ShowLostOverlay() { // 创建全屏黑色遮罩 var canvasObj = new GameObject("FadeOverlay"); var canvas = canvasObj.AddComponent(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; canvas.sortingOrder = 99999; canvasObj.AddComponent(); var overlay = canvasObj.AddComponent(); 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.Show(); // 等待一帧让 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); } /// /// 创建全屏黑色遮罩并淡出,然后加载 Scoring 场景(仅胜利时使用)。 /// private IEnumerator FadeOutAndLoad() { // 创建全屏黑色遮罩 var canvasObj = new GameObject("FadeOverlay"); var canvas = canvasObj.AddComponent(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; canvas.sortingOrder = 99999; canvasObj.AddComponent(); var overlay = canvasObj.AddComponent(); 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 } }