471 lines
18 KiB
C#
471 lines
18 KiB
C#
using System;
|
|
using Core;
|
|
using Core.SaveLoad;
|
|
using Data.CardSystem;
|
|
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;
|
|
|
|
[Header("Dev Options")]
|
|
[SerializeField] private UnityEngine.UI.Button devOptionsButton;
|
|
[SerializeField] private GameObject mainOptionsContainer;
|
|
[SerializeField] private GameObject devOptionsContainer;
|
|
|
|
// 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()
|
|
{
|
|
// Clear all save data for the current gameplay level before reloading
|
|
if (SaveLoadManager.Instance != null && SceneManagerService.Instance != null)
|
|
{
|
|
string currentLevel = SceneManagerService.Instance.CurrentGameplayScene;
|
|
if (!string.IsNullOrEmpty(currentLevel))
|
|
{
|
|
SaveLoadManager.Instance.ClearLevelData(currentLevel);
|
|
Logging.Debug($"[PauseMenu] Cleared save data for current level: {currentLevel}");
|
|
}
|
|
}
|
|
|
|
// Now reload the current scene with fresh state - skipSave=true prevents re-saving cleared data
|
|
var progress = new Progress<float>(p => Logging.Debug($"Loading progress: {p * 100:F0}%"));
|
|
await SceneManagerService.Instance.ReloadCurrentScene(progress, autoHideLoadingScreen: true, skipSave: true);
|
|
}
|
|
|
|
/// <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);
|
|
}
|
|
|
|
#region Dev Options
|
|
|
|
/// <summary>
|
|
/// Shows dev options panel and hides main options
|
|
/// </summary>
|
|
public void ShowDevOptions()
|
|
{
|
|
if (mainOptionsContainer != null) mainOptionsContainer.SetActive(false);
|
|
if (devOptionsContainer != null) devOptionsContainer.SetActive(true);
|
|
Logging.Debug("[PauseMenu] Showing dev options");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hides dev options panel and shows main options
|
|
/// </summary>
|
|
public void HideDevOptions()
|
|
{
|
|
if (devOptionsContainer != null) devOptionsContainer.SetActive(false);
|
|
if (mainOptionsContainer != null) mainOptionsContainer.SetActive(true);
|
|
Logging.Debug("[PauseMenu] Hiding dev options");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dev option: Completely wipes all save data and reloads the current level
|
|
/// </summary>
|
|
public async void DevResetAndReload()
|
|
{
|
|
Logging.Debug("[PauseMenu] Dev Reset: Clearing all save data and reloading level");
|
|
|
|
// Clear the card collection
|
|
if (CardSystemManager.Instance != null)
|
|
{
|
|
CardSystemManager.Instance.ClearAllCollectionData();
|
|
Logging.Debug("[PauseMenu] Cleared card collection");
|
|
}
|
|
|
|
// Clear all save data from memory
|
|
if (SaveLoadManager.Instance != null && SaveLoadManager.Instance.currentSaveData != null)
|
|
{
|
|
SaveLoadManager.Instance.currentSaveData.participantStates.Clear();
|
|
SaveLoadManager.Instance.currentSaveData.unlockedMinigames.Clear();
|
|
SaveLoadManager.Instance.currentSaveData.playedDivingTutorial = false;
|
|
Logging.Debug("[PauseMenu] Cleared all save data from memory");
|
|
}
|
|
|
|
// Delete the save file
|
|
string saveFolder = System.IO.Path.Combine(Application.persistentDataPath, "GameSaves");
|
|
if (System.IO.Directory.Exists(saveFolder))
|
|
{
|
|
try
|
|
{
|
|
string[] files = System.IO.Directory.GetFiles(saveFolder);
|
|
foreach (string file in files)
|
|
{
|
|
System.IO.File.Delete(file);
|
|
Logging.Debug($"[PauseMenu] Deleted save file: {file}");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.Warning($"[PauseMenu] Failed to delete some save files: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
// Now reload the current scene - skipSave=true prevents re-saving the cleared data
|
|
var progress = new Progress<float>(p => Logging.Debug($"Loading progress: {p * 100:F0}%"));
|
|
await SceneManagerService.Instance.ReloadCurrentScene(progress, autoHideLoadingScreen: true, skipSave: true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dev option: Gives the player 3 booster packs
|
|
/// </summary>
|
|
public void DevGiveBoosters()
|
|
{
|
|
if (CardSystemManager.Instance != null)
|
|
{
|
|
CardSystemManager.Instance.AddBoosterPack(3);
|
|
Logging.Debug("[PauseMenu] Dev: Granted 3 booster packs");
|
|
}
|
|
else
|
|
{
|
|
Logging.Warning("[PauseMenu] Dev: CardSystemManager not available");
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|