Files
gold_dolphin/unity/Assets/UI/GameManager.cs
T
2026-07-07 03:34:56 +08:00

330 lines
13 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;
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
}
}