using UnityEngine; using System; using System.Collections; using UnityEngine.SceneManagement; using UnityEngine.UI; #if UNITY_EDITOR using UnityEditor; #endif /// /// Central gameplay pause manager. /// - ESC / top-right Pause button can pause only in gameplay scene. /// - Continue: hide pause panel, replay 3-2-1-Go, then resume chart + music. /// - Replay: reload current gameplay scene (with current selected song/difficulty if available). /// - Exit: return to selectYourSongFirst. /// public class PauseManager : MonoBehaviour { public static PauseManager Instance { get; private set; } [Header("Overlay")] public GameObject overlayRoot; [SerializeField] private float overlayFadeDuration = 0.2f; [SerializeField] private bool preferPausePrefabOverWhiteScreen = true; [SerializeField] private string legacyWhiteScreenRootName = "pauseWhiteScreen"; [Header("Gameplay Scope")] [SerializeField] private bool onlyInGameplayScene = true; [SerializeField] private string gameplaySceneName = "gamePlay_gamePlay"; [SerializeField] private bool requirePlaybackStarted = true; [Header("Buttons")] [SerializeField] private string pauseButtonScenePath = "artworks/NEW UI/zhuangshi (3)/Pause"; [SerializeField] private Button pauseTriggerButton; [SerializeField] private Button continueButton; [SerializeField] private Button replayButton; [SerializeField] private Button exitButton; [SerializeField] private Button settingsButton; [Header("Pause Entry Animation")] [SerializeField] private bool playPauseEntryAnimation = true; [SerializeField] private float pauseItemEnterDuration = 0.18f; [SerializeField] private float pauseItemEnterStagger = 0.016f; public bool IsPaused { get; private set; } = false; public event Action OnPauseStateChanged; private CanvasGroup overlayCanvasGroup; private RectTransform overlayPanelRoot; private CanvasGroup overlayPanelCanvasGroup; private Vector3 panelShownScale = Vector3.zero; private static readonly Vector3 PanelHiddenScale = Vector3.zero; private Coroutine overlayFadeCoroutine; private Coroutine continueCoroutine; private bool buttonsBound = false; private float nextBindRetryTime = 0f; private const string ExitSceneName = "selectYourSongFirst"; void Awake() { if (Instance == null) Instance = this; else { Destroy(gameObject); return; } } void Start() { ResolveOverlayRootForPausePrefab(); EnsureOverlayCanvasGroup(); EnsureOverlayPanelRoot(); SetOverlayVisibleImmediate(false); TryBindButtons(force: true); } void Update() { if (!buttonsBound && Time.unscaledTime >= nextBindRetryTime) { TryBindButtons(force: false); } if (Input.GetKeyDown(KeyCode.Delete)) { #if UNITY_EDITOR EditorApplication.isPlaying = false; #else Application.Quit(); #endif return; } if (Input.GetKeyDown(KeyCode.Escape)) { // User requested ESC as pause trigger in gameplay only. if (CanPauseFromInput() && !IsPaused) { Pause(true); } } // Keep existing Backspace quick-exit while paused. if (IsPaused && Input.GetKeyDown(KeyCode.Backspace)) { Pause(false); StartCoroutine(LoadSceneAsync("Main_main")); } } public void Pause(bool pause) { if (pause) { if (IsPaused) return; IsPaused = true; Time.timeScale = 0f; OnPauseStateChanged?.Invoke(true); // Startup flows call Pause(true) before real gameplay starts. // In that phase we keep time paused but do not show pause UI. bool allowOverlay = CanPauseFromInput(); if (allowOverlay) ShowOverlayAnimated(true); else SetOverlayVisibleImmediate(false); } else { if (!IsPaused) return; IsPaused = false; Time.timeScale = 1f; OnPauseStateChanged?.Invoke(false); ShowOverlayAnimated(false); } } public void TogglePause() { if (!CanPauseFromInput()) return; if (!IsPaused) Pause(true); } public void ContinueFromPause() { if (!IsPaused) return; if (continueCoroutine != null) return; continueCoroutine = StartCoroutine(ContinueFromPauseCoroutine()); } public void ReplayFromPause() { if (!IsPaused) return; if (continueCoroutine != null) { StopCoroutine(continueCoroutine); continueCoroutine = null; } StartCoroutine(ReplayFromPauseCoroutine()); } public void ExitFromPause() { if (!IsPaused) return; if (continueCoroutine != null) { StopCoroutine(continueCoroutine); continueCoroutine = null; } StartCoroutine(ExitFromPauseCoroutine()); } private bool IsGameplaySceneActive() { string sceneName = SceneManager.GetActiveScene().name ?? string.Empty; if (string.Equals(sceneName, gameplaySceneName, StringComparison.OrdinalIgnoreCase)) return true; if (sceneName.IndexOf("gameplay", StringComparison.OrdinalIgnoreCase) >= 0) return true; return false; } private bool CanPauseFromInput() { if (onlyInGameplayScene && !IsGameplaySceneActive()) return false; if (!requirePlaybackStarted) return true; var gm = FindAnyObjectByType(); if (gm == null) return false; return gm.PlaybackStarted; } private void EnsureOverlayCanvasGroup() { if (overlayRoot == null) return; overlayCanvasGroup = overlayRoot.GetComponent(); if (overlayCanvasGroup == null) overlayCanvasGroup = overlayRoot.AddComponent(); } private void ResolveOverlayRootForPausePrefab() { if (!preferPausePrefabOverWhiteScreen) return; // Prefer the real pause prefab root (contains Continue/Replay/Exit). GameObject pauseRoot = FindPauseMenuRootInScene(); if (pauseRoot != null) { overlayRoot = pauseRoot; return; } if (overlayRoot == null) return; // Legacy setup fallback: PauseManager points to pauseWhiteScreen. if (!string.Equals(overlayRoot.name, legacyWhiteScreenRootName, StringComparison.OrdinalIgnoreCase)) return; GameObject candidate = FindPauseOverlayCandidate(overlayRoot.transform); if (candidate == null) candidate = FindPauseOverlayCandidate(null); if (candidate != null && candidate != overlayRoot) overlayRoot = candidate; } private GameObject FindPauseOverlayCandidate(Transform searchRoot) { Canvas[] canvases = searchRoot != null ? searchRoot.GetComponentsInChildren(true) : FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); for (int i = 0; i < canvases.Length; i++) { Canvas canvas = canvases[i]; if (canvas == null) continue; GameObject go = canvas.gameObject; if (go == null || go == overlayRoot) continue; if (!HasPauseButtons(go)) continue; return go; } return null; } private GameObject FindPauseMenuRootInScene() { var all = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < all.Length; i++) { var t = all[i]; if (t == null) continue; if (!t.gameObject.scene.IsValid()) continue; if (!string.Equals(t.name, "pause", StringComparison.OrdinalIgnoreCase)) continue; GameObject go = t.gameObject; if (HasPauseButtons(go)) return go; } // Fallback: any object that contains all three pause action buttons. for (int i = 0; i < all.Length; i++) { var t = all[i]; if (t == null) continue; if (!t.gameObject.scene.IsValid()) continue; GameObject go = t.gameObject; if (HasPauseButtons(go)) return go; } return null; } private static bool HasPauseButtons(GameObject root) { if (root == null) return false; Button[] buttons = root.GetComponentsInChildren