Files
AppleHillsProduction/Assets/Scripts/CardSystem/UI/MinigameBoosterGiver.cs
2025-12-15 11:59:40 +01:00

427 lines
16 KiB
C#

using System;
using System.Collections;
using Core;
using Data.CardSystem;
using Pixelplacement;
using UnityEngine;
using UnityEngine.UI;
namespace UI.CardSystem
{
/// <summary>
/// UI component for granting booster packs from minigames.
/// Supports awarding 1 to N booster packs with visual animations.
/// Shows up to 3 booster packs visually. For more than 3, duplicates the first pack and tweens them sequentially.
/// </summary>
public class MinigameBoosterGiver : MonoBehaviour
{
public static MinigameBoosterGiver Instance { get; private set; }
[Header("Visual References")]
[SerializeField] private GameObject visualContainer;
[SerializeField] private RectTransform[] boosterImages; // Up to 3 booster pack visuals
[SerializeField] private RectTransform glowImage; // Single glow effect for all boosters
[SerializeField] private Button continueButton;
[Header("Animation Settings")]
[SerializeField] private float hoverAmount = 20f;
[SerializeField] private float hoverDuration = 1.5f;
[SerializeField] private float glowPulseMax = 1.1f;
[SerializeField] private float glowPulseDuration = 1.2f;
[Header("Disappear Animation")]
[SerializeField] private Vector2 targetBottomLeftOffset = new Vector2(100f, 100f);
[SerializeField] private float tweenDuration = 0.8f;
[SerializeField] private float delayBetweenTweens = 0.2f;
[SerializeField] private float disappearScale = 0.2f;
private Vector3[] _boosterInitialPositions;
private Vector3[] _boosterInitialScales;
private Vector3 _glowInitialScale;
private Coroutine _currentSequence;
private Action _onCompleteCallback;
private int _totalPackCount = 1;
private void Awake()
{
// Singleton pattern
if (Instance != null && Instance != this)
{
Logging.Warning("[MinigameBoosterGiver] Duplicate instance found. Destroying.");
Destroy(gameObject);
return;
}
Instance = this;
// Cache initial values for all booster images
if (boosterImages != null && boosterImages.Length > 0)
{
_boosterInitialPositions = new Vector3[boosterImages.Length];
_boosterInitialScales = new Vector3[boosterImages.Length];
for (int i = 0; i < boosterImages.Length; i++)
{
if (boosterImages[i] != null)
{
_boosterInitialPositions[i] = boosterImages[i].localPosition;
_boosterInitialScales[i] = boosterImages[i].localScale;
}
}
}
if (glowImage != null)
{
_glowInitialScale = glowImage.localScale;
}
// Setup button listener
if (continueButton != null)
{
continueButton.onClick.AddListener(OnContinueClicked);
}
// Start hidden
if (visualContainer != null)
{
visualContainer.SetActive(false);
}
}
/// <summary>
/// Initialize the booster giver with a specific pack count.
/// Shows up to 3 booster visuals based on count.
/// </summary>
/// <param name="packCount">Number of booster packs to grant</param>
public void Initialize(int packCount)
{
_totalPackCount = Mathf.Max(1, packCount);
// Show up to 3 booster visuals
int visualCount = Mathf.Min(_totalPackCount, boosterImages.Length);
for (int i = 0; i < boosterImages.Length; i++)
{
bool shouldShow = i < visualCount;
if (boosterImages[i] != null)
{
boosterImages[i].gameObject.SetActive(shouldShow);
}
}
Logging.Debug($"[MinigameBoosterGiver] Initialized with {_totalPackCount} packs, showing {visualCount} visuals");
}
private void OnDestroy()
{
if (Instance == this)
{
Instance = null;
}
if (continueButton != null)
{
continueButton.onClick.RemoveListener(OnContinueClicked);
}
}
/// <summary>
/// Public API to give a booster pack. Displays UI, starts animations, and waits for user interaction.
/// </summary>
/// <param name="onComplete">Optional callback when the sequence completes and pack is granted</param>
public void GiveBooster(Action onComplete = null)
{
if (_currentSequence != null)
{
Logging.Warning("[MinigameBoosterGiver] Already running a sequence. Ignoring new request.");
return;
}
_onCompleteCallback = onComplete;
_currentSequence = StartCoroutine(GiveBoosterSequence());
}
private IEnumerator GiveBoosterSequence()
{
// Show the visual
if (visualContainer != null)
{
visualContainer.SetActive(true);
}
// Reset positions and scales for visible boosters
int visualCount = Mathf.Min(_totalPackCount, boosterImages.Length);
for (int i = 0; i < visualCount; i++)
{
if (boosterImages[i] != null)
{
boosterImages[i].localPosition = _boosterInitialPositions[i];
boosterImages[i].localScale = _boosterInitialScales[i];
}
}
// Reset glow scale
if (glowImage != null)
{
glowImage.localScale = _glowInitialScale;
}
// Enable the continue button
if (continueButton != null)
{
continueButton.interactable = true;
}
// Start idle hovering animation on all visible boosters (ping-pong)
for (int i = 0; i < visualCount; i++)
{
if (boosterImages[i] != null)
{
Vector3 hoverTarget = _boosterInitialPositions[i] + Vector3.up * hoverAmount;
Tween.LocalPosition(boosterImages[i], hoverTarget, hoverDuration, 0f, Tween.EaseLinear, Tween.LoopType.PingPong);
}
}
// Start pulsing animation on the single glow (ping-pong scale)
if (glowImage != null)
{
Vector3 glowPulseScale = _glowInitialScale * glowPulseMax;
Tween.LocalScale(glowImage, glowPulseScale, glowPulseDuration, 0f, Tween.EaseOut, Tween.LoopType.PingPong);
}
// Wait for button click (handled by OnContinueClicked)
yield return null;
}
private void OnContinueClicked()
{
if (_currentSequence == null)
{
return; // Not in a sequence
}
// Disable and hide button to prevent double-clicks
if (continueButton != null)
{
continueButton.interactable = false;
continueButton.gameObject.SetActive(false);
}
// Start moving all boosters to backpack
StartCoroutine(MoveAllBoostersToBackpack());
}
private IEnumerator MoveAllBoostersToBackpack()
{
// Show scrapbook button temporarily using HUD visibility context
PlayerHudManager.HudVisibilityContext hudContext = null;
GameObject scrapbookButton = PlayerHudManager.Instance?.GetScrabookButton();
if (scrapbookButton != null)
{
hudContext = PlayerHudManager.Instance.ShowElementTemporarily(scrapbookButton);
}
else
{
Logging.Warning("[MinigameBoosterGiver] Scrapbook button not found in PlayerHudManager.");
}
// Calculate target position once
Vector3 targetPosition = GetTargetPosition(scrapbookButton);
int remaining = _totalPackCount;
int visualCount = Mathf.Min(_totalPackCount, boosterImages.Length);
// Stop and fade out the single glow immediately
if (glowImage != null)
{
Tween.Stop(glowImage.GetInstanceID());
Tween.LocalScale(glowImage, Vector3.zero, tweenDuration * 0.5f, 0f, Tween.EaseInBack);
}
// Phase 1: Animate duplicates for packs above 3 (if any)
int duplicatesToAnimate = Mathf.Max(0, _totalPackCount - 3);
for (int i = 0; i < duplicatesToAnimate; i++)
{
// Spawn duplicate at the first booster's current position
if (boosterImages.Length > 0 && boosterImages[0] != null)
{
StartCoroutine(TweenPackToBackpack(boosterImages[0], targetPosition, isDuplicate: true));
}
remaining--;
// Wait for stagger delay before next
if (i < duplicatesToAnimate - 1 || remaining > 0)
{
yield return new WaitForSeconds(delayBetweenTweens);
}
}
// Phase 2: Animate the actual visible packs (up to 3)
for (int i = 0; i < visualCount && i < remaining; i++)
{
if (boosterImages[i] != null)
{
// Stop hover animations first
Tween.Stop(boosterImages[i].GetInstanceID());
// Tween the actual booster
StartCoroutine(TweenPackToBackpack(boosterImages[i], targetPosition, isDuplicate: false));
}
// Wait for stagger delay before next (except after last one)
if (i < visualCount - 1 && i < remaining - 1)
{
yield return new WaitForSeconds(delayBetweenTweens);
}
}
// Wait for the last tween to complete
yield return new WaitForSeconds(tweenDuration);
// Grant all booster packs at once
if (CardSystemManager.Instance != null)
{
CardSystemManager.Instance.AddBoosterPack(_totalPackCount);
Logging.Debug($"[MinigameBoosterGiver] Granted {_totalPackCount} booster pack(s)!");
}
else
{
Logging.Warning("[MinigameBoosterGiver] CardSystemManager not found, cannot grant booster packs.");
}
// Hide scrapbook button by disposing the context
hudContext?.Dispose();
// Hide the visual container
if (visualContainer != null)
{
visualContainer.SetActive(false);
}
// Show button again for next use
if (continueButton != null)
{
continueButton.gameObject.SetActive(true);
}
// Invoke completion callback
_onCompleteCallback?.Invoke();
_onCompleteCallback = null;
// Clear sequence reference
_currentSequence = null;
}
/// <summary>
/// Generic method to tween a booster pack to the backpack button.
/// Handles both duplicates (instantiated clones) and actual booster visuals.
/// </summary>
private IEnumerator TweenPackToBackpack(RectTransform sourceRect, Vector3 targetPosition, bool isDuplicate)
{
RectTransform packToAnimate;
if (isDuplicate)
{
// Create a clone at the source position
GameObject clone = Instantiate(sourceRect.gameObject, sourceRect.parent);
packToAnimate = clone.GetComponent<RectTransform>();
if (packToAnimate != null)
{
packToAnimate.localPosition = sourceRect.localPosition;
packToAnimate.localScale = sourceRect.localScale;
packToAnimate.gameObject.SetActive(true);
}
}
else
{
packToAnimate = sourceRect;
}
if (packToAnimate == null)
{
yield break;
}
// Tween to target position
Tween.LocalPosition(packToAnimate, targetPosition, tweenDuration, 0f, Tween.EaseInBack);
// Scale down
Vector3 targetScale = packToAnimate.localScale * disappearScale;
Tween.LocalScale(packToAnimate, targetScale, tweenDuration, 0f, Tween.EaseInBack);
// Don't wait here - let animations overlap
// Hide/destroy the booster after tween completes (duplicate or not)
// Since we destroy the entire UI instance anyway, just clean up visual artifacts
yield return new WaitForSeconds(tweenDuration);
if (packToAnimate != null)
{
packToAnimate.gameObject.SetActive(false);
}
yield break;
}
/// <summary>
/// Get the target position for the booster pack animation (scrapbook button or fallback).
/// </summary>
private Vector3 GetTargetPosition(GameObject scrapbookButton)
{
if (scrapbookButton != null && boosterImages.Length > 0 && boosterImages[0] != null)
{
RectTransform scrapbookRect = scrapbookButton.GetComponent<RectTransform>();
if (scrapbookRect != null)
{
// Convert scrapbook button's world position to local position relative to booster's parent
Canvas canvas = GetComponentInParent<Canvas>();
if (canvas != null && canvas.renderMode == RenderMode.ScreenSpaceOverlay)
{
// For overlay canvas, convert screen position to local position
Vector2 screenPos = RectTransformUtility.WorldToScreenPoint(null, scrapbookRect.position);
RectTransformUtility.ScreenPointToLocalPointInRectangle(
boosterImages[0].parent as RectTransform,
screenPos,
null,
out Vector2 localPoint);
return localPoint;
}
else
{
// For world space or camera canvas
return boosterImages[0].parent.InverseTransformPoint(scrapbookRect.position);
}
}
}
// Fallback position
return GetFallbackPosition();
}
private Vector3 GetFallbackPosition()
{
RectTransform canvasRect = GetComponentInParent<Canvas>()?.GetComponent<RectTransform>();
if (canvasRect != null)
{
// Convert bottom-left corner with offset to local position
Vector2 bottomLeft = new Vector2(-canvasRect.rect.width / 2f, -canvasRect.rect.height / 2f);
return bottomLeft + targetBottomLeftOffset;
}
else
{
// Ultimate fallback if no canvas found
if (boosterImages.Length > 0 && _boosterInitialPositions != null && _boosterInitialPositions.Length > 0)
{
return _boosterInitialPositions[0] + new Vector3(-500f, -500f, 0f);
}
return new Vector3(-500f, -500f, 0f);
}
}
}
}