# Lifecycle Management & Save System Revamp
## Overview
Complete overhaul of game lifecycle management, interactable system, and save/load architecture. Introduces centralized `ManagedBehaviour` base class for consistent initialization ordering and lifecycle hooks across all systems.
## Core Architecture
### New Lifecycle System
- **`LifecycleManager`**: Centralized coordinator for all managed objects
- **`ManagedBehaviour`**: Base class replacing ad-hoc initialization patterns
- `OnManagedAwake()`: Priority-based initialization (0-100, lower = earlier)
- `OnSceneReady()`: Scene-specific setup after managers ready
- Replaces `BootCompletionService` (deleted)
- **Priority groups**: Infrastructure (0-20) → Game Systems (30-50) → Data (60-80) → UI/Gameplay (90-100)
- **Editor support**: `EditorLifecycleBootstrap` ensures lifecycle works in editor mode
### Unified SaveID System
- Consistent format: `{ParentName}_{ComponentType}`
- Auto-registration via `AutoRegisterForSave = true`
- New `DebugSaveIds` editor tool for inspection
## Save/Load Improvements
### Enhanced State Management
- **Extended SaveLoadData**: Unlocked minigames, card collection states, combination items, slot occupancy
- **Async loading**: `ApplyCardCollectionState()` waits for card definitions before restoring
- **New `SaveablePlayableDirector`**: Timeline sequences save/restore playback state
- **Fixed race conditions**: Proper initialization ordering prevents data corruption
## Interactable & Pickup System
- Migrated to `OnManagedAwake()` for consistent initialization
- Template method pattern for state restoration (`RestoreInteractionState()`)
- Fixed combination item save/load bugs (items in slots vs. follower hand)
- Dynamic spawning support for combined items on load
- **Breaking**: `Interactable.Awake()` now sealed, use `OnManagedAwake()` instead
## UI System Changes
- **AlbumViewPage** and **BoosterNotificationDot**: Migrated to `ManagedBehaviour`
- **Fixed menu persistence bug**: Menus no longer reappear after scene transitions
- **Pause Menu**: Now reacts to all scene loads (not just first scene)
- **Orientation Enforcer**: Enforces per-scene via `SceneManagementService`
- **Loading Screen**: Integrated with new lifecycle
## ⚠️ Breaking Changes
1. **`BootCompletionService` removed** → Use `ManagedBehaviour.OnManagedAwake()` with priority
2. **`Interactable.Awake()` sealed** → Override `OnManagedAwake()` instead
3. **SaveID format changed** → Now `{ParentName}_{ComponentType}` consistently
4. **MonoBehaviours needing init ordering** → Must inherit from `ManagedBehaviour`
Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com>
Co-authored-by: Michal Pikulski <michal@foolhardyhorizons.com>
Reviewed-on: #51
745 lines
28 KiB
C#
745 lines
28 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using AppleHills.Data.CardSystem;
|
|
using Data.CardSystem;
|
|
using Pixelplacement;
|
|
using UI.Core;
|
|
using UI.DragAndDrop.Core;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
namespace UI.CardSystem
|
|
{
|
|
/// <summary>
|
|
/// UI page for viewing the player's card collection in an album.
|
|
/// Manages booster pack button visibility and opening flow.
|
|
/// </summary>
|
|
public class AlbumViewPage : UIPage
|
|
{
|
|
[Header("UI References")]
|
|
[SerializeField] private CanvasGroup canvasGroup;
|
|
[SerializeField] private Button exitButton;
|
|
[SerializeField] private BookCurlPro.BookPro book;
|
|
|
|
[Header("Zone Navigation")]
|
|
[SerializeField] private BookTabButton[] zoneTabs; // All zone tab buttons
|
|
|
|
[Header("Album Card Reveal")]
|
|
[SerializeField] private SlotContainer bottomRightSlots;
|
|
[SerializeField] private GameObject albumCardPlacementPrefab; // The wrapper prefab with flip/drag (AlbumPlacementCard)
|
|
|
|
[Header("Card Enlarge System")]
|
|
[SerializeField] private GameObject cardEnlargedBackdrop; // Backdrop to block interactions
|
|
[SerializeField] private Transform cardEnlargedContainer; // Container for enlarged cards (sits above backdrop)
|
|
|
|
[Header("Booster Pack UI")]
|
|
[SerializeField] private GameObject[] boosterPackButtons;
|
|
[SerializeField] private BoosterOpeningPage boosterOpeningPage;
|
|
|
|
private Input.InputMode _previousInputMode;
|
|
private List<AlbumCardPlacementDraggable> _activeCards = new List<AlbumCardPlacementDraggable>();
|
|
private const int MAX_VISIBLE_CARDS = 3;
|
|
|
|
protected override void OnManagedAwake()
|
|
{
|
|
base.OnManagedAwake();
|
|
|
|
// Make sure we have a CanvasGroup for transitions
|
|
if (canvasGroup == null)
|
|
canvasGroup = GetComponent<CanvasGroup>();
|
|
if (canvasGroup == null)
|
|
canvasGroup = gameObject.AddComponent<CanvasGroup>();
|
|
|
|
// Hide backdrop initially
|
|
if (cardEnlargedBackdrop != null)
|
|
{
|
|
cardEnlargedBackdrop.SetActive(false);
|
|
}
|
|
|
|
// Set up exit button
|
|
if (exitButton != null)
|
|
{
|
|
exitButton.onClick.AddListener(OnExitButtonClicked);
|
|
}
|
|
|
|
// Set up booster pack button listeners
|
|
SetupBoosterButtonListeners();
|
|
|
|
// Subscribe to CardSystemManager events (managers are guaranteed to be initialized)
|
|
if (CardSystemManager.Instance != null)
|
|
{
|
|
CardSystemManager.Instance.OnBoosterCountChanged += OnBoosterCountChanged;
|
|
// NOTE: OnPendingCardAdded is subscribed in TransitionIn, not here
|
|
// This prevents spawning cards when page is not active
|
|
|
|
// Update initial button visibility
|
|
int initialCount = CardSystemManager.Instance.GetBoosterPackCount();
|
|
UpdateBoosterButtons(initialCount);
|
|
}
|
|
|
|
// UI pages should start disabled
|
|
gameObject.SetActive(false);
|
|
}
|
|
|
|
private void SetupBoosterButtonListeners()
|
|
{
|
|
if (boosterPackButtons == null) return;
|
|
|
|
for (int i = 0; i < boosterPackButtons.Length; i++)
|
|
{
|
|
if (boosterPackButtons[i] == null) continue;
|
|
|
|
Button button = boosterPackButtons[i].GetComponent<Button>();
|
|
if (button != null)
|
|
{
|
|
button.onClick.AddListener(OnBoosterButtonClicked);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnDestroy()
|
|
{
|
|
// Unsubscribe from CardSystemManager
|
|
if (CardSystemManager.Instance != null)
|
|
{
|
|
CardSystemManager.Instance.OnBoosterCountChanged -= OnBoosterCountChanged;
|
|
// NOTE: OnPendingCardAdded is unsubscribed in TransitionOut
|
|
}
|
|
|
|
// Clean up exit button
|
|
if (exitButton != null)
|
|
{
|
|
exitButton.onClick.RemoveListener(OnExitButtonClicked);
|
|
}
|
|
|
|
// Clean up booster button listeners
|
|
if (boosterPackButtons != null)
|
|
{
|
|
foreach (var buttonObj in boosterPackButtons)
|
|
{
|
|
if (buttonObj == null) continue;
|
|
|
|
Button button = buttonObj.GetComponent<Button>();
|
|
if (button != null)
|
|
{
|
|
button.onClick.RemoveListener(OnBoosterButtonClicked);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clean up active cards
|
|
CleanupActiveCards();
|
|
|
|
// Call base implementation
|
|
base.OnDestroy();
|
|
}
|
|
|
|
private void OnExitButtonClicked()
|
|
{
|
|
if (book != null && book.CurrentPaper != 1)
|
|
{
|
|
// Not on page 0, flip to page 0 first
|
|
BookCurlPro.AutoFlip autoFlip = book.GetComponent<BookCurlPro.AutoFlip>();
|
|
if (autoFlip == null)
|
|
{
|
|
autoFlip = book.gameObject.AddComponent<BookCurlPro.AutoFlip>();
|
|
}
|
|
|
|
autoFlip.enabled = true;
|
|
autoFlip.StartFlipping(1);
|
|
}
|
|
else
|
|
{
|
|
// Already on page 0 or no book reference, exit
|
|
// Restore input mode before popping
|
|
if (Input.InputManager.Instance != null)
|
|
{
|
|
Input.InputManager.Instance.SetInputMode(_previousInputMode);
|
|
Debug.Log($"[AlbumViewPage] Restored input mode to {_previousInputMode} on exit");
|
|
}
|
|
|
|
if (UIPageController.Instance != null)
|
|
{
|
|
UIPageController.Instance.PopPage();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnBoosterCountChanged(int newCount)
|
|
{
|
|
UpdateBoosterButtons(newCount);
|
|
}
|
|
|
|
private void UpdateBoosterButtons(int boosterCount)
|
|
{
|
|
if (boosterPackButtons == null || boosterPackButtons.Length == 0) return;
|
|
|
|
int visibleCount = Mathf.Min(boosterCount, boosterPackButtons.Length);
|
|
|
|
for (int i = 0; i < boosterPackButtons.Length; i++)
|
|
{
|
|
if (boosterPackButtons[i] != null)
|
|
{
|
|
boosterPackButtons[i].SetActive(i < visibleCount);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnBoosterButtonClicked()
|
|
{
|
|
if (boosterOpeningPage != null && UIPageController.Instance != null)
|
|
{
|
|
// Pass current booster count to the opening page
|
|
int boosterCount = CardSystemManager.Instance?.GetBoosterPackCount() ?? 0;
|
|
boosterOpeningPage.SetAvailableBoosterCount(boosterCount);
|
|
|
|
UIPageController.Instance.PushPage(boosterOpeningPage);
|
|
}
|
|
}
|
|
|
|
public override void TransitionIn()
|
|
{
|
|
// Only store and switch input mode if this is the first time entering
|
|
// (when _previousInputMode hasn't been set yet)
|
|
if (Input.InputManager.Instance != null)
|
|
{
|
|
// Store the current input mode before switching
|
|
_previousInputMode = Input.InputMode.GameAndUI;
|
|
Input.InputManager.Instance.SetInputMode(Input.InputMode.UI);
|
|
Debug.Log("[AlbumViewPage] Switched to UI-only input mode on first entry");
|
|
}
|
|
|
|
// Subscribe to pending card events while page is active
|
|
if (CardSystemManager.Instance != null)
|
|
{
|
|
CardSystemManager.Instance.OnPendingCardAdded += OnPendingCardAdded;
|
|
}
|
|
|
|
// Spawn pending cards when opening album
|
|
SpawnPendingCards();
|
|
|
|
base.TransitionIn();
|
|
}
|
|
|
|
public override void TransitionOut()
|
|
{
|
|
// Unsubscribe from pending card events when page closes
|
|
if (CardSystemManager.Instance != null)
|
|
{
|
|
CardSystemManager.Instance.OnPendingCardAdded -= OnPendingCardAdded;
|
|
}
|
|
|
|
// Don't restore input mode here - only restore when actually exiting (in OnExitButtonClicked)
|
|
base.TransitionOut();
|
|
}
|
|
|
|
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)
|
|
{
|
|
// Clean up any enlarged card state before closing
|
|
CleanupEnlargedCardState();
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clean up enlarged card state when closing the album
|
|
/// </summary>
|
|
private void CleanupEnlargedCardState()
|
|
{
|
|
// Hide backdrop if visible
|
|
if (cardEnlargedBackdrop != null && cardEnlargedBackdrop.activeSelf)
|
|
{
|
|
cardEnlargedBackdrop.SetActive(false);
|
|
}
|
|
|
|
// If there's an enlarged card in the container, return it to its slot
|
|
if (cardEnlargedContainer != null && cardEnlargedContainer.childCount > 0)
|
|
{
|
|
// Get all enlarged cards (should only be one, but just in case)
|
|
for (int i = cardEnlargedContainer.childCount - 1; i >= 0; i--)
|
|
{
|
|
Transform cardTransform = cardEnlargedContainer.GetChild(i);
|
|
AlbumCard albumCard = cardTransform.GetComponent<AlbumCard>();
|
|
|
|
if (albumCard != null && albumCard.IsEnlarged)
|
|
{
|
|
// Force reset state and return to slot
|
|
Transform originalParent = albumCard.GetOriginalParent();
|
|
if (originalParent != null)
|
|
{
|
|
cardTransform.SetParent(originalParent, true);
|
|
cardTransform.localPosition = albumCard.GetOriginalLocalPosition();
|
|
cardTransform.localRotation = albumCard.GetOriginalLocalRotation();
|
|
}
|
|
|
|
albumCard.ForceResetEnlargedState();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#region Album Card Reveal System
|
|
|
|
/// <summary>
|
|
/// Spawn pending cards from CardSystemManager
|
|
/// Only spawns unique cards (one per definition+rarity, not one per copy)
|
|
/// </summary>
|
|
private void SpawnPendingCards()
|
|
{
|
|
if (CardSystemManager.Instance == null || bottomRightSlots == null || albumCardPlacementPrefab == null)
|
|
return;
|
|
|
|
var pending = CardSystemManager.Instance.GetPendingRevealCards();
|
|
|
|
// Get unique cards only (by DefinitionId + Rarity)
|
|
// Filter out cards with CopiesOwned = 0 (shouldn't happen but guard against it)
|
|
var uniquePending = pending
|
|
.Where(c => c.CopiesOwned > 0) // Guard: exclude zero-count cards
|
|
.GroupBy(c => new { c.DefinitionId, c.Rarity })
|
|
.Select(g => g.First()) // Take first instance of each unique card
|
|
.ToList();
|
|
|
|
int spawnCount = Mathf.Min(uniquePending.Count, MAX_VISIBLE_CARDS);
|
|
|
|
Debug.Log($"[AlbumViewPage] Spawning {spawnCount} unique pending cards (total pending: {pending.Count})");
|
|
|
|
for (int i = 0; i < spawnCount; i++)
|
|
{
|
|
SpawnCardInSlot(i, uniquePending[i]);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Spawn a card in a specific slot
|
|
/// </summary>
|
|
private void SpawnCardInSlot(int slotIndex, CardData cardData)
|
|
{
|
|
// Guard: Don't spawn cards with zero copies
|
|
if (cardData.CopiesOwned <= 0)
|
|
{
|
|
Debug.LogWarning($"[AlbumViewPage] Skipping spawn of card '{cardData.Name}' with {cardData.CopiesOwned} copies");
|
|
return;
|
|
}
|
|
|
|
DraggableSlot slot = FindSlotByIndex(slotIndex);
|
|
if (slot == null)
|
|
{
|
|
Debug.LogWarning($"[AlbumViewPage] Could not find slot with SlotIndex {slotIndex}");
|
|
return;
|
|
}
|
|
|
|
// Instantiate card directly as child of the slot container (not the slot itself, not canvas root)
|
|
// This keeps it in the correct UI hierarchy
|
|
GameObject cardObj = Instantiate(albumCardPlacementPrefab, bottomRightSlots.transform);
|
|
AlbumCardPlacementDraggable cardPlacement = cardObj.GetComponent<AlbumCardPlacementDraggable>();
|
|
|
|
if (cardPlacement != null)
|
|
{
|
|
// Setup card data
|
|
cardPlacement.SetupCard(cardData);
|
|
|
|
// Subscribe to events
|
|
cardPlacement.OnCardRevealed += OnCardRevealed;
|
|
cardPlacement.OnCardPlacedInAlbum += OnCardPlacedInAlbum;
|
|
|
|
// NOW assign to slot - this will:
|
|
// 1. Reparent to slot
|
|
// 2. Apply slot's occupantSizeMode scaling
|
|
// 3. Animate to slot position
|
|
cardPlacement.AssignToSlot(slot, true);
|
|
|
|
// Track it
|
|
_activeCards.Add(cardPlacement);
|
|
|
|
Debug.Log($"[AlbumViewPage] Spawned card '{cardData.Name}' (CopiesOwned: {cardData.CopiesOwned}) in slot {slotIndex}");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"[AlbumViewPage] Spawned card has no AlbumCardDraggable component!");
|
|
Destroy(cardObj);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle when a new card is added to pending queue
|
|
/// Only spawn if this unique card isn't already visualized
|
|
/// </summary>
|
|
private void OnPendingCardAdded(CardData card)
|
|
{
|
|
// Guard: Don't spawn cards with zero copies
|
|
if (card.CopiesOwned <= 0)
|
|
{
|
|
Debug.LogWarning($"[AlbumViewPage] Ignoring pending card '{card.Name}' with {card.CopiesOwned} copies");
|
|
return;
|
|
}
|
|
|
|
// Check if we already have a card with this definition + rarity spawned
|
|
bool alreadySpawned = _activeCards.Any(c =>
|
|
c.CardData.DefinitionId == card.DefinitionId &&
|
|
c.CardData.Rarity == card.Rarity);
|
|
|
|
if (alreadySpawned)
|
|
{
|
|
Debug.Log($"[AlbumViewPage] Card '{card.Name}' already spawned, skipping duplicate spawn");
|
|
return; // Don't spawn duplicates
|
|
}
|
|
|
|
// Try to spawn if we have space
|
|
if (_activeCards.Count < MAX_VISIBLE_CARDS)
|
|
{
|
|
int nextSlotIndex = _activeCards.Count;
|
|
SpawnCardInSlot(nextSlotIndex, card);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle when a card is revealed (flipped)
|
|
/// </summary>
|
|
private void OnCardRevealed(AlbumCardPlacementDraggable cardPlacement, CardData cardData)
|
|
{
|
|
Debug.Log($"[AlbumViewPage] Card revealed: {cardData.Name} (Zone: {cardData.Zone}, CopiesOwned: {cardData.CopiesOwned})");
|
|
|
|
// IMMEDIATELY move card from pending to inventory upon reveal
|
|
if (CardSystemManager.Instance != null)
|
|
{
|
|
CardSystemManager.Instance.MarkCardAsPlaced(cardData);
|
|
Debug.Log($"[AlbumViewPage] Moved card '{cardData.Name}' from pending to inventory on reveal");
|
|
}
|
|
|
|
// Remove this card from active cards list
|
|
_activeCards.Remove(cardPlacement);
|
|
|
|
// Check if we're currently viewing the correct zone for this card
|
|
CardZone currentZone = GetCurrentZone();
|
|
|
|
if (currentZone != cardData.Zone)
|
|
{
|
|
// Card is from a different zone - navigate to its zone
|
|
Debug.Log($"[AlbumViewPage] Card zone ({cardData.Zone}) doesn't match current zone ({currentZone}). Navigating to card's zone...");
|
|
NavigateToZone(cardData.Zone);
|
|
}
|
|
else
|
|
{
|
|
Debug.Log($"[AlbumViewPage] Card zone ({cardData.Zone}) matches current zone - no navigation needed.");
|
|
}
|
|
|
|
// Shuffle remaining cards to front and spawn next unique card
|
|
ShuffleCardsToFront();
|
|
TrySpawnNextCard();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle when a card is placed in the album (from AlbumCardDraggable)
|
|
/// Card data already moved to inventory in OnCardRevealed
|
|
/// This just handles cleanup
|
|
/// </summary>
|
|
private void OnCardPlacedInAlbum(AlbumCardPlacementDraggable cardPlacement, CardData cardData)
|
|
{
|
|
Debug.Log($"[AlbumViewPage] Card placed in album slot: {cardData.Name}");
|
|
|
|
// Unsubscribe from events (card is now static in album)
|
|
cardPlacement.OnCardRevealed -= OnCardRevealed;
|
|
cardPlacement.OnCardPlacedInAlbum -= OnCardPlacedInAlbum;
|
|
|
|
// Note: Card already removed from _activeCards in OnCardRevealed
|
|
// Note: Shuffle and spawn already done in OnCardRevealed
|
|
}
|
|
|
|
/// <summary>
|
|
/// Shuffle active cards to occupy front slots
|
|
/// </summary>
|
|
private void ShuffleCardsToFront()
|
|
{
|
|
if (bottomRightSlots == null || _activeCards.Count == 0)
|
|
return;
|
|
|
|
// Convert to base DraggableObject list for helper method
|
|
List<DraggableObject> draggableList = _activeCards.Cast<DraggableObject>().ToList();
|
|
SlotContainerHelper.ShuffleToFront(bottomRightSlots, draggableList, animate: true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Try to spawn the next pending card
|
|
/// Only spawns unique cards (not duplicates)
|
|
/// </summary>
|
|
private void TrySpawnNextCard()
|
|
{
|
|
if (CardSystemManager.Instance == null)
|
|
return;
|
|
|
|
if (_activeCards.Count >= MAX_VISIBLE_CARDS)
|
|
return; // Already at max
|
|
|
|
var pending = CardSystemManager.Instance.GetPendingRevealCards();
|
|
|
|
// Get unique pending cards, excluding zero-count cards
|
|
var uniquePending = pending
|
|
.Where(c => c.CopiesOwned > 0) // Guard: exclude zero-count cards
|
|
.GroupBy(c => new { c.DefinitionId, c.Rarity })
|
|
.Select(g => g.First())
|
|
.ToList();
|
|
|
|
// Find first unique card that's not already spawned
|
|
foreach (var cardData in uniquePending)
|
|
{
|
|
bool alreadySpawned = _activeCards.Any(c =>
|
|
c.CardData.DefinitionId == cardData.DefinitionId &&
|
|
c.CardData.Rarity == cardData.Rarity);
|
|
|
|
if (!alreadySpawned)
|
|
{
|
|
int nextSlotIndex = _activeCards.Count;
|
|
SpawnCardInSlot(nextSlotIndex, cardData);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Find a slot by its SlotIndex property
|
|
/// </summary>
|
|
private DraggableSlot FindSlotByIndex(int slotIndex)
|
|
{
|
|
if (bottomRightSlots == null)
|
|
return null;
|
|
|
|
foreach (var slot in bottomRightSlots.Slots)
|
|
{
|
|
if (slot.SlotIndex == slotIndex)
|
|
{
|
|
return slot;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the current zone based on book page
|
|
/// </summary>
|
|
public CardZone GetCurrentZone()
|
|
{
|
|
if (book == null || zoneTabs == null || zoneTabs.Length == 0)
|
|
return CardZone.AppleHills; // Default
|
|
|
|
int currentPage = book.CurrentPaper;
|
|
|
|
// Find tab with matching target page
|
|
foreach (var tab in zoneTabs)
|
|
{
|
|
if (tab.TargetPage == currentPage)
|
|
{
|
|
return tab.Zone;
|
|
}
|
|
}
|
|
|
|
// Fallback to first zone
|
|
return zoneTabs[0].Zone;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get tab for a specific zone
|
|
/// </summary>
|
|
public BookTabButton GetTabForZone(CardZone zone)
|
|
{
|
|
if (zoneTabs == null)
|
|
return null;
|
|
|
|
foreach (var tab in zoneTabs)
|
|
{
|
|
if (tab.Zone == zone)
|
|
{
|
|
return tab;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Navigate to a specific zone
|
|
/// </summary>
|
|
public void NavigateToZone(CardZone zone)
|
|
{
|
|
BookTabButton tab = GetTabForZone(zone);
|
|
if (tab != null)
|
|
{
|
|
tab.ActivateTab();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clean up all active cards
|
|
/// </summary>
|
|
private void CleanupActiveCards()
|
|
{
|
|
foreach (var card in _activeCards)
|
|
{
|
|
if (card != null && card.gameObject != null)
|
|
{
|
|
card.OnCardRevealed -= OnCardRevealed;
|
|
card.OnCardPlacedInAlbum -= OnCardPlacedInAlbum;
|
|
Destroy(card.gameObject);
|
|
}
|
|
}
|
|
_activeCards.Clear();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Card Enlarge System
|
|
|
|
/// <summary>
|
|
/// Subscribe to album card events when a card is spawned in a slot
|
|
/// Call this when AlbumCardSlot spawns a card
|
|
/// </summary>
|
|
public void RegisterAlbumCard(AlbumCard albumCard)
|
|
{
|
|
if (albumCard == null) return;
|
|
|
|
albumCard.OnEnlargeRequested += OnCardEnlargeRequested;
|
|
albumCard.OnShrinkRequested += OnCardShrinkRequested;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unsubscribe from album card events
|
|
/// </summary>
|
|
public void UnregisterAlbumCard(AlbumCard albumCard)
|
|
{
|
|
if (albumCard == null) return;
|
|
|
|
albumCard.OnEnlargeRequested -= OnCardEnlargeRequested;
|
|
albumCard.OnShrinkRequested -= OnCardShrinkRequested;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle card enlarge request - show backdrop and reparent card
|
|
/// </summary>
|
|
private void OnCardEnlargeRequested(AlbumCard card)
|
|
{
|
|
if (card == null) return;
|
|
|
|
Debug.Log($"[AlbumViewPage] OnCardEnlargeRequested called for card: {card.name}, current parent: {card.transform.parent.name}");
|
|
|
|
// IMPORTANT: Call EnlargeCard FIRST to store original parent (the slot)
|
|
// BEFORE reparenting to the enlarged container
|
|
card.EnlargeCard();
|
|
|
|
// Show backdrop
|
|
if (cardEnlargedBackdrop != null)
|
|
{
|
|
cardEnlargedBackdrop.SetActive(true);
|
|
Debug.Log($"[AlbumViewPage] Backdrop shown");
|
|
}
|
|
|
|
// NOW reparent card to enlarged container (above backdrop)
|
|
if (cardEnlargedContainer != null)
|
|
{
|
|
card.transform.SetParent(cardEnlargedContainer, true);
|
|
card.transform.SetAsLastSibling(); // Ensure on top
|
|
Debug.Log($"[AlbumViewPage] Card reparented to enlarged container");
|
|
}
|
|
|
|
Debug.Log($"[AlbumViewPage] Card enlarged: {card.GetCardData()?.Name}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle card shrink request - hide backdrop and reparent card back to slot
|
|
/// </summary>
|
|
private void OnCardShrinkRequested(AlbumCard card)
|
|
{
|
|
if (card == null) return;
|
|
|
|
// Trigger shrink animation
|
|
card.ShrinkCard();
|
|
|
|
// Hide backdrop
|
|
if (cardEnlargedBackdrop != null)
|
|
{
|
|
cardEnlargedBackdrop.SetActive(false);
|
|
}
|
|
|
|
// Reparent back to original parent (the slot)
|
|
Transform originalParent = card.GetOriginalParent();
|
|
if (originalParent != null)
|
|
{
|
|
card.transform.SetParent(originalParent, true);
|
|
card.transform.localPosition = card.GetOriginalLocalPosition();
|
|
card.transform.localRotation = card.GetOriginalLocalRotation();
|
|
}
|
|
|
|
Debug.Log($"[AlbumViewPage] Card shrunk: {card.GetCardData()?.Name}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Show backdrop and reparent slot preview card for enlargement
|
|
/// </summary>
|
|
public void ShowSlotPreview(AlbumCardSlot slot, Transform previewCardTransform)
|
|
{
|
|
if (previewCardTransform == null)
|
|
return;
|
|
|
|
Debug.Log($"[AlbumViewPage] ShowSlotPreview called for slot: {slot.name}");
|
|
|
|
// Show backdrop
|
|
if (cardEnlargedBackdrop != null)
|
|
{
|
|
cardEnlargedBackdrop.SetActive(true);
|
|
}
|
|
|
|
// Reparent preview card to enlarged container (above backdrop)
|
|
if (cardEnlargedContainer != null)
|
|
{
|
|
previewCardTransform.SetParent(cardEnlargedContainer, true);
|
|
previewCardTransform.SetAsLastSibling();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hide backdrop and trigger shrink animation for slot preview
|
|
/// </summary>
|
|
public void HideSlotPreview(AlbumCardSlot slot, Transform previewCardTransform, System.Action onComplete)
|
|
{
|
|
if (previewCardTransform == null)
|
|
return;
|
|
|
|
Debug.Log($"[AlbumViewPage] HideSlotPreview called for slot: {slot.name}");
|
|
|
|
// Hide backdrop
|
|
if (cardEnlargedBackdrop != null)
|
|
{
|
|
cardEnlargedBackdrop.SetActive(false);
|
|
}
|
|
|
|
// Shrink preview card
|
|
Vector3 originalScale = previewCardTransform.localScale / 2.5f; // Assuming 2.5x is enlarged scale
|
|
Pixelplacement.Tween.LocalScale(previewCardTransform, originalScale, 0.3f, 0f, Pixelplacement.Tween.EaseInBack,
|
|
completeCallback: () => onComplete?.Invoke());
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|