using System.Collections;
using System.Collections.Generic;
using System.Linq;
using AppleHills.Data.CardSystem;
using Data.CardSystem;
using Pixelplacement;
using UI.Core;
using UI.CardSystem.DragDrop;
using UI.DragAndDrop.Core;
using Unity.Cinemachine;
using UnityEngine;
using UnityEngine.UI;
namespace UI.CardSystem
{
///
/// UI page for opening booster packs and displaying the cards received.
/// Manages the entire booster opening flow with drag-and-drop interaction.
///
public class BoosterOpeningPage : UIPage
{
[Header("UI References")]
[SerializeField] private CanvasGroup canvasGroup;
[Header("Booster Management")]
[SerializeField] private GameObject boosterPackPrefab; // Prefab to instantiate new boosters
[SerializeField] private SlotContainer bottomRightSlots; // Holds waiting boosters (max 3)
[SerializeField] private DraggableSlot centerOpeningSlot; // Where booster goes to open
[Header("Card Display")]
[SerializeField] private Transform cardDisplayContainer;
[SerializeField] private GameObject flippableCardPrefab; // Placeholder for card backs
[SerializeField] private float cardSpacing = 150f;
[Header("Settings")]
[SerializeField] private float cardRevealDelay = 0.5f;
[SerializeField] private float boosterDisappearDuration = 0.5f;
[SerializeField] private CinemachineImpulseSource impulseSource;
[SerializeField] private ParticleSystem openingParticleSystem;
[SerializeField] private GameObject albumIcon; // Target for card fly-away animation and dismiss button
private Button _dismissButton; // Button to close/dismiss the booster opening page
private int _availableBoosterCount;
private BoosterPackDraggable _currentBoosterInCenter;
private List _activeBoostersInSlots = new List();
private List _currentRevealedCards = new List();
private CardData[] _currentCardData;
private int _revealedCardCount;
private int _cardsCompletedInteraction; // Track how many cards finished their new/repeat interaction
private bool _isProcessingOpening;
private const int MAX_VISIBLE_BOOSTERS = 3;
private FlippableCard _currentActiveCard; // The card currently awaiting interaction
private void Awake()
{
// Make sure we have a CanvasGroup for transitions
if (canvasGroup == null)
canvasGroup = GetComponent();
if (canvasGroup == null)
canvasGroup = gameObject.AddComponent();
// Get dismiss button from albumIcon GameObject
if (albumIcon != null)
{
_dismissButton = albumIcon.GetComponent();
if (_dismissButton != null)
{
_dismissButton.onClick.AddListener(OnDismissButtonClicked);
}
else
{
Debug.LogWarning("[BoosterOpeningPage] albumIcon does not have a Button component!");
}
}
// UI pages should start disabled
gameObject.SetActive(false);
}
private void OnDestroy()
{
// Unsubscribe from dismiss button
if (_dismissButton != null)
{
_dismissButton.onClick.RemoveListener(OnDismissButtonClicked);
}
// Unsubscribe from slot events
if (centerOpeningSlot != null)
{
centerOpeningSlot.OnOccupied -= OnBoosterPlacedInCenter;
centerOpeningSlot.OnVacated -= OnBoosterRemovedFromCenter;
}
// Unsubscribe from booster events
UnsubscribeFromAllBoosters();
}
///
/// Set the number of available booster packs before showing the page
///
public void SetAvailableBoosterCount(int count)
{
_availableBoosterCount = count;
}
///
/// Called when the dismiss button (albumIcon) is clicked
///
private void OnDismissButtonClicked()
{
if (UIPageController.Instance != null)
{
UIPageController.Instance.PopPage();
Debug.Log("[BoosterOpeningPage] Dismiss button clicked, popping page from stack");
}
}
public override void TransitionIn()
{
base.TransitionIn();
InitializeBoosterDisplay();
}
public override void TransitionOut()
{
CleanupPage();
base.TransitionOut();
}
///
/// Initialize the booster pack display based on available count
///
private void InitializeBoosterDisplay()
{
Debug.Log($"[BoosterOpeningPage] InitializeBoosterDisplay called with {_availableBoosterCount} boosters available");
if (boosterPackPrefab == null)
{
Debug.LogWarning("BoosterOpeningPage: No booster pack prefab assigned!");
return;
}
if (bottomRightSlots == null || bottomRightSlots.SlotCount == 0)
{
Debug.LogWarning("BoosterOpeningPage: No slots available!");
return;
}
// Clear any existing boosters
_activeBoostersInSlots.Clear();
// Calculate how many boosters to show (max 3, or available count, whichever is lower)
int visibleCount = Mathf.Min(_availableBoosterCount, MAX_VISIBLE_BOOSTERS);
Debug.Log($"[BoosterOpeningPage] Will spawn {visibleCount} boosters");
// Spawn boosters and assign to slots
for (int i = 0; i < visibleCount; i++)
{
SpawnBoosterInSlot(i);
}
// Subscribe to center slot events
if (centerOpeningSlot != null)
{
centerOpeningSlot.OnOccupied += OnBoosterPlacedInCenter;
centerOpeningSlot.OnVacated += OnBoosterRemovedFromCenter;
Debug.Log($"[BoosterOpeningPage] Subscribed to center slot events");
}
else
{
Debug.LogWarning("[BoosterOpeningPage] centerOpeningSlot is null!");
}
}
///
/// Spawn a new booster and place it in the specified slot index
///
private void SpawnBoosterInSlot(int slotIndex)
{
DraggableSlot slot = FindSlotByIndex(slotIndex);
if (slot == null)
{
Debug.LogWarning($"[BoosterOpeningPage] Could not find slot with SlotIndex {slotIndex}!");
return;
}
// Instantiate booster
GameObject boosterObj = Instantiate(boosterPackPrefab, slot.transform);
BoosterPackDraggable booster = boosterObj.GetComponent();
if (booster != null)
{
// Reset state
booster.ResetTapCount();
booster.SetTapToOpenEnabled(false);
// Subscribe to events
booster.OnReadyToOpen += OnBoosterReadyToOpen;
// Assign to slot with animation
booster.AssignToSlot(slot, true);
// Track it
_activeBoostersInSlots.Add(booster);
Debug.Log($"[BoosterOpeningPage] Spawned booster in slot with SlotIndex {slotIndex}");
}
else
{
Debug.LogWarning($"[BoosterOpeningPage] Spawned booster has no BoosterPackDraggable component!");
Destroy(boosterObj);
}
}
///
/// Remove and destroy the booster from the specified slot
///
private void RemoveBoosterFromSlot(int slotIndex)
{
if (slotIndex >= _activeBoostersInSlots.Count)
return;
BoosterPackDraggable booster = _activeBoostersInSlots[slotIndex];
if (booster != null)
{
// Unsubscribe from events
booster.OnReadyToOpen -= OnBoosterReadyToOpen;
// Animate out and destroy
Transform boosterTransform = booster.transform;
Tween.LocalScale(boosterTransform, Vector3.zero, 0.3f, 0f, Tween.EaseInBack,
completeCallback: () =>
{
if (booster != null && booster.gameObject != null)
{
Destroy(booster.gameObject);
}
});
// Remove from slot
if (booster.CurrentSlot != null)
{
booster.CurrentSlot.Vacate();
}
Debug.Log($"[BoosterOpeningPage] Removed booster from slot {slotIndex}");
}
_activeBoostersInSlots.RemoveAt(slotIndex);
}
///
/// Update visible boosters based on available count
///
private void UpdateVisibleBoosters()
{
int targetCount = Mathf.Min(_availableBoosterCount, MAX_VISIBLE_BOOSTERS);
// Remove excess boosters (from the end)
while (_activeBoostersInSlots.Count > targetCount)
{
int lastIndex = _activeBoostersInSlots.Count - 1;
RemoveBoosterFromSlot(lastIndex);
}
Debug.Log($"[BoosterOpeningPage] Updated visible boosters: {_activeBoostersInSlots.Count}/{targetCount}");
}
///
/// Shuffle boosters so they always occupy the first available slots
///
private void ShuffleBoostersToFront()
{
if (_activeBoostersInSlots.Count == 0) return;
Debug.Log($"[BoosterOpeningPage] Shuffling {_activeBoostersInSlots.Count} boosters to front slots");
// Unassign all boosters from their current slots
foreach (var booster in _activeBoostersInSlots)
{
if (booster.CurrentSlot != null)
{
booster.CurrentSlot.Vacate();
}
}
// Reassign boosters to first N slots starting from slot with SlotIndex 0
for (int i = 0; i < _activeBoostersInSlots.Count; i++)
{
// Find slot by its actual SlotIndex property
DraggableSlot targetSlot = FindSlotByIndex(i);
BoosterPackDraggable booster = _activeBoostersInSlots[i];
if (targetSlot != null)
{
Debug.Log($"[BoosterOpeningPage] Assigning booster to slot with SlotIndex {i} {targetSlot.name}");
booster.AssignToSlot(targetSlot, true); // Animate the move
}
else
{
Debug.LogWarning($"[BoosterOpeningPage] Could not find slot with SlotIndex {i} {targetSlot.name}");
}
}
}
///
/// Find a slot by its SlotIndex property (not list position)
///
private DraggableSlot FindSlotByIndex(int slotIndex)
{
foreach (var slot in bottomRightSlots.Slots)
{
if (slot.SlotIndex == slotIndex)
{
return slot;
}
}
return null;
}
///
/// Try to spawn a new booster to maintain up to 3 visible
/// Pass decrementCount=true when called after placing a booster in center slot
/// (accounts for the booster that will be consumed)
///
private void TrySpawnNewBooster(bool decrementCount = false)
{
// Can we spawn more boosters?
if (_activeBoostersInSlots.Count >= MAX_VISIBLE_BOOSTERS)
return; // Already at max
// Use decremented count if this is called after placing in center
// (the booster in center will be consumed, so we check against count - 1)
int effectiveCount = decrementCount ? _availableBoosterCount - 1 : _availableBoosterCount;
if (_activeBoostersInSlots.Count >= effectiveCount)
return; // No more boosters available
// Find first available slot by SlotIndex (0, 1, 2)
for (int i = 0; i < MAX_VISIBLE_BOOSTERS; i++)
{
DraggableSlot slot = FindSlotByIndex(i);
if (slot != null && !slot.IsOccupied)
{
SpawnBoosterInSlot(i);
Debug.Log($"[BoosterOpeningPage] Spawned new booster in slot with SlotIndex {i}");
break;
}
}
}
///
/// Handle when a booster is placed in the center opening slot
///
private void OnBoosterPlacedInCenter(DraggableObject draggable)
{
BoosterPackDraggable booster = draggable as BoosterPackDraggable;
if (booster == null) return;
_currentBoosterInCenter = booster;
// Remove from active slots list
_activeBoostersInSlots.Remove(booster);
// Lock the slot so it can't be dragged out
Debug.Log($"[BoosterOpeningPage] Locking center slot. IsLocked before: {centerOpeningSlot.IsLocked}");
centerOpeningSlot.SetLocked(true);
Debug.Log($"[BoosterOpeningPage] IsLocked after: {centerOpeningSlot.IsLocked}");
// Configure booster for opening (disables drag, enables tapping, resets tap count)
Debug.Log($"[BoosterOpeningPage] Calling SetInOpeningSlot(true) on booster");
booster.SetInOpeningSlot(true);
// Subscribe to tap events for visual feedback
booster.OnTapped += OnBoosterTapped;
booster.OnReadyToOpen += OnBoosterReadyToOpen;
booster.OnBoosterOpened += OnBoosterOpened;
// Try to spawn a new booster to maintain 3 visible
// Use decrementCount=true because this booster will be consumed
TrySpawnNewBooster(decrementCount: true);
// Shuffle remaining boosters to occupy the first slots
ShuffleBoostersToFront();
Debug.Log($"[BoosterOpeningPage] Booster placed in center, ready for taps. Active boosters in slots: {_activeBoostersInSlots.Count}");
}
///
/// Handle when a booster is removed from the center opening slot
///
private void OnBoosterRemovedFromCenter(DraggableObject draggable)
{
BoosterPackDraggable booster = draggable as BoosterPackDraggable;
if (booster == null) return;
// If it's being removed back to a corner slot, add it back to tracking
if (booster.CurrentSlot != null && bottomRightSlots.HasSlot(booster.CurrentSlot))
{
_activeBoostersInSlots.Add(booster);
booster.SetInOpeningSlot(false);
}
_currentBoosterInCenter = null;
Debug.Log($"[BoosterOpeningPage] Booster removed from center");
}
private void OnBoosterTapped(BoosterPackDraggable booster, int currentTaps, int maxTaps)
{
Debug.Log($"[BoosterOpeningPage] Booster tapped: {currentTaps}/{maxTaps}");
// Fire Cinemachine impulse with random velocity (excluding Z)
if (impulseSource != null)
{
// Generate random velocity vector (X and Y only, Z = 0)
Vector3 randomVelocity = new Vector3(
Random.Range(-1f, 1f),
Random.Range(-1f, 1f),
0f
);
// Normalize to ensure consistent strength
randomVelocity.Normalize();
// Generate the impulse with strength 1 and random velocity
impulseSource.GenerateImpulse(randomVelocity);
}
}
///
/// Handle when booster is opened - play particle effects
///
private void OnBoosterOpened(BoosterPackDraggable booster)
{
Debug.Log($"[BoosterOpeningPage] Booster opened, playing particle effect");
// Reset and play particle system
if (openingParticleSystem != null)
{
// Stop any existing playback
if (openingParticleSystem.isPlaying)
{
openingParticleSystem.Stop();
}
// Clear existing particles
openingParticleSystem.Clear();
// Play the particle system
openingParticleSystem.Play();
}
}
///
/// Handle tap-to-place: When player taps a booster in bottom slots, move it to center
///
public void OnBoosterTappedInBottomSlot(BoosterPackDraggable booster)
{
if (_currentBoosterInCenter != null || centerOpeningSlot == null)
return; // Center slot already occupied
// Move booster to center slot
booster.AssignToSlot(centerOpeningSlot, true);
}
///
/// Handle when booster is ready to open (after max taps)
///
private void OnBoosterReadyToOpen(BoosterPackDraggable booster)
{
if (_isProcessingOpening) return;
Debug.Log($"[BoosterOpeningPage] Booster ready to open!");
// Trigger the actual opening sequence
booster.TriggerOpen();
StartCoroutine(ProcessBoosterOpening(booster));
}
///
/// Process the booster opening sequence
///
private IEnumerator ProcessBoosterOpening(BoosterPackDraggable booster)
{
_isProcessingOpening = true;
// Call CardSystemManager to open the pack
if (CardSystemManager.Instance != null)
{
List revealedCardsList = CardSystemManager.Instance.OpenBoosterPack();
_currentCardData = revealedCardsList.ToArray();
// Animate booster disappearing
yield return StartCoroutine(AnimateBoosterDisappear(booster));
// Decrement available count
_availableBoosterCount--;
// Update visible boosters (remove from end if we drop below thresholds)
UpdateVisibleBoosters();
// Show card backs
SpawnCardBacks(_currentCardData.Length);
// Wait for player to reveal all cards
bool isLastBooster = _availableBoosterCount <= 0;
yield return StartCoroutine(WaitForCardReveals());
// Check if this was the last booster pack
if (isLastBooster)
{
// Wait for all card animations to complete before transitioning
// WaitForCardReveals already includes: 0.5s wait + (cardCount * 0.5s stagger) + 0.5s animation + 0.5s final
// Total is: 1.5s + (cardCount * 0.5s)
// For 5 cards that's 4 seconds total, which should be enough
Debug.Log("[BoosterOpeningPage] Last booster opened, auto-transitioning to album main page");
if (UIPageController.Instance != null)
{
UIPageController.Instance.PopPage();
}
}
}
_isProcessingOpening = false;
}
///
/// Animate the booster pack disappearing
///
private IEnumerator AnimateBoosterDisappear(BoosterPackDraggable booster)
{
if (booster == null) yield break;
// Scale down and fade out
Transform boosterTransform = booster.transform;
Tween.LocalScale(boosterTransform, Vector3.zero, boosterDisappearDuration, 0f, Tween.EaseInBack);
// Also fade the visual if it has a CanvasGroup
CanvasGroup boosterCg = booster.GetComponentInChildren();
if (boosterCg != null)
{
Tween.Value(1f, 0f, (val) => boosterCg.alpha = val, boosterDisappearDuration, 0f);
}
yield return new WaitForSeconds(boosterDisappearDuration);
// Destroy the booster
Destroy(booster.gameObject);
_currentBoosterInCenter = null;
// Unlock center slot
centerOpeningSlot.SetLocked(false);
}
///
/// Spawn card back placeholders for revealing
///
private void SpawnCardBacks(int count)
{
if (flippableCardPrefab == null || cardDisplayContainer == null)
{
Debug.LogWarning("BoosterOpeningPage: Missing card prefab or container!");
return;
}
_currentRevealedCards.Clear();
_revealedCardCount = 0;
_cardsCompletedInteraction = 0; // Reset interaction count
// Calculate positions
float totalWidth = (count - 1) * cardSpacing;
float startX = -totalWidth / 2f;
for (int i = 0; i < count; i++)
{
GameObject cardObj = Instantiate(flippableCardPrefab, cardDisplayContainer);
RectTransform cardRect = cardObj.GetComponent();
if (cardRect != null)
{
cardRect.anchoredPosition = new Vector2(startX + (i * cardSpacing), 0);
}
// Get FlippableCard component and setup the card data
FlippableCard flippableCard = cardObj.GetComponent();
if (flippableCard != null)
{
// Setup the card data (stored but not revealed yet)
flippableCard.SetupCard(_currentCardData[i]);
// Subscribe to flip started event (to disable other cards IMMEDIATELY)
int cardIndex = i; // Capture for closure
flippableCard.OnFlipStarted += OnCardFlipStarted;
// Subscribe to reveal event to track when flipped
flippableCard.OnCardRevealed += (card, data) => OnCardRevealed(cardIndex);
// Subscribe to inactive click event (for jiggle effect)
flippableCard.OnClickedWhileInactive += OnCardClickedWhileInactive;
// Initially, all cards are clickable (for flipping)
flippableCard.SetClickable(true);
}
else
{
Debug.LogWarning($"[BoosterOpeningPage] FlippableCard component not found on card {i}!");
}
_currentRevealedCards.Add(cardObj);
// Animate cards flying in
cardRect.localScale = Vector3.zero;
Tween.LocalScale(cardRect, Vector3.one, 0.3f, i * 0.1f, Tween.EaseOutBack);
}
}
///
/// Handle when a card flip starts (disable all other cards IMMEDIATELY)
///
private void OnCardFlipStarted(FlippableCard flippingCard)
{
Debug.Log($"[BoosterOpeningPage] Card flip started, disabling all other cards.");
// Disable ALL cards immediately to prevent multi-flip
foreach (GameObject cardObj in _currentRevealedCards)
{
FlippableCard card = cardObj.GetComponent();
if (card != null)
{
card.SetClickable(false);
}
}
}
///
/// Handle card reveal (when flipped)
///
private void OnCardRevealed(int cardIndex)
{
Debug.Log($"[BoosterOpeningPage] Card {cardIndex} revealed!");
_revealedCardCount++;
// Get the flippable card and card data
FlippableCard flippableCard = _currentRevealedCards[cardIndex].GetComponent();
if (flippableCard == null)
{
Debug.LogWarning($"[BoosterOpeningPage] FlippableCard not found for card {cardIndex}!");
return;
}
CardData cardData = flippableCard.CardData;
// Check if this is a new card using CardSystemManager
bool isNew = Data.CardSystem.CardSystemManager.Instance.IsCardNew(cardData, out CardData existingCard);
if (isNew)
{
Debug.Log($"[BoosterOpeningPage] Card '{cardData.Name}' is NEW!");
flippableCard.ShowAsNew();
}
else
{
// Check if card is already Legendary - if so, skip progress bar and auto-progress
if (existingCard.Rarity == AppleHills.Data.CardSystem.CardRarity.Legendary)
{
Debug.Log($"[BoosterOpeningPage] Card '{cardData.Name}' is LEGENDARY - auto-progressing!");
// Add to inventory immediately and move to next card
Data.CardSystem.CardSystemManager.Instance.AddCardToInventoryDelayed(cardData);
_cardsCompletedInteraction++;
_revealedCardCount++; // This was already incremented earlier, but we need to track completion
EnableUnrevealedCards();
return; // Skip showing the card enlarged
}
int ownedCount = existingCard.CopiesOwned;
Debug.Log($"[BoosterOpeningPage] Card '{cardData.Name}' is a REPEAT! Owned: {ownedCount}");
// Check if this card will trigger an upgrade (ownedCount + 1 >= threshold)
bool willUpgrade = (ownedCount + 1) >= flippableCard.CardsToUpgrade && existingCard.Rarity < AppleHills.Data.CardSystem.CardRarity.Legendary;
if (willUpgrade)
{
Debug.Log($"[BoosterOpeningPage] This card will trigger upgrade! ({ownedCount + 1}/{flippableCard.CardsToUpgrade})");
// Show as repeat - progress bar will fill and auto-trigger upgrade
flippableCard.ShowAsRepeatWithUpgrade(ownedCount, existingCard);
}
else
{
// Normal repeat, no upgrade
flippableCard.ShowAsRepeat(ownedCount);
}
}
// Set this card as the active one (only this card is clickable now)
SetActiveCard(flippableCard);
// Subscribe to tap event to know when interaction is complete
flippableCard.OnCardTappedAfterReveal += (card) => OnCardCompletedInteraction(card, cardIndex);
}
///
/// Handle when a card's interaction is complete (tapped after reveal)
///
private void OnCardCompletedInteraction(FlippableCard card, int cardIndex)
{
Debug.Log($"[BoosterOpeningPage] Card {cardIndex} interaction complete!");
// Add card to inventory NOW (after player saw it)
Data.CardSystem.CardSystemManager.Instance.AddCardToInventoryDelayed(card.CardData);
// Return card to normal size
card.ReturnToNormalSize();
// Increment completed interaction count
_cardsCompletedInteraction++;
// Clear active card
_currentActiveCard = null;
// Re-enable all unrevealed cards (they can be flipped now)
EnableUnrevealedCards();
Debug.Log($"[BoosterOpeningPage] Cards completed interaction: {_cardsCompletedInteraction}/{_currentCardData.Length}");
}
///
/// Set which card is currently active (only this card can be clicked)
///
private void SetActiveCard(FlippableCard activeCard)
{
_currentActiveCard = activeCard;
// Disable all other cards
foreach (GameObject cardObj in _currentRevealedCards)
{
FlippableCard card = cardObj.GetComponent();
if (card != null)
{
// Only the active card is clickable
card.SetClickable(card == activeCard);
}
}
Debug.Log($"[BoosterOpeningPage] Set active card. Only one card is now clickable.");
}
///
/// Re-enable all unrevealed cards (allow them to be flipped)
///
private void EnableUnrevealedCards()
{
foreach (GameObject cardObj in _currentRevealedCards)
{
FlippableCard card = cardObj.GetComponent();
if (card != null && !card.IsFlipped)
{
card.SetClickable(true);
}
}
Debug.Log($"[BoosterOpeningPage] Re-enabled unrevealed cards for flipping.");
}
///
/// Handle when a card is clicked while not active (jiggle the active card)
///
private void OnCardClickedWhileInactive(FlippableCard inactiveCard)
{
Debug.Log($"[BoosterOpeningPage] Inactive card clicked, jiggling active card.");
if (_currentActiveCard != null)
{
_currentActiveCard.Jiggle();
}
}
///
/// Wait until all cards are revealed AND all interactions are complete
///
private IEnumerator WaitForCardReveals()
{
// Wait until all cards are flipped
while (_revealedCardCount < _currentCardData.Length)
{
yield return null;
}
Debug.Log($"[BoosterOpeningPage] All cards revealed! Waiting for interactions...");
// Wait until all cards have completed their new/repeat interaction
while (_cardsCompletedInteraction < _currentCardData.Length)
{
yield return null;
}
Debug.Log($"[BoosterOpeningPage] All interactions complete! Animating cards to album...");
// All cards revealed and interacted with, wait a moment
yield return new WaitForSeconds(0.5f);
// Animate cards to album icon (or center if no icon assigned) with staggered delays
Vector3 targetPosition = albumIcon != null ? albumIcon.transform.position : Vector3.zero;
int cardIndex = 0;
foreach (GameObject cardObj in _currentRevealedCards)
{
if (cardObj != null)
{
// Stagger each card with 0.5s delay
float delay = cardIndex * 0.5f;
// Animate to album icon position, then destroy
Tween.Position(cardObj.transform, targetPosition, 0.5f, delay, Tween.EaseInBack);
Tween.LocalScale(cardObj.transform, Vector3.zero, 0.5f, delay, Tween.EaseInBack,
completeCallback: () => Destroy(cardObj));
cardIndex++;
}
}
// Wait for all animations to complete
// Last card starts at: (cardCount - 1) * 0.5s delay
// Last card finishes at: (cardCount - 1) * 0.5s + 0.5s animation duration = cardCount * 0.5s
float totalAnimationTime = _currentCardData.Length * 0.5f;
_currentRevealedCards.Clear();
yield return new WaitForSeconds(totalAnimationTime);
}
///
/// Clean up the page when hidden
///
private void CleanupPage()
{
UnsubscribeFromAllBoosters();
// Destroy all active boosters
foreach (var booster in _activeBoostersInSlots.ToList())
{
if (booster != null && booster.gameObject != null)
Destroy(booster.gameObject);
}
_activeBoostersInSlots.Clear();
// Clear any remaining cards
foreach (GameObject card in _currentRevealedCards)
{
if (card != null)
Destroy(card);
}
_currentRevealedCards.Clear();
_currentBoosterInCenter = null;
_isProcessingOpening = false;
}
///
/// Unsubscribe from all booster events
///
private void UnsubscribeFromAllBoosters()
{
// Unsubscribe from active boosters in slots
foreach (var booster in _activeBoostersInSlots)
{
if (booster != null)
{
booster.OnReadyToOpen -= OnBoosterReadyToOpen;
booster.OnTapped -= OnBoosterTapped;
booster.OnBoosterOpened -= OnBoosterOpened;
}
}
// Unsubscribe from center booster
if (_currentBoosterInCenter != null)
{
_currentBoosterInCenter.OnReadyToOpen -= OnBoosterReadyToOpen;
_currentBoosterInCenter.OnTapped -= OnBoosterTapped;
_currentBoosterInCenter.OnBoosterOpened -= OnBoosterOpened;
}
}
protected override void DoTransitionIn(System.Action onComplete)
{
// Simple fade in animation
if (canvasGroup != null)
{
canvasGroup.alpha = 0f;
Tween.Value(0f, 1f, (value) => canvasGroup.alpha = value, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, onComplete, obeyTimescale: false);
}
else
{
// Fallback if no CanvasGroup
onComplete?.Invoke();
}
}
protected override void DoTransitionOut(System.Action onComplete)
{
// Simple fade out animation
if (canvasGroup != null)
{
Tween.Value(canvasGroup.alpha, 0f, (value) => canvasGroup.alpha = value, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, onComplete, obeyTimescale: false);
}
else
{
// Fallback if no CanvasGroup
onComplete?.Invoke();
}
}
}
}