Revamp game pausing and input handling. Fix minigame tutorial and end sequence. (#39)
- Revamp pausing and centralize management in GameManager - Switch Pause implementation to be counter-based to solve corner case of multiple pause requests - Remove duplicated Pause logic from other components - Add pausing when browsing the card album - Fully deliver the exclusive UI implementation - Spruce up the MiniGame tutorial with correct pausing, hiding other UI - Correctly unpause after showing tutorial - Fix minigame ending sequence. The cinematic correctly plays only once now - Replaying the minigame works Co-authored-by: Michal Adam Pikulski <michal@foolhardyhorizons.com> Co-authored-by: Michal Pikulski <michal@foolhardyhorizons.com> Reviewed-on: #39
This commit is contained in:
@@ -1,175 +1,205 @@
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Pixelplacement;
|
||||
using Minigames.DivingForPictures;
|
||||
using Bootstrap;
|
||||
using Core;
|
||||
using Input;
|
||||
using UnityEngine.Events;
|
||||
using Pixelplacement;
|
||||
using UI.Core;
|
||||
using UnityEngine;
|
||||
|
||||
public class DivingTutorial : MonoBehaviour, ITouchInputConsumer
|
||||
namespace UI.Tutorial
|
||||
{
|
||||
private StateMachine stateMachine;
|
||||
public DivingGameManager divingGameManager;
|
||||
public bool playTutorial;
|
||||
|
||||
// gating for input until current state's animation finishes first loop
|
||||
private bool canAcceptInput = false;
|
||||
private Coroutine waitLoopCoroutine;
|
||||
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
public class DivingTutorial : MonoBehaviour, ITouchInputConsumer
|
||||
{
|
||||
if (playTutorial == true)
|
||||
private StateMachine _stateMachine;
|
||||
public bool playTutorial;
|
||||
|
||||
// gating for input until current state's animation finishes first loop
|
||||
[SerializeField] private GameObject tapPrompt;
|
||||
|
||||
private bool _canAcceptInput;
|
||||
private Coroutine _waitLoopCoroutine;
|
||||
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
InitializeTutorial();
|
||||
BootCompletionService.RegisterInitAction(InitializeTutorial);
|
||||
|
||||
// Ensure prompt is hidden initially (even before tutorial initialization)
|
||||
if (tapPrompt != null)
|
||||
tapPrompt.SetActive(false);
|
||||
}
|
||||
else { RemoveTutorial(); }
|
||||
}
|
||||
|
||||
void InitializeTutorial()
|
||||
{
|
||||
stateMachine = GetComponentInChildren<StateMachine>();
|
||||
divingGameManager.Pause();
|
||||
InputManager.Instance.RegisterOverrideConsumer(this);
|
||||
stateMachine.OnLastStateExited.AddListener(RemoveTutorial);
|
||||
|
||||
// prepare gating for the initial active state
|
||||
SetupInputGateForCurrentState();
|
||||
}
|
||||
|
||||
void RemoveTutorial()
|
||||
{
|
||||
Debug.Log("Remove me!");
|
||||
if (waitLoopCoroutine != null)
|
||||
void InitializeTutorial()
|
||||
{
|
||||
StopCoroutine(waitLoopCoroutine);
|
||||
waitLoopCoroutine = null;
|
||||
}
|
||||
InputManager.Instance.UnregisterOverrideConsumer(this);
|
||||
divingGameManager.DoResume();
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
public void OnTap(Vector2 position)
|
||||
{
|
||||
if (!canAcceptInput) return; // block taps until allowed
|
||||
|
||||
// consume this tap and immediately block further taps
|
||||
canAcceptInput = false;
|
||||
|
||||
// move to next state
|
||||
stateMachine.Next(true);
|
||||
|
||||
// after the state changes, set up gating for the new active state's animation
|
||||
SetupInputGateForCurrentState();
|
||||
}
|
||||
|
||||
public void OnHoldStart(Vector2 position)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public void OnHoldMove(Vector2 position)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public void OnHoldEnd(Vector2 position)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
private void SetupInputGateForCurrentState()
|
||||
{
|
||||
if (waitLoopCoroutine != null)
|
||||
{
|
||||
StopCoroutine(waitLoopCoroutine);
|
||||
waitLoopCoroutine = null;
|
||||
}
|
||||
waitLoopCoroutine = StartCoroutine(WaitForFirstLoopOnActiveState());
|
||||
}
|
||||
|
||||
private IEnumerator WaitForFirstLoopOnActiveState()
|
||||
{
|
||||
// wait a frame to ensure StateMachine has activated the correct state GameObject
|
||||
yield return null;
|
||||
|
||||
// find the active child under the StateMachine (the current state)
|
||||
Transform smTransform = stateMachine != null ? stateMachine.transform : transform;
|
||||
Transform activeState = null;
|
||||
for (int i = 0; i < smTransform.childCount; i++)
|
||||
{
|
||||
var child = smTransform.GetChild(i);
|
||||
if (child.gameObject.activeInHierarchy)
|
||||
if (playTutorial)
|
||||
{
|
||||
activeState = child;
|
||||
break;
|
||||
// pause the game, hide UI, and register for input overrides
|
||||
GameManager.Instance.RequestPause(this);
|
||||
UIPageController.Instance.HideAllUI();
|
||||
InputManager.Instance.RegisterOverrideConsumer(this);
|
||||
|
||||
// Setup references
|
||||
_stateMachine = GetComponentInChildren<StateMachine>();
|
||||
_stateMachine.OnLastStateExited.AddListener(RemoveTutorial);
|
||||
|
||||
// prepare gating for the initial active state
|
||||
SetupInputGateForCurrentState();
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveTutorial();
|
||||
}
|
||||
}
|
||||
|
||||
if (activeState == null)
|
||||
void RemoveTutorial()
|
||||
{
|
||||
// if we can't find an active state, fail open: allow input
|
||||
canAcceptInput = true;
|
||||
yield break;
|
||||
}
|
||||
|
||||
// look for a legacy Animation component on the active state
|
||||
var anim = activeState.GetComponent<Animation>();
|
||||
if (anim == null)
|
||||
{
|
||||
// no animation to wait for; allow input immediately
|
||||
canAcceptInput = true;
|
||||
yield break;
|
||||
}
|
||||
|
||||
// determine a clip/state to observe
|
||||
string clipName = anim.clip != null ? anim.clip.name : null;
|
||||
AnimationState observedState = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(clipName))
|
||||
{
|
||||
observedState = anim[clipName];
|
||||
}
|
||||
else
|
||||
{
|
||||
// fallback: take the first enabled state in the Animation
|
||||
foreach (AnimationState st in anim)
|
||||
Debug.Log("Remove me!");
|
||||
if (_waitLoopCoroutine != null)
|
||||
{
|
||||
observedState = st;
|
||||
break;
|
||||
StopCoroutine(_waitLoopCoroutine);
|
||||
_waitLoopCoroutine = null;
|
||||
}
|
||||
|
||||
// Unpause, unregister input, and show UI
|
||||
InputManager.Instance.UnregisterOverrideConsumer(this);
|
||||
UIPageController.Instance.ShowAllUI();
|
||||
GameManager.Instance.ReleasePause(this);
|
||||
|
||||
// hide prompt if present
|
||||
if (tapPrompt != null)
|
||||
tapPrompt.SetActive(false);
|
||||
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
public void OnTap(Vector2 position)
|
||||
{
|
||||
if (!_canAcceptInput) return; // block taps until allowed
|
||||
|
||||
// consume this tap and immediately block further taps
|
||||
SetInputEnabled(false);
|
||||
|
||||
// move to next state
|
||||
_stateMachine.Next(true);
|
||||
|
||||
// after the state changes, set up gating for the new active state's animation
|
||||
SetupInputGateForCurrentState();
|
||||
}
|
||||
|
||||
public void OnHoldStart(Vector2 position)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnHoldMove(Vector2 position)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnHoldEnd(Vector2 position)
|
||||
{
|
||||
}
|
||||
|
||||
// centralize enabling/disabling input and the tap prompt
|
||||
private void SetInputEnabled(bool allow)
|
||||
{
|
||||
_canAcceptInput = allow;
|
||||
if (tapPrompt != null)
|
||||
{
|
||||
tapPrompt.SetActive(allow);
|
||||
}
|
||||
}
|
||||
|
||||
if (observedState == null)
|
||||
private void SetupInputGateForCurrentState()
|
||||
{
|
||||
// nothing to observe; allow input
|
||||
canAcceptInput = true;
|
||||
yield break;
|
||||
if (_waitLoopCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_waitLoopCoroutine);
|
||||
_waitLoopCoroutine = null;
|
||||
}
|
||||
_waitLoopCoroutine = StartCoroutine(WaitForFirstLoopOnActiveState());
|
||||
}
|
||||
|
||||
// wait until the animation starts playing the observed clip
|
||||
float safetyTimer = 0f;
|
||||
while (anim.isActiveAndEnabled && activeState.gameObject.activeInHierarchy && !anim.IsPlaying(observedState.name) && safetyTimer < 2f)
|
||||
private IEnumerator WaitForFirstLoopOnActiveState()
|
||||
{
|
||||
safetyTimer += Time.deltaTime;
|
||||
// wait a frame to ensure StateMachine has activated the correct state GameObject
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// wait until the first loop completes (normalizedTime >= 1)
|
||||
while (anim.isActiveAndEnabled && activeState.gameObject.activeInHierarchy)
|
||||
{
|
||||
// if state changed (not playing anymore), allow input to avoid deadlock
|
||||
if (!anim.IsPlaying(observedState.name)) break;
|
||||
|
||||
if (observedState.normalizedTime >= 1f)
|
||||
// find the active child under the StateMachine (the current state)
|
||||
Transform smTransform = _stateMachine != null ? _stateMachine.transform : transform;
|
||||
Transform activeState = null;
|
||||
for (int i = 0; i < smTransform.childCount; i++)
|
||||
{
|
||||
break;
|
||||
var child = smTransform.GetChild(i);
|
||||
if (child.gameObject.activeInHierarchy)
|
||||
{
|
||||
activeState = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
|
||||
canAcceptInput = true;
|
||||
waitLoopCoroutine = null;
|
||||
if (activeState == null)
|
||||
{
|
||||
// if we can't find an active state, fail open: allow input
|
||||
SetInputEnabled(true);
|
||||
yield break;
|
||||
}
|
||||
|
||||
// look for a legacy Animation component on the active state
|
||||
var anim = activeState.GetComponent<Animation>();
|
||||
if (anim == null)
|
||||
{
|
||||
// no animation to wait for; allow input immediately
|
||||
SetInputEnabled(true);
|
||||
yield break;
|
||||
}
|
||||
|
||||
// determine a clip/state to observe
|
||||
string clipName = anim.clip != null ? anim.clip.name : null;
|
||||
AnimationState observedState = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(clipName))
|
||||
{
|
||||
observedState = anim[clipName];
|
||||
}
|
||||
else
|
||||
{
|
||||
// fallback: take the first enabled state in the Animation
|
||||
foreach (AnimationState st in anim)
|
||||
{
|
||||
observedState = st;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (observedState == null)
|
||||
{
|
||||
// nothing to observe; allow input
|
||||
SetInputEnabled(true);
|
||||
yield break;
|
||||
}
|
||||
|
||||
// wait until the animation starts playing the observed clip
|
||||
float safetyTimer = 0f;
|
||||
while (anim.isActiveAndEnabled && activeState.gameObject.activeInHierarchy && !anim.IsPlaying(observedState.name) && safetyTimer < 2f)
|
||||
{
|
||||
safetyTimer += Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// wait until the first loop completes (normalizedTime >= 1)
|
||||
while (anim.isActiveAndEnabled && activeState.gameObject.activeInHierarchy)
|
||||
{
|
||||
// if state changed (not playing anymore), allow input to avoid deadlock
|
||||
if (!anim.IsPlaying(observedState.name)) break;
|
||||
|
||||
if (observedState.normalizedTime >= 1f)
|
||||
{
|
||||
break;
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
|
||||
SetInputEnabled(true);
|
||||
_waitLoopCoroutine = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user