using System.Collections; using UnityEngine; using UnityEngine.UI; using UnityEngine.SceneManagement; namespace GameFramework { /// /// 游戏失败叠加层 —— 直接挂在 Gameplay 场景的 Canvas 上。 /// /// 使用方式: /// 1. 在 Gameplay 场景里创建一个空物体,挂上本脚本 /// 2. 运行一次游戏(或点 Editor 按钮),脚本会自动构建子 UI /// 3. 停止运行后,子 UI 留在场景中,可自由编辑样式/位置/精灵 /// 4. 运行时由 GameManager 调用 GameLostOverlay.Show() 激活 /// /// 所有 UI 元素均为 [SerializeField],可在 Inspector 中替换。 /// public class GameLostOverlay : MonoBehaviour { private static GameLostOverlay _instance; // ====== Canvas ====== [Header("Canvas(留空则自动创建)")] [SerializeField] private Canvas lostCanvas; // ====== UI 元素引用(全部可编辑) ====== [Header("背景蒙版")] [SerializeField] private Image dimBackground; [Header("标题 —— 你已迷失……")] [SerializeField] private Text lostTitleText; [SerializeField] private Image lostTitleImage; // 可用图片替代文字 [Header("装饰")] [SerializeField] private Image leftDecoration; [SerializeField] private Image rightDecoration; [Header("主界面按钮")] [SerializeField] private Button mainMenuButton; [SerializeField] private Image mainMenuButtonImage; [Header("音效")] [SerializeField] private AudioData lostSFX; [Header("标题渐现动效")] [Tooltip("标题渐现总时长(秒)")] [SerializeField] private float titleFadeDuration = 1.5f; [Tooltip("水面波纹位置抖动幅度(像素)")] [SerializeField] private float ripplePositionAmplitude = 8f; [Tooltip("水面波纹缩放抖动幅度")] [SerializeField] private float rippleScaleAmplitude = 0.08f; [Tooltip("水面波纹频率(越大抖动越快)")] [SerializeField] private float rippleFrequency = 15f; [Tooltip("水波纹材质(留空则自动从 Shader 创建)")] [SerializeField] private Material rippleMaterial; [Tooltip("主界面按钮延迟出现时间(秒)")] [SerializeField] private float buttonFadeDelay = 2f; [Tooltip("按钮渐现时长(秒)")] [SerializeField] private float buttonFadeDuration = 0.5f; private bool _isVisible; private Coroutine _titleAnimCoroutine; private Coroutine _buttonFadeCoroutine; private Material _rippleMaterial; // ==================================================================== // 静态入口 // ==================================================================== /// /// 显示失败叠加层。优先使用场景中已有的实例, /// 没有则动态创建一个。 /// public static void Show() { // 场景中已有(隐藏的)实例? if (_instance != null) { _instance.Activate(); return; } // 尝试在场景中找到 var found = FindObjectOfType(); if (found != null) { _instance = found; found.Activate(); return; } // 都没有 → 动态创建 var obj = new GameObject("GameLostOverlay"); var overlay = obj.AddComponent(); overlay.BuildAll(); overlay.Activate(); } // ==================================================================== // 生命周期 // ==================================================================== void Awake() { _instance = this; // 如果 Canvas 还没赋值,尝试从自身获取 if (lostCanvas == null) lostCanvas = GetComponent(); // 初始隐藏 if (lostCanvas != null) lostCanvas.enabled = false; } void Update() { if (!_isVisible) return; if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.Space) || Input.GetKeyDown(KeyCode.Escape)) { ReturnToMainMenu(); } } // ==================================================================== // 激活 / 隐藏 // ==================================================================== private void Activate() { _isVisible = true; if (lostCanvas != null) lostCanvas.enabled = true; // 自动查找标题元素(兜底:Inspector 未赋值时从 Canvas 子节点中找) AutoFindTitleElement(); Debug.Log($"[GameLostOverlay] Activate - lostTitleImage={lostTitleImage != null}, lostTitleText={lostTitleText != null}, canvas={lostCanvas != null}"); // 启动标题渐现动效(水面波纹 + 渐现) if (_titleAnimCoroutine != null) StopCoroutine(_titleAnimCoroutine); _titleAnimCoroutine = StartCoroutine(TitleRippleFadeIn()); // 绑定按钮 + 延迟渐现 if (mainMenuButton != null) { mainMenuButton.onClick.RemoveAllListeners(); mainMenuButton.onClick.AddListener(ReturnToMainMenu); // 初始隐藏按钮 SetButtonAlpha(0f); // 延迟后渐现 if (_buttonFadeCoroutine != null) StopCoroutine(_buttonFadeCoroutine); _buttonFadeCoroutine = StartCoroutine(ButtonFadeIn()); } // 音效 if (AudioManager.Instance != null && lostSFX != null) AudioManager.Instance.PlaySFX(lostSFX); // 显示鼠标 Cursor.visible = true; Cursor.lockState = CursorLockMode.None; } /// /// 按钮延迟渐现协程:等待 buttonFadeDelay 秒后,用 buttonFadeDuration 秒渐现。 /// private IEnumerator ButtonFadeIn() { yield return new WaitForSecondsRealtime(buttonFadeDelay); float elapsed = 0f; while (elapsed < buttonFadeDuration) { elapsed += Time.unscaledDeltaTime; float t = Mathf.Clamp01(elapsed / buttonFadeDuration); SetButtonAlpha(t); yield return null; } SetButtonAlpha(1f); if (mainMenuButton != null) mainMenuButton.Select(); } /// /// 设置按钮整体透明度(通过 Image.color.a)。 /// private void SetButtonAlpha(float alpha) { if (mainMenuButtonImage != null) { var c = mainMenuButtonImage.color; c.a = alpha; mainMenuButtonImage.color = c; } // 同时处理按钮子物体的 Text if (mainMenuButton != null) { var texts = mainMenuButton.GetComponentsInChildren(true); foreach (var t in texts) { var c = t.color; c.a = alpha; t.color = c; } } } /// /// 返回主界面。 /// private void ReturnToMainMenu() { _isVisible = false; Time.timeScale = 1f; Cursor.visible = true; Cursor.lockState = CursorLockMode.None; if (SceneLoader.Instance != null) SceneLoader.Instance.LoadMainMenuScene(); else SceneManager.LoadScene("MainMenu"); } /// /// 自动查找标题元素。搜索范围: /// 1. 自身 transform 子节点 /// 2. 场景中所有 Canvas 的子节点 /// 3. 都找不到则动态创建 /// private void AutoFindTitleElement() { if (lostTitleImage != null || lostTitleText != null) return; // 1. 从自身子节点查找 TryFindTitleInHierarchy(transform); if (lostTitleText != null || lostTitleImage != null) return; // 2. 从 lostCanvas 子节点查找 if (lostCanvas != null) { TryFindTitleInHierarchy(lostCanvas.transform); if (lostTitleText != null || lostTitleImage != null) return; } // 3. 从场景中所有 Canvas 查找 var allCanvases = FindObjectsOfType(); foreach (var c in allCanvases) { if (c == lostCanvas) continue; TryFindTitleInHierarchy(c.transform); if (lostTitleText != null || lostTitleImage != null) return; } // 4. 都找不到 Debug.LogWarning("[GameLostOverlay] 未找到标题元素,请在 Inspector 中拖入 lostTitleImage 或 lostTitleText。"); } private void TryFindTitleInHierarchy(Transform root) { // 按路径查找 var titleObj = root.Find("CenterPanel/LostTitle"); if (titleObj != null) { lostTitleImage = titleObj.GetComponent(); if (lostTitleImage != null) return; lostTitleText = titleObj.GetComponent(); if (lostTitleText != null) return; } // 按名称查找 Image(lostTitleImage 节点) var allImages = root.GetComponentsInChildren(true); foreach (var img in allImages) { var name = img.gameObject.name.ToLower(); if (name.Contains("lost") || name.Contains("title") || name.Contains("迷失")) { lostTitleImage = img; return; } } // 按文字内容查找 Text var allTexts = root.GetComponentsInChildren(true); foreach (var t in allTexts) { if (t.text.Contains("迷失") || t.text.Contains("Lost")) { lostTitleText = t; return; } } // 兜底:第一个非按钮 Text foreach (var t in allTexts) { if (t.GetComponentInParent