Files
AppleHillsProduction/Assets/Scripts/UI/PauseMenu.cs
2025-11-05 20:37:17 +01:00

365 lines
13 KiB
C#

using System;
using Core;
using UnityEngine;
using UnityEngine.SceneManagement;
using UI.Core;
using Pixelplacement;
namespace UI
{
public class PauseMenu : UIPage
{
private static PauseMenu _instance;
/// <summary>
/// Singleton instance of the PauseMenu. No longer creates an instance if one doesn't exist.
/// </summary>
public static PauseMenu Instance => _instance;
[Header("UI References")]
[SerializeField] private GameObject pauseMenuPanel;
[SerializeField] private GameObject pauseButton;
[SerializeField] private CanvasGroup canvasGroup;
// After UIPageController (50)
public override int ManagedAwakePriority => 55;
private new void Awake()
{
base.Awake(); // CRITICAL: Register with LifecycleManager!
// Set instance immediately so it's available before OnManagedAwake() is called
_instance = this;
// Ensure we have a CanvasGroup for transitions
if (canvasGroup == null)
canvasGroup = GetComponent<CanvasGroup>();
if (canvasGroup == null)
canvasGroup = gameObject.AddComponent<CanvasGroup>();
// Set initial state
canvasGroup.alpha = 0f;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
gameObject.SetActive(false);
}
protected override void OnManagedAwake()
{
// Subscribe to scene-dependent events - must be in OnManagedAwake, not OnSceneReady
// because PauseMenu is in DontDestroyOnLoad and OnSceneReady only fires once
if (SceneManagerService.Instance != null)
{
SceneManagerService.Instance.SceneLoadCompleted += SetPauseMenuByLevel;
}
// Also react to global UI hide/show events from the page controller
if (UIPageController.Instance != null)
{
UIPageController.Instance.OnAllUIHidden += HandleAllUIHidden;
UIPageController.Instance.OnAllUIShown += HandleAllUIShown;
}
// Set initial state based on current scene
SetPauseMenuByLevel(SceneManager.GetActiveScene().name);
Logging.Debug("[PauseMenu] Subscribed to SceneManagerService events");
}
protected override void OnSceneReady()
{
// This only fires once for DontDestroyOnLoad objects, so we handle scene loads in OnManagedAwake
}
protected override void OnDestroy()
{
base.OnDestroy();
// Unsubscribe when destroyed
if (SceneManagerService.Instance != null)
{
SceneManagerService.Instance.SceneLoadCompleted -= SetPauseMenuByLevel;
}
if (UIPageController.Instance != null)
{
UIPageController.Instance.OnAllUIHidden -= HandleAllUIHidden;
UIPageController.Instance.OnAllUIShown -= HandleAllUIShown;
}
}
/// <summary>
/// Sets the pause menu game object active or inactive based on the current level
/// </summary>
/// <param name="levelName">The name of the level/scene</param>
public void SetPauseMenuByLevel(string levelName)
{
// When a new scene loads, ensure pause menu is removed from UIPageController stack
// and properly hidden, regardless of pause state
if (UIPageController.Instance != null && UIPageController.Instance.CurrentPage == this)
{
UIPageController.Instance.PopPage();
}
// Ensure pause state is cleared
if (GameManager.Instance != null && GameManager.Instance.IsPaused)
{
EndPauseSideEffects();
}
// Hide the menu UI
if (pauseMenuPanel != null) pauseMenuPanel.SetActive(false);
if (canvasGroup != null)
{
canvasGroup.alpha = 0f;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
}
gameObject.SetActive(false);
Logging.Debug($"[PauseMenu] Cleaned up pause menu state for scene: {levelName}");
}
/// <summary>
/// Shows the pause menu and hides the pause button. Sets input mode to UI.
/// </summary>
public void ShowPauseMenu()
{
if (UIPageController.Instance != null)
{
UIPageController.Instance.PushPage(this);
}
else
{
// Fallback if no controller, just show
if (pauseMenuPanel != null)
pauseMenuPanel.SetActive(true);
gameObject.SetActive(true);
BeginPauseSideEffects();
// no animation fallback
if (canvasGroup != null)
{
canvasGroup.alpha = 1f;
canvasGroup.interactable = true;
canvasGroup.blocksRaycasts = true;
}
}
}
/// <summary>
/// Hides the pause menu and shows the pause button. Sets input mode to Game.
/// </summary>
public void HidePauseMenu()
{
if (!GameManager.Instance.IsPaused)
{
// Ensure UI is hidden if somehow active without state
if (pauseMenuPanel != null) pauseMenuPanel.SetActive(false);
gameObject.SetActive(false);
return;
}
if (UIPageController.Instance != null && UIPageController.Instance.CurrentPage == this)
{
UIPageController.Instance.PopPage();
}
else
{
// Fallback if no controller, just hide
if (pauseMenuPanel != null)
pauseMenuPanel.SetActive(false);
if (pauseButton != null)
pauseButton.SetActive(true);
if (canvasGroup != null)
{
canvasGroup.alpha = 0f;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
}
gameObject.SetActive(false);
}
}
public void HidePauseMenuAndResumeGame()
{
HidePauseMenu();
EndPauseSideEffects();
}
/// <summary>
/// Resumes the game by hiding the pause menu.
/// </summary>
public void ResumeGame()
{
HidePauseMenuAndResumeGame();
}
private void BeginPauseSideEffects()
{
if (pauseButton != null) pauseButton.SetActive(false);
GameManager.Instance.RequestPause(this);
Logging.Debug("[PauseMenu] Game Paused");
}
private void EndPauseSideEffects()
{
if (pauseButton != null) pauseButton.SetActive(true);
GameManager.Instance.ReleasePause(this);
Logging.Debug("[PauseMenu] Game Resumed");
}
protected override void DoTransitionIn(Action onComplete)
{
// Ensure the panel root is active
if (pauseMenuPanel != null) pauseMenuPanel.SetActive(true);
// Pause side effects should run immediately (hide button, set input mode, etc.).
// The tween itself must run in unscaled time so it still animates while the game is paused.
BeginPauseSideEffects();
if (canvasGroup != null)
{
canvasGroup.interactable = true;
canvasGroup.blocksRaycasts = true;
canvasGroup.alpha = 0f;
// pass obeyTimescale = false so this tween runs even when Time.timeScale == 0
Tween.Value(0f, 1f, (v) =>
{
// Logging.Debug($"[PauseMenu] Tweening pause menu alpha: {v}");
canvasGroup.alpha = v;
}, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, () =>
{
// Logging.Debug("[PauseMenu] Finished tweening pause menu in.");
onComplete?.Invoke();
}, false);
}
else
{
onComplete?.Invoke();
}
}
protected override void DoTransitionOut(Action onComplete)
{
if (canvasGroup != null)
{
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
// Run out-tween in unscaled time as well so the fade completes while paused.
Tween.Value(canvasGroup.alpha, 0f, v => canvasGroup.alpha = v, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, () =>
{
EndPauseSideEffects();
if (pauseMenuPanel != null) pauseMenuPanel.SetActive(false);
onComplete?.Invoke();
}, false);
}
else
{
EndPauseSideEffects();
if (pauseMenuPanel != null) pauseMenuPanel.SetActive(false);
onComplete?.Invoke();
}
}
/// <summary>
/// Exits to the main menu scene.
/// </summary>
public async void ExitToAppleHills()
{
// Pop from UIPageController stack before switching scenes
if (UIPageController.Instance != null && UIPageController.Instance.CurrentPage == this)
{
UIPageController.Instance.PopPage();
}
// Ensure pause state is cleared
if (GameManager.Instance != null && GameManager.Instance.IsPaused)
{
EndPauseSideEffects();
}
// Replace with the actual scene name as set in Build Settings
var progress = new Progress<float>(p => Logging.Debug($"Loading progress: {p * 100:F0}%"));
await SceneManagerService.Instance.SwitchSceneAsync("AppleHillsOverworld", progress);
}
/// <summary>
/// Exits the application.
/// </summary>
public void ExitGame()
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
}
public async void ReloadLevel()
{
var progress = new Progress<float>(p => Logging.Debug($"Loading progress: {p * 100:F0}%"));
await SceneManagerService.Instance.ReloadCurrentScene(progress);
}
/// <summary>
/// Loads a level based on the selection from a dropdown menu.
/// Connect this to a Dropdown's onValueChanged event and pass the selected option text.
/// </summary>
/// <param name="levelSelection">The selected level name or identifier from the dropdown</param>
public async void LoadLevel(int levelSelection)
{
// Hide the pause menu before loading a new level
HidePauseMenuAndResumeGame();
// Replace with the actual scene name as set in Build Settings
var progress = new Progress<float>(p => Logging.Debug($"Loading progress: {p * 100:F0}%"));
switch (levelSelection)
{
case 0:
await SceneManagerService.Instance.SwitchSceneAsync("AppleHillsOverworld", progress);
break;
case 1:
await SceneManagerService.Instance.SwitchSceneAsync("Quarry", progress);
break;
case 2:
await SceneManagerService.Instance.SwitchSceneAsync("DivingForPictures", progress);
break;
}
}
// Handlers for UI controller hide/show events — make these safe with respect to transitions and pause state
private void HandleAllUIHidden()
{
var parent = transform.parent?.gameObject ?? gameObject;
// If we're currently transitioning, wait for the transition out to complete before deactivating
if (_isTransitioning)
{
Action handler = null;
handler = () =>
{
OnTransitionOutCompleted -= handler;
parent.SetActive(false);
};
OnTransitionOutCompleted += handler;
return;
}
// If this page is visible, request a proper hide so transition/out side-effects run (e.g. releasing pause)
if (_isVisible)
{
HidePauseMenu();
return;
}
// Otherwise it's safe to immediately deactivate
parent.SetActive(false);
}
private void HandleAllUIShown()
{
var parent = transform.parent?.gameObject ?? gameObject;
// Just ensure the parent is active. Do not force pause or transitions here.
parent.SetActive(true);
}
}
}