Add a dirty booster pack opening sequence

This commit is contained in:
Michal Adam Pikulski
2025-10-21 15:40:47 +02:00
parent c71836c029
commit 00f6d2c6c6
11 changed files with 1874 additions and 436 deletions

View File

@@ -685,6 +685,12 @@ namespace Minigames.DivingForPictures
// Call this when the game ends
public void EndGame()
{
// Start the end game sequence that grants a booster, waits for the UI animation, then shows Game Over.
StartCoroutine(EndGameSequence());
}
private IEnumerator EndGameSequence()
{
// Clean up active monsters
foreach (var monster in activeMonsters.ToArray())
@@ -694,10 +700,38 @@ namespace Minigames.DivingForPictures
monster.DespawnMonster();
}
}
CinematicsManager.Instance.ShowGameOverScreen();
activeMonsters.Clear();
// 1) Call the booster pack giver if available
bool completed = false;
var giver = UI.CardSystem.BoosterPackGiver.Instance;
if (giver != null)
{
// Temporarily subscribe to completion
UnityAction onDone = null;
onDone = () => { completed = true; giver.OnCompleted.RemoveListener(onDone); };
giver.OnCompleted.AddListener(onDone);
giver.GiveBoosterPack();
// 2) Wait for it to finish (with a safety timeout in case it's not wired)
float timeout = 5f; // fallback to avoid blocking forever
float elapsed = 0f;
while (!completed && elapsed < timeout)
{
elapsed += Time.unscaledDeltaTime;
yield return null;
}
}
else
{
// If no giver is present, proceed immediately
Logging.Debug("[DivingGameManager] BoosterPackGiver not found; skipping booster animation.");
}
// 3) Only then show the game over screen
CinematicsManager.Instance.ShowGameOverScreen();
// Final score could be saved to player prefs or other persistence
Logging.Debug($"Final Score: {playerScore}");
}

View File

@@ -0,0 +1,219 @@
using System.Collections;
using Data.CardSystem;
using UnityEngine;
using UnityEngine.Events;
namespace UI.CardSystem
{
/// <summary>
/// One-off helper to visually grant a booster pack.
/// Place this on a UI GameObject with two Image children (a background "glow" and a booster pack image).
/// Access via BoosterPackGiver.Instance and call GiveBoosterPack().
/// The sequence:
/// 1) Shows the object (enables children)
/// 2) Pulses the glow scale for a fixed duration
/// 3) Hides the glow, tweens the booster image towards the backpack icon and scales to zero
/// 4) Invokes OnCompleted
/// </summary>
public class BoosterPackGiver : MonoBehaviour
{
public static BoosterPackGiver Instance { get; private set; }
[Header("References")]
[Tooltip("Canvas that contains these UI elements. If null, will search up the hierarchy.")]
[SerializeField] private Canvas canvas;
[Tooltip("Background glow RectTransform (child image)")]
[SerializeField] private RectTransform backgroundGlow;
[Tooltip("Booster pack image RectTransform (child image)")]
[SerializeField] private RectTransform boosterImage;
[Tooltip("Target RectTransform for the backpack icon (where the booster flies to)")]
[SerializeField] private RectTransform targetBackpackIcon;
[Header("Timing")]
[Tooltip("How long the glow should pulse before the booster flies to the backpack")]
[SerializeField] private float pulseDuration = 2.0f;
[Tooltip("Duration of the flight/scale-down animation")]
[SerializeField] private float moveDuration = 0.6f;
[Header("Glow Pulse")]
[Tooltip("Minimum scale during pulse")] [SerializeField] private float glowScaleMin = 0.9f;
[Tooltip("Maximum scale during pulse")] [SerializeField] private float glowScaleMax = 1.1f;
[Tooltip("Pulse speed in cycles per second")] [SerializeField] private float glowPulseSpeed = 2.0f;
[Header("Move/Scale Easing")]
[SerializeField] private AnimationCurve moveCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
[SerializeField] private AnimationCurve scaleCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
[Header("Behaviour")]
[Tooltip("Hide visuals when the sequence completes")] [SerializeField] private bool hideOnComplete = true;
[Header("Events")]
public UnityEvent OnCompleted;
private Coroutine _sequenceCoroutine;
private Vector3 _boosterInitialScale;
private Vector2 _boosterInitialAnchoredPos;
private IEnumerator Start()
{
if (Instance != null && Instance != this)
{
Debug.LogWarning("[BoosterPackGiver] Duplicate instance detected. Destroying this component.");
Destroy(this);
yield break;
}
Instance = this;
if (canvas == null)
{
canvas = GetComponentInParent<Canvas>();
}
CacheInitialBoosterState();
// Start hidden (keep GameObject active so the singleton remains accessible)
SetVisualsActive(false);
// yield return new WaitForSeconds(1f);
// GiveBoosterPack();
}
private void OnDestroy()
{
if (Instance == this)
Instance = null;
}
private void CacheInitialBoosterState()
{
if (boosterImage != null)
{
_boosterInitialScale = boosterImage.localScale;
_boosterInitialAnchoredPos = boosterImage.anchoredPosition;
}
}
/// <summary>
/// Public entry point: run the grant animation.
/// </summary>
public void GiveBoosterPack()
{
if (backgroundGlow == null || boosterImage == null)
{
Debug.LogError("[BoosterPackGiver] Missing references. Assign Background Glow and Booster Image in the inspector.");
return;
}
// Reset and start fresh
if (_sequenceCoroutine != null)
{
StopCoroutine(_sequenceCoroutine);
_sequenceCoroutine = null;
}
// Ensure canvas reference
if (canvas == null)
{
canvas = GetComponentInParent<Canvas>();
}
// Reset booster transform
boosterImage.localScale = _boosterInitialScale;
boosterImage.anchoredPosition = _boosterInitialAnchoredPos;
// Show visuals
SetVisualsActive(true);
_sequenceCoroutine = StartCoroutine(RunSequence());
}
private IEnumerator RunSequence()
{
// 1) Pulse the glow
float elapsed = 0f;
Vector3 baseGlowScale = backgroundGlow.localScale;
while (elapsed < pulseDuration)
{
elapsed += Time.unscaledDeltaTime;
float t = Mathf.Sin(elapsed * Mathf.PI * 2f * glowPulseSpeed) * 0.5f + 0.5f; // 0..1
float s = Mathf.Lerp(glowScaleMin, glowScaleMax, t);
backgroundGlow.localScale = baseGlowScale * s;
yield return null;
}
// 2) Hide glow
backgroundGlow.gameObject.SetActive(false);
// 3) Move booster to backpack icon and scale to zero
Vector2 startPos = boosterImage.anchoredPosition;
Vector2 targetPos = startPos;
// Convert target to booster parent space if available
if (targetBackpackIcon != null)
{
var parentRect = boosterImage.parent as RectTransform;
if (parentRect != null)
{
Vector2 localPoint;
Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(canvas != null ? canvas.worldCamera : null, targetBackpackIcon.position);
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(parentRect, screenPoint, canvas != null ? canvas.worldCamera : null, out localPoint))
{
targetPos = localPoint;
}
}
}
elapsed = 0f;
while (elapsed < moveDuration)
{
elapsed += Time.unscaledDeltaTime;
float t = Mathf.Clamp01(elapsed / moveDuration);
float mt = moveCurve != null ? moveCurve.Evaluate(t) : t;
float st = scaleCurve != null ? scaleCurve.Evaluate(t) : t;
boosterImage.anchoredPosition = Vector2.LerpUnclamped(startPos, targetPos, mt);
boosterImage.localScale = Vector3.LerpUnclamped(_boosterInitialScale, Vector3.zero, st);
yield return null;
}
// Ensure final state
boosterImage.anchoredPosition = targetPos;
boosterImage.localScale = Vector3.zero;
if (hideOnComplete)
{
SetVisualsActive(false);
// Restore booster for the next run
boosterImage.localScale = _boosterInitialScale;
boosterImage.anchoredPosition = _boosterInitialAnchoredPos;
backgroundGlow.localScale = Vector3.one; // reset pulse scaling
}
_sequenceCoroutine = null;
OnCompleted?.Invoke();
CardSystemManager.Instance.AddBoosterPack(1);
}
private void SetVisualsActive(bool active)
{
if (backgroundGlow != null) backgroundGlow.gameObject.SetActive(active);
if (boosterImage != null) boosterImage.gameObject.SetActive(active);
}
// Optional: quick editor hookup to validate references
#if UNITY_EDITOR
private void OnValidate()
{
if (canvas == null)
{
canvas = GetComponentInParent<Canvas>();
}
if (boosterImage != null && _boosterInitialScale == Vector3.zero)
{
_boosterInitialScale = boosterImage.localScale;
_boosterInitialAnchoredPos = boosterImage.anchoredPosition;
}
}
#endif
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e805057df6a34bd4b881031b5f460fe5
timeCreated: 1761053022

View File

@@ -0,0 +1,89 @@
using Bootstrap;
using Core;
using UnityEngine;
namespace UI.CardSystem
{
/// <summary>
/// One-off helper that shows/hides the Card System root based on scene loads.
/// Attach this to your Card System root GameObject. It subscribes to SceneManagerService.SceneLoadCompleted
/// and applies visibility: hidden in "StartingScene" (configurable), visible in all other gameplay scenes.
/// </summary>
public class CardSystemSceneVisibility : MonoBehaviour
{
[Header("Target Root")]
[Tooltip("The GameObject to show/hide. Defaults to this GameObject if not assigned.")]
[SerializeField] private GameObject targetRoot;
[Header("Rules")]
[Tooltip("The scene name in which the Card System should be hidden.")]
[SerializeField] private string startingSceneName = "StartingScene";
[Tooltip("Also hide when SceneManagerService reports the Bootstrap scene.")]
[SerializeField] private bool hideInBootstrapScene = true;
private void Awake()
{
if (targetRoot == null)
targetRoot = gameObject;
// Defer subscription to after boot so SceneManagerService is guaranteed ready.
BootCompletionService.RegisterInitAction(InitializePostBoot, priority: 95, name: "CardSystem Scene Visibility Init");
}
private void InitializePostBoot()
{
var sceneSvc = SceneManagerService.Instance;
if (sceneSvc == null)
{
Debug.LogWarning("[CardSystemSceneVisibility] SceneManagerService.Instance is null post-boot.");
return;
}
// Subscribe to scene load completion notifications
sceneSvc.SceneLoadCompleted += OnSceneLoaded;
// Apply initial state based on current gameplay scene
ApplyVisibility(sceneSvc.CurrentGameplayScene);
}
private void OnDestroy()
{
var sceneSvc = SceneManagerService.Instance;
if (sceneSvc != null)
{
sceneSvc.SceneLoadCompleted -= OnSceneLoaded;
}
}
private void OnSceneLoaded(string sceneName)
{
ApplyVisibility(sceneName);
}
private void ApplyVisibility(string sceneName)
{
if (targetRoot == null)
return;
if (string.IsNullOrEmpty(sceneName))
return;
if (hideInBootstrapScene && sceneName == "BootstrapScene")
{
SetActiveSafe(false);
return;
}
bool shouldShow = sceneName != startingSceneName;
SetActiveSafe(shouldShow);
}
private void SetActiveSafe(bool active)
{
if (targetRoot.activeSelf != active)
{
targetRoot.SetActive(active);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bd47485d27c34e138bbc5fbd894a3dea
timeCreated: 1761053493