Add a dirty booster pack opening sequence
This commit is contained in:
219
Assets/Scripts/UI/CardSystem/BoosterPackGiver.cs
Normal file
219
Assets/Scripts/UI/CardSystem/BoosterPackGiver.cs
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user