220 lines
8.2 KiB
C#
220 lines
8.2 KiB
C#
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
|
|
}
|
|
}
|