468 lines
15 KiB
C#
468 lines
15 KiB
C#
using System;
|
|
using System.Threading;
|
|
using BriarQueen.Framework.Coordinators.Events;
|
|
using BriarQueen.Framework.Events.UI;
|
|
using BriarQueen.Framework.Managers.Audio;
|
|
using BriarQueen.Framework.Managers.Interaction;
|
|
using BriarQueen.Framework.Managers.IO;
|
|
using BriarQueen.Framework.Managers.Levels;
|
|
using BriarQueen.Framework.Managers.UI.Base;
|
|
using BriarQueen.Framework.Managers.UI.Events;
|
|
using BriarQueen.Framework.Services.Game;
|
|
using BriarQueen.UI.Menus.Components;
|
|
using Cysharp.Threading.Tasks;
|
|
using PrimeTween;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.UI;
|
|
using VContainer;
|
|
|
|
namespace BriarQueen.UI.Menus
|
|
{
|
|
public class PauseMenuWindow : MonoBehaviour, IUIWindow, IUIOverlayHost
|
|
{
|
|
[Header("Root UI")]
|
|
[SerializeField] private CanvasGroup _canvasGroup;
|
|
[SerializeField] private RectTransform _windowRect;
|
|
|
|
[Header("Background")]
|
|
[SerializeField] private CanvasGroup _backgroundGroup;
|
|
|
|
[Header("Transient Info")]
|
|
[SerializeField] private TextMeshProUGUI _levelName;
|
|
|
|
[Header("Buttons")]
|
|
[SerializeField] private UnderlineButton _resumeButton;
|
|
[SerializeField] private UnderlineButton _saveButton;
|
|
[SerializeField] private UnderlineButton _settingsButton;
|
|
[SerializeField] private UnderlineButton _exitButton;
|
|
[SerializeField] private UnderlineButton _quitToDesktopButton;
|
|
|
|
[Header("Layout")]
|
|
[SerializeField] private CanvasGroup _buttonsGroup;
|
|
|
|
[Header("Selection")]
|
|
[SerializeField] private Selectable _firstSelectedOnOpen;
|
|
|
|
[Header("Tween Settings")]
|
|
[SerializeField] private TweenSettings _backgroundFadeSettings = new()
|
|
{
|
|
duration = 0.4f,
|
|
ease = Ease.OutQuad,
|
|
useUnscaledTime = true
|
|
};
|
|
|
|
[SerializeField] private TweenSettings _buttonFadeSettings = new()
|
|
{
|
|
duration = 0.25f,
|
|
ease = Ease.OutQuad,
|
|
useUnscaledTime = true
|
|
};
|
|
|
|
[Header("Scale")]
|
|
[SerializeField] private float _hiddenScale = 0.85f;
|
|
|
|
[Header("Internal")]
|
|
[SerializeField] private GraphicRaycaster _graphicRaycaster;
|
|
|
|
private AudioManager _audioManager;
|
|
private EventCoordinator _eventCoordinator;
|
|
private GameService _gameService;
|
|
private InteractManager _interactManager;
|
|
private SaveManager _saveManager;
|
|
private LevelManager _levelManager;
|
|
|
|
private CancellationTokenSource _cts;
|
|
private Sequence _sequence;
|
|
private bool _raycasterRegistered;
|
|
|
|
public bool IsModal => true;
|
|
public WindowType WindowType => WindowType.PauseMenuWindow;
|
|
|
|
private void TryRegisterRaycaster()
|
|
{
|
|
if (_raycasterRegistered)
|
|
return;
|
|
|
|
if (_interactManager == null || _graphicRaycaster == null)
|
|
return;
|
|
|
|
_interactManager.AddUIRaycaster(_graphicRaycaster);
|
|
_interactManager.SetExclusiveRaycaster(_graphicRaycaster);
|
|
_raycasterRegistered = true;
|
|
}
|
|
|
|
private void TryUnregisterRaycaster()
|
|
{
|
|
if (!_raycasterRegistered)
|
|
return;
|
|
|
|
if (_interactManager == null || _graphicRaycaster == null)
|
|
return;
|
|
|
|
_interactManager.RemoveUIRaycaster(_graphicRaycaster);
|
|
_interactManager.ClearExclusiveRaycaster();
|
|
_raycasterRegistered = false;
|
|
}
|
|
|
|
// ── Unity lifecycle ───────────────────────────────────────────
|
|
|
|
private void Awake()
|
|
{
|
|
if (_canvasGroup != null)
|
|
{
|
|
_canvasGroup.alpha = 0f;
|
|
_canvasGroup.blocksRaycasts = false;
|
|
_canvasGroup.interactable = false;
|
|
}
|
|
|
|
if (_windowRect != null)
|
|
_windowRect.localScale = Vector3.one;
|
|
|
|
SetGroupImmediate(_backgroundGroup, 0f);
|
|
HideButtons();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
BindButtons();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
UnbindButtons();
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
StopAndResetCancellation();
|
|
TryUnregisterRaycaster();
|
|
}
|
|
|
|
// ── IUIWindow ─────────────────────────────────────────────────
|
|
|
|
public async UniTask Show()
|
|
{
|
|
StopAndResetCancellation();
|
|
|
|
SetLevelName();
|
|
|
|
gameObject.SetActive(true);
|
|
TryRegisterRaycaster();
|
|
|
|
_canvasGroup.blocksRaycasts = false;
|
|
_canvasGroup.interactable = false;
|
|
|
|
SetGroupImmediate(_backgroundGroup, 0f);
|
|
HideButtons();
|
|
|
|
try
|
|
{
|
|
var fadeWindowTask = FadeGroup(_canvasGroup, 1f, _backgroundFadeSettings, _cts.Token);
|
|
var fadeBackgroundTask = FadeGroup(_backgroundGroup, 1f, _backgroundFadeSettings, _cts.Token);
|
|
|
|
await UniTask.WhenAll(fadeWindowTask, fadeBackgroundTask);
|
|
|
|
_backgroundGroup.blocksRaycasts = true;
|
|
_backgroundGroup.interactable = true;
|
|
|
|
_buttonsGroup.blocksRaycasts = true;
|
|
_buttonsGroup.interactable = true;
|
|
|
|
await FadeGroup(_buttonsGroup, 1f, _buttonFadeSettings, _cts.Token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_canvasGroup.alpha = 1f;
|
|
_canvasGroup.blocksRaycasts = true;
|
|
_canvasGroup.interactable = true;
|
|
}
|
|
|
|
public async UniTask Hide()
|
|
{
|
|
StopAndResetCancellation();
|
|
|
|
_canvasGroup.blocksRaycasts = true;
|
|
_canvasGroup.interactable = false;
|
|
|
|
try
|
|
{
|
|
await FadeGroup(_buttonsGroup, 0f, _buttonFadeSettings, _cts.Token);
|
|
await FadeGroup(_backgroundGroup, 0f, _backgroundFadeSettings, _cts.Token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_resumeButton.SetSelectedState(false);
|
|
_saveButton.SetSelectedState(false);
|
|
_settingsButton.SetSelectedState(false);
|
|
_exitButton.SetSelectedState(false);
|
|
_quitToDesktopButton.SetSelectedState(false);
|
|
|
|
_canvasGroup.alpha = 0f;
|
|
_canvasGroup.blocksRaycasts = false;
|
|
_canvasGroup.interactable = false;
|
|
|
|
TryUnregisterRaycaster();
|
|
gameObject.SetActive(false);
|
|
}
|
|
|
|
public async UniTask SuspendForOverlay()
|
|
{
|
|
StopAndResetCancellation();
|
|
|
|
_buttonsGroup.blocksRaycasts = false;
|
|
_buttonsGroup.interactable = false;
|
|
|
|
try
|
|
{
|
|
await FadeGroup(_buttonsGroup, 0f, _buttonFadeSettings, _cts.Token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
public async UniTask ResumeFromOverlay()
|
|
{
|
|
StopAndResetCancellation();
|
|
|
|
_buttonsGroup.blocksRaycasts = true;
|
|
_buttonsGroup.interactable = true;
|
|
|
|
try
|
|
{
|
|
await FadeGroup(_buttonsGroup, 1f, _buttonFadeSettings, _cts.Token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SelectDefault();
|
|
}
|
|
|
|
// ── DI ────────────────────────────────────────────────────────
|
|
|
|
[Inject]
|
|
public void Construct(
|
|
EventCoordinator eventCoordinator,
|
|
SaveManager saveManager,
|
|
GameService gameService,
|
|
InteractManager interactManager,
|
|
AudioManager audioManager,
|
|
LevelManager levelManager)
|
|
{
|
|
_eventCoordinator = eventCoordinator;
|
|
_saveManager = saveManager;
|
|
_gameService = gameService;
|
|
_interactManager = interactManager;
|
|
_audioManager = audioManager;
|
|
_levelManager = levelManager;
|
|
}
|
|
|
|
// ── Level ─────────────────────────────────────────────────────
|
|
|
|
private void SetLevelName()
|
|
{
|
|
if (_levelManager == null || _levelName == null)
|
|
return;
|
|
|
|
var currentLevel = _levelManager.CurrentLevel;
|
|
|
|
if (!string.IsNullOrEmpty(currentLevel.LevelName))
|
|
_levelName.text = currentLevel.LevelName;
|
|
}
|
|
|
|
// ── Button binding ────────────────────────────────────────────
|
|
|
|
private void BindButtons()
|
|
{
|
|
if (_resumeButton != null)
|
|
_resumeButton.SelectionRequested += OnResumeClicked;
|
|
|
|
if (_saveButton != null)
|
|
_saveButton.SelectionRequested += OnSaveClicked;
|
|
|
|
if (_settingsButton != null)
|
|
_settingsButton.SelectionRequested += OnSettingsClicked;
|
|
|
|
if (_exitButton != null)
|
|
_exitButton.SelectionRequested += OnExitClicked;
|
|
|
|
if (_quitToDesktopButton != null)
|
|
_quitToDesktopButton.SelectionRequested += OnQuitToDesktopClicked;
|
|
}
|
|
|
|
private void UnbindButtons()
|
|
{
|
|
if (_resumeButton != null)
|
|
_resumeButton.SelectionRequested -= OnResumeClicked;
|
|
|
|
if (_saveButton != null)
|
|
_saveButton.SelectionRequested -= OnSaveClicked;
|
|
|
|
if (_settingsButton != null)
|
|
_settingsButton.SelectionRequested -= OnSettingsClicked;
|
|
|
|
if (_exitButton != null)
|
|
_exitButton.SelectionRequested -= OnExitClicked;
|
|
|
|
if (_quitToDesktopButton != null)
|
|
_quitToDesktopButton.SelectionRequested -= OnQuitToDesktopClicked;
|
|
}
|
|
|
|
// ── Button callbacks ──────────────────────────────────────────
|
|
|
|
private void OnResumeClicked(UnderlineButton _)
|
|
{
|
|
_eventCoordinator?.Publish(new PauseButtonClickedEvent());
|
|
}
|
|
|
|
private void OnSaveClicked(UnderlineButton _)
|
|
{
|
|
SaveGame().Forget();
|
|
}
|
|
|
|
private void OnSettingsClicked(UnderlineButton _)
|
|
{
|
|
_eventCoordinator?.Publish(new UIToggleSettingsWindow(true, SettingsOpenSource.PauseMenu));
|
|
}
|
|
|
|
private void OnExitClicked(UnderlineButton _)
|
|
{
|
|
_eventCoordinator?.Publish(new FadeEvent(false, 1f));
|
|
ExitInternal().Forget();
|
|
}
|
|
|
|
private void OnQuitToDesktopClicked(UnderlineButton _)
|
|
{
|
|
QuitInternal().Forget();
|
|
}
|
|
|
|
// ── Internal async ────────────────────────────────────────────
|
|
|
|
private async UniTask ExitInternal()
|
|
{
|
|
await UniTask.Delay(TimeSpan.FromSeconds(1));
|
|
|
|
_audioManager?.StopAllAudio();
|
|
|
|
await SaveGame();
|
|
|
|
_eventCoordinator?.Publish(new PauseButtonClickedEvent());
|
|
|
|
await _gameService.LoadMainMenu();
|
|
}
|
|
|
|
private async UniTask QuitInternal()
|
|
{
|
|
_eventCoordinator?.Publish(new FadeEvent(false, 1f));
|
|
|
|
await UniTask.Delay(TimeSpan.FromSeconds(1));
|
|
|
|
await SaveGame();
|
|
|
|
_gameService?.QuitGame();
|
|
}
|
|
|
|
private async UniTask SaveGame()
|
|
{
|
|
if (_saveManager == null)
|
|
return;
|
|
|
|
await _saveManager.SaveGameDataLatest();
|
|
}
|
|
|
|
// ── Layout helpers ────────────────────────────────────────────
|
|
|
|
private void HideButtons()
|
|
{
|
|
SetGroupImmediate(_buttonsGroup, 0f);
|
|
}
|
|
|
|
private static void SetGroupImmediate(CanvasGroup group, float alpha)
|
|
{
|
|
if (group == null)
|
|
return;
|
|
|
|
group.alpha = alpha;
|
|
group.interactable = false;
|
|
group.blocksRaycasts = false;
|
|
}
|
|
|
|
private async UniTask FadeGroup(
|
|
CanvasGroup group,
|
|
float target,
|
|
TweenSettings settings,
|
|
CancellationToken token)
|
|
{
|
|
if (group == null)
|
|
return;
|
|
|
|
await Tween.Alpha(group, new TweenSettings<float>
|
|
{
|
|
startValue = group.alpha,
|
|
endValue = target,
|
|
settings = settings
|
|
}).ToUniTask(cancellationToken: token);
|
|
|
|
group.alpha = target;
|
|
}
|
|
|
|
// ── Selection ─────────────────────────────────────────────────
|
|
|
|
private void SelectDefault()
|
|
{
|
|
if (EventSystem.current == null)
|
|
return;
|
|
|
|
EventSystem.current.SetSelectedGameObject(null);
|
|
|
|
if (_firstSelectedOnOpen != null)
|
|
{
|
|
EventSystem.current.SetSelectedGameObject(
|
|
_firstSelectedOnOpen.gameObject);
|
|
}
|
|
else if (_resumeButton != null)
|
|
{
|
|
EventSystem.current.SetSelectedGameObject(
|
|
_resumeButton.gameObject);
|
|
}
|
|
|
|
Debug.Log(
|
|
$"[PauseMenuWindow] Selected: {EventSystem.current.currentSelectedGameObject}");
|
|
}
|
|
|
|
// ── CTS ───────────────────────────────────────────────────────
|
|
|
|
private void StopAndResetCancellation()
|
|
{
|
|
if (_sequence.isAlive)
|
|
_sequence.Stop();
|
|
|
|
if (_cts != null)
|
|
{
|
|
try
|
|
{
|
|
_cts.Cancel();
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
|
|
_cts.Dispose();
|
|
}
|
|
|
|
_cts = new CancellationTokenSource();
|
|
}
|
|
}
|
|
}
|