Refactor, cleanup code and add documentaiton
This commit is contained in:
3
Assets/Scripts/CardSystem/Controllers.meta
Normal file
3
Assets/Scripts/CardSystem/Controllers.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: abf7a5def55e43178a4c88caf5686cc9
|
||||||
|
timeCreated: 1763454388
|
||||||
3
Assets/Scripts/CardSystem/Core.meta
Normal file
3
Assets/Scripts/CardSystem/Core.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 619e69d1b6e44ecabc40657b2bcd13f9
|
||||||
|
timeCreated: 1763454353
|
||||||
3
Assets/Scripts/CardSystem/UI.meta
Normal file
3
Assets/Scripts/CardSystem/UI.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2a6295f642a94601ada9c21dc400d180
|
||||||
|
timeCreated: 1763454480
|
||||||
3
Assets/Scripts/CardSystem/UI/Component.meta
Normal file
3
Assets/Scripts/CardSystem/UI/Component.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7cb791415c884e97ac181816424200e4
|
||||||
|
timeCreated: 1763454497
|
||||||
3
Assets/Scripts/CardSystem/UI/Pages.meta
Normal file
3
Assets/Scripts/CardSystem/UI/Pages.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c548995da9c746d1916b79304734c1c9
|
||||||
|
timeCreated: 1763454486
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: e0b15b90103942c3b0e630462ecc5de1
|
|
||||||
timeCreated: 1757518020
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Core;
|
|
||||||
using UI.DragAndDrop.Core;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace UI.CardSystem
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Helper utility for shuffling draggable objects in a SlotContainer.
|
|
||||||
/// Moves objects to occupy the first available slots (0, 1, 2, etc.)
|
|
||||||
/// </summary>
|
|
||||||
public static class SlotContainerHelper
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Shuffles draggable objects to always occupy the first available slots.
|
|
||||||
/// Unassigns all objects from their current slots, then reassigns them starting from slot 0.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="container">The slot container holding the slots</param>
|
|
||||||
/// <param name="objects">List of draggable objects to shuffle</param>
|
|
||||||
/// <param name="animate">Whether to animate the movement</param>
|
|
||||||
public static void ShuffleToFront(SlotContainer container, List<DraggableObject> objects, bool animate = true)
|
|
||||||
{
|
|
||||||
if (container == null || objects == null || objects.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Logging.Debug($"[SlotContainerHelper] Shuffling {objects.Count} objects to front slots");
|
|
||||||
|
|
||||||
// Unassign all objects from their current slots
|
|
||||||
foreach (var obj in objects)
|
|
||||||
{
|
|
||||||
if (obj.CurrentSlot != null)
|
|
||||||
{
|
|
||||||
obj.CurrentSlot.Vacate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reassign objects to first N slots starting from slot 0
|
|
||||||
for (int i = 0; i < objects.Count; i++)
|
|
||||||
{
|
|
||||||
DraggableSlot targetSlot = FindSlotByIndex(container, i);
|
|
||||||
DraggableObject obj = objects[i];
|
|
||||||
|
|
||||||
if (targetSlot != null)
|
|
||||||
{
|
|
||||||
Logging.Debug($"[SlotContainerHelper] Assigning object to slot with SlotIndex {i}");
|
|
||||||
obj.AssignToSlot(targetSlot, animate);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logging.Warning($"[SlotContainerHelper] Could not find slot with SlotIndex {i}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Find a slot by its SlotIndex property (not list position)
|
|
||||||
/// </summary>
|
|
||||||
private static DraggableSlot FindSlotByIndex(SlotContainer container, int slotIndex)
|
|
||||||
{
|
|
||||||
foreach (var slot in container.Slots)
|
|
||||||
{
|
|
||||||
if (slot.SlotIndex == slotIndex)
|
|
||||||
{
|
|
||||||
return slot;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: cad44f85ab1a4672ab4bb14e2f919413
|
|
||||||
timeCreated: 1762470959
|
|
||||||
@@ -1,460 +0,0 @@
|
|||||||
# Album Card Placement Flow - Refactored Design
|
|
||||||
|
|
||||||
## Current State Analysis
|
|
||||||
|
|
||||||
### Existing Flow (Pre-Refactor):
|
|
||||||
1. Pending cards spawn face-up in bottom-right slots
|
|
||||||
2. User drags card to album slot
|
|
||||||
3. Card placement triggers inventory move
|
|
||||||
4. Next card spawns
|
|
||||||
|
|
||||||
### Problems:
|
|
||||||
- Cards spawn face-up (should be face-down)
|
|
||||||
- No "smart selection" from pending queue based on current album page
|
|
||||||
- No auto-flip to correct album page when card is picked up
|
|
||||||
- States don't support "hold to reveal" behavior
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Proposed New Flow
|
|
||||||
|
|
||||||
### Visual Journey:
|
|
||||||
```
|
|
||||||
[Face-Down Card in Corner]
|
|
||||||
↓ (user holds/drags)
|
|
||||||
[Card Flips to Reveal] + [Album auto-flips to correct page]
|
|
||||||
↓ (user drags over album)
|
|
||||||
[Card hovers over slot]
|
|
||||||
↓ (user releases)
|
|
||||||
[Card snaps to slot] → [Revealed State in slot]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Technical Implementation:
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. New Card States
|
|
||||||
|
|
||||||
### A. `CardPendingFaceDownState`
|
|
||||||
**Purpose:** Initial state for cards in pending corner slots
|
|
||||||
**Visuals:**
|
|
||||||
- Card back visible (card front hidden)
|
|
||||||
- Small scale (to fit in corner slot)
|
|
||||||
- Idle in corner
|
|
||||||
|
|
||||||
**Behavior:**
|
|
||||||
- Does NOT respond to clicks
|
|
||||||
- Responds to drag start (OnDragStarted)
|
|
||||||
- On drag start → trigger smart card selection + flip animation
|
|
||||||
|
|
||||||
**Transitions:**
|
|
||||||
- OnDragStarted → `CardFlippingPendingState`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### B. `CardFlippingPendingState`
|
|
||||||
**Purpose:** Transition state while card flips and album navigates
|
|
||||||
**Visuals:**
|
|
||||||
- Flip animation (card back → card front)
|
|
||||||
- Card follows cursor during flip
|
|
||||||
|
|
||||||
**Behavior:**
|
|
||||||
- Play flip animation (uses CardAnimator.PlayFlip)
|
|
||||||
- Emit event to AlbumViewPage to navigate to card's page
|
|
||||||
- Wait for flip animation complete
|
|
||||||
|
|
||||||
**Transitions:**
|
|
||||||
- OnFlipComplete → `CardDraggingRevealedState`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### C. `CardDraggingRevealedState`
|
|
||||||
**Purpose:** Card is revealed and being dragged around album
|
|
||||||
**Visuals:**
|
|
||||||
- Card front visible
|
|
||||||
- No badges (clean revealed state)
|
|
||||||
- Follow cursor/drag position
|
|
||||||
- Slight scale-up while dragging
|
|
||||||
|
|
||||||
**Behavior:**
|
|
||||||
- Respond to drag position updates
|
|
||||||
- Detect when hovering over valid AlbumCardSlot
|
|
||||||
- Visual feedback when over valid slot
|
|
||||||
- On drag end → snap to slot if valid, otherwise return to corner
|
|
||||||
|
|
||||||
**Transitions:**
|
|
||||||
- OnDragEnd (over valid slot) → slot's `PlacedInSlotState`
|
|
||||||
- OnDragEnd (invalid) → `CardPendingFaceDownState` (return to corner, flip back)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Smart Card Selection System
|
|
||||||
|
|
||||||
### AlbumViewPage Responsibilities:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public class AlbumViewPage
|
|
||||||
{
|
|
||||||
private List<CardData> _pendingQueue; // All pending cards
|
|
||||||
private List<Card> _cornerCards; // 3 face-down card GameObjects in corner
|
|
||||||
private int _currentAlbumPageIndex;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// When user starts dragging ANY corner card, we pick which pending card to reveal
|
|
||||||
/// </summary>
|
|
||||||
private void OnCornerCardDragStarted(Card cornerCard)
|
|
||||||
{
|
|
||||||
// 1. Get current album page's expected cards
|
|
||||||
var currentPageSlots = GetSlotsOnCurrentPage();
|
|
||||||
var currentPageDefinitions = currentPageSlots
|
|
||||||
.Select(slot => slot.TargetCardDefinition)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// 2. Try to find a pending card that belongs on this page
|
|
||||||
CardData selectedCard = _pendingQueue.FirstOrDefault(card =>
|
|
||||||
currentPageDefinitions.Any(def => def.Id == card.DefinitionId && def.Rarity == card.Rarity)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 3. If none on current page, pick random pending
|
|
||||||
if (selectedCard == null)
|
|
||||||
{
|
|
||||||
selectedCard = _pendingQueue[Random.Range(0, _pendingQueue.Count)];
|
|
||||||
|
|
||||||
// Navigate album to the page where this card belongs
|
|
||||||
int targetPage = FindPageForCard(selectedCard);
|
|
||||||
NavigateToPage(targetPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Assign the selected card data to the corner card being dragged
|
|
||||||
cornerCard.Context.SetupCard(selectedCard);
|
|
||||||
|
|
||||||
// 5. Trigger flip (handled by state)
|
|
||||||
cornerCard.Context.StateMachine.ChangeState("FlippingPendingState");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Card.cs Extensions
|
|
||||||
|
|
||||||
### New Setup Method:
|
|
||||||
```csharp
|
|
||||||
public class Card
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Setup for album pending placement (starts face-down in corner)
|
|
||||||
/// </summary>
|
|
||||||
public void SetupForAlbumPending()
|
|
||||||
{
|
|
||||||
// Start with NO card data (will be assigned on drag)
|
|
||||||
SetupCard(null, "PendingFaceDownState");
|
|
||||||
SetDraggingEnabled(true); // Enable drag immediately
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Drag Event Routing:
|
|
||||||
```csharp
|
|
||||||
// In Card.cs
|
|
||||||
public event Action<Card> OnDragStartedEvent;
|
|
||||||
|
|
||||||
private void OnDragStarted()
|
|
||||||
{
|
|
||||||
OnDragStartedEvent?.Invoke(this);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. AlbumViewPage Modifications
|
|
||||||
|
|
||||||
### Spawn Pending Cards (Face-Down):
|
|
||||||
```csharp
|
|
||||||
private void SpawnPendingCards()
|
|
||||||
{
|
|
||||||
// Spawn up to 3 "blank" face-down cards in corner
|
|
||||||
for (int i = 0; i < MAX_VISIBLE_CARDS; i++)
|
|
||||||
{
|
|
||||||
GameObject cardObj = Instantiate(cardPrefab, bottomRightSlots.transform);
|
|
||||||
var card = cardObj.GetComponent<StateMachine.Card>();
|
|
||||||
|
|
||||||
if (card != null)
|
|
||||||
{
|
|
||||||
// Setup as pending (no data yet, face-down)
|
|
||||||
card.SetupForAlbumPending();
|
|
||||||
|
|
||||||
// Subscribe to drag start
|
|
||||||
card.OnDragStartedEvent += OnCornerCardDragStarted;
|
|
||||||
|
|
||||||
// Assign to corner slot
|
|
||||||
DraggableSlot slot = FindSlotByIndex(i);
|
|
||||||
card.AssignToSlot(slot, true);
|
|
||||||
|
|
||||||
_cornerCards.Add(card);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Handle Drag Start (Smart Selection):
|
|
||||||
```csharp
|
|
||||||
private void OnCornerCardDragStarted(Card cornerCard)
|
|
||||||
{
|
|
||||||
if (_pendingQueue.Count == 0) return;
|
|
||||||
|
|
||||||
// Smart selection logic (from section 2)
|
|
||||||
CardData selectedCard = SelectSmartPendingCard();
|
|
||||||
|
|
||||||
// Assign data to the dragged corner card
|
|
||||||
cornerCard.Context.SetupCard(selectedCard);
|
|
||||||
|
|
||||||
// State transition to flipping (handled by state machine)
|
|
||||||
// FlippingPendingState will trigger flip animation + album navigation
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Navigate to Card's Page:
|
|
||||||
```csharp
|
|
||||||
public void NavigateToCardPage(CardData card)
|
|
||||||
{
|
|
||||||
int targetPage = FindPageForCard(card);
|
|
||||||
if (targetPage != _currentAlbumPageIndex)
|
|
||||||
{
|
|
||||||
// Trigger book page flip animation
|
|
||||||
bookController.FlipToPage(targetPage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. State Implementation Details
|
|
||||||
|
|
||||||
### CardPendingFaceDownState.cs
|
|
||||||
```csharp
|
|
||||||
public class CardPendingFaceDownState : AppleState
|
|
||||||
{
|
|
||||||
private CardContext _context;
|
|
||||||
|
|
||||||
public override void OnEnterState()
|
|
||||||
{
|
|
||||||
// Show card back, hide card front
|
|
||||||
if (_context.CardDisplay != null)
|
|
||||||
{
|
|
||||||
_context.CardDisplay.gameObject.SetActive(false); // Hide front
|
|
||||||
}
|
|
||||||
|
|
||||||
var cardBack = GetComponentInChildren<CardBack>(); // Assumes CardBack component exists
|
|
||||||
if (cardBack != null)
|
|
||||||
{
|
|
||||||
cardBack.gameObject.SetActive(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Small scale for corner slot
|
|
||||||
_context.RootTransform.localScale = Vector3.one * 0.8f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### CardFlippingPendingState.cs
|
|
||||||
```csharp
|
|
||||||
public class CardFlippingPendingState : AppleState
|
|
||||||
{
|
|
||||||
private CardContext _context;
|
|
||||||
|
|
||||||
public override void OnEnterState()
|
|
||||||
{
|
|
||||||
// Notify album page to navigate
|
|
||||||
var albumPage = FindObjectOfType<AlbumViewPage>();
|
|
||||||
if (albumPage != null)
|
|
||||||
{
|
|
||||||
albumPage.NavigateToCardPage(_context.CardData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Play flip animation
|
|
||||||
if (_context.Animator != null)
|
|
||||||
{
|
|
||||||
_context.Animator.PlayFlip(
|
|
||||||
startRotation: Quaternion.Euler(0, 180, 0), // back facing
|
|
||||||
endRotation: Quaternion.identity, // front facing
|
|
||||||
onComplete: OnFlipComplete
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnFlipComplete()
|
|
||||||
{
|
|
||||||
_context.StateMachine.ChangeState("DraggingRevealedState");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### CardDraggingRevealedState.cs
|
|
||||||
```csharp
|
|
||||||
public class CardDraggingRevealedState : AppleState
|
|
||||||
{
|
|
||||||
private CardContext _context;
|
|
||||||
private AlbumCardSlot _hoveredSlot;
|
|
||||||
|
|
||||||
public override void OnEnterState()
|
|
||||||
{
|
|
||||||
// Card front visible, clean revealed (no badges)
|
|
||||||
if (_context.CardDisplay != null)
|
|
||||||
{
|
|
||||||
_context.CardDisplay.gameObject.SetActive(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slightly larger while dragging
|
|
||||||
_context.Animator.PlayEnlarge(1.2f);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Update()
|
|
||||||
{
|
|
||||||
// Detect hover over valid album slots
|
|
||||||
_hoveredSlot = DetectValidSlotUnderCursor();
|
|
||||||
|
|
||||||
if (_hoveredSlot != null)
|
|
||||||
{
|
|
||||||
// Visual feedback: highlight slot or card
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnDragEnded()
|
|
||||||
{
|
|
||||||
if (_hoveredSlot != null && _hoveredSlot.CanAcceptCard(_context.CardData))
|
|
||||||
{
|
|
||||||
// Snap to slot and transition to PlacedInSlotState
|
|
||||||
SnapToSlot(_hoveredSlot);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Return to corner, flip back to face-down
|
|
||||||
ReturnToCorner();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Required New Components
|
|
||||||
|
|
||||||
### CardBack Component
|
|
||||||
```csharp
|
|
||||||
public class CardBack : MonoBehaviour
|
|
||||||
{
|
|
||||||
[SerializeField] private Image backImage;
|
|
||||||
|
|
||||||
public void Show() => gameObject.SetActive(true);
|
|
||||||
public void Hide() => gameObject.SetActive(false);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Attach to Card prefab as a sibling to CardDisplay.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Prefab Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
Card (GameObject)
|
|
||||||
├── StateMachine (AppleMachine)
|
|
||||||
│ ├── PendingFaceDownState
|
|
||||||
│ ├── FlippingPendingState
|
|
||||||
│ ├── DraggingRevealedState
|
|
||||||
│ ├── PlacedInSlotState (existing)
|
|
||||||
│ └── ... (other states)
|
|
||||||
├── CardContext
|
|
||||||
├── CardAnimator
|
|
||||||
├── CardDisplay (front visuals)
|
|
||||||
├── CardBack (back visuals - NEW)
|
|
||||||
└── DraggableObject
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Migration Steps
|
|
||||||
|
|
||||||
### Step 1: Create New States
|
|
||||||
- CardPendingFaceDownState.cs
|
|
||||||
- CardFlippingPendingState.cs
|
|
||||||
- CardDraggingRevealedState.cs
|
|
||||||
|
|
||||||
### Step 2: Add CardBack Component
|
|
||||||
- Create CardBack.cs script
|
|
||||||
- Add CardBack GameObject to Card prefab
|
|
||||||
- Design card back visual (sprite, frame, etc.)
|
|
||||||
|
|
||||||
### Step 3: Update Card.cs
|
|
||||||
- Add SetupForAlbumPending() method
|
|
||||||
- Add OnDragStartedEvent
|
|
||||||
- Wire drag events to state machine
|
|
||||||
|
|
||||||
### Step 4: Update AlbumViewPage
|
|
||||||
- Modify SpawnPendingCards() to spawn face-down
|
|
||||||
- Implement smart selection logic
|
|
||||||
- Add NavigateToCardPage() method
|
|
||||||
- Connect to book flip controller
|
|
||||||
|
|
||||||
### Step 5: Update CardAnimator
|
|
||||||
- Ensure PlayFlip() can handle arbitrary start/end rotations
|
|
||||||
- Add any needed drag-follow animation helpers
|
|
||||||
|
|
||||||
### Step 6: Testing
|
|
||||||
- Test corner card drag → flip → album navigation
|
|
||||||
- Test smart selection (page match prioritization)
|
|
||||||
- Test return-to-corner on invalid drop
|
|
||||||
- Test snap-to-slot on valid drop
|
|
||||||
- Test multiple cards in queue
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. Edge Cases & Considerations
|
|
||||||
|
|
||||||
### No Pending Cards
|
|
||||||
- Don't spawn corner cards if pending queue is empty
|
|
||||||
- Hide corner slots when no cards to place
|
|
||||||
|
|
||||||
### Album Page Navigation During Drag
|
|
||||||
- Lock page flipping while dragging (prevent user manual flip)
|
|
||||||
- Queue navigation if flip animation in progress
|
|
||||||
|
|
||||||
### Multiple Cards Dragged Simultaneously
|
|
||||||
- Only allow one card to be in FlippingPending/DraggingRevealed at a time
|
|
||||||
- Disable other corner cards while one is being dragged
|
|
||||||
|
|
||||||
### Card Returns to Corner
|
|
||||||
- Flip back animation (reverse of reveal)
|
|
||||||
- Re-enter PendingFaceDownState
|
|
||||||
- Unassign CardData (become "blank" again for next drag)
|
|
||||||
|
|
||||||
### Invalid Slot Drop
|
|
||||||
- Visual feedback (shake, red highlight)
|
|
||||||
- Smooth return animation to corner
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. Benefits of This Approach
|
|
||||||
|
|
||||||
✅ **Consistent State Architecture** - Uses same state machine pattern as booster flow
|
|
||||||
✅ **Smart UX** - Auto-navigation to correct album page
|
|
||||||
✅ **Clean Separation** - States handle visuals/behavior, page handles logic
|
|
||||||
✅ **Reusable** - States can be reused for other card flows
|
|
||||||
✅ **Extensible** - Easy to add new behaviors (e.g., card preview on hover)
|
|
||||||
✅ **Testable** - Each state can be tested independently
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Open Questions for Approval
|
|
||||||
|
|
||||||
1. **Card Back Design:** Should we use a generic back for all cards, or rarity-specific backs?
|
|
||||||
2. **Navigation Timing:** Should album flip happen instantly or animated during card flip?
|
|
||||||
3. **Return Animation:** Fast snap-back or gentle float-back when invalid drop?
|
|
||||||
4. **Multiple Rarities:** If pending queue has same card at multiple rarities, which to prioritize?
|
|
||||||
5. **Corner Slot Count:** Keep at 3, or make configurable?
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Ready to implement once approved! 🎉
|
|
||||||
|
|
||||||
674
docs/card_system_guide.md
Normal file
674
docs/card_system_guide.md
Normal file
@@ -0,0 +1,674 @@
|
|||||||
|
# Card System Guide
|
||||||
|
|
||||||
|
Complete guide to the Apple Hills card collecting and album system.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [Quick Start](#quick-start)
|
||||||
|
- [System Architecture](#system-architecture)
|
||||||
|
- [Card Flows](#card-flows)
|
||||||
|
- [Key Components](#key-components)
|
||||||
|
- [Working with Cards in Code](#working-with-cards-in-code)
|
||||||
|
- [Extending the System](#extending-the-system)
|
||||||
|
- [Troubleshooting](#troubleshooting)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Card System manages the player's collectible card journey from booster pack opening to album placement. It consists of:
|
||||||
|
|
||||||
|
- **Booster Opening Flow**: Interactive pack opening with card reveals
|
||||||
|
- **Album Placement Flow**: Drag-and-drop cards into album slots
|
||||||
|
- **State Machine**: Each card progresses through well-defined states
|
||||||
|
- **Album Navigation**: Book-based album with zone tabs
|
||||||
|
- **Notification System**: Visual feedback for new boosters/pending cards
|
||||||
|
|
||||||
|
### Core Concepts
|
||||||
|
|
||||||
|
**CardData**: Runtime data for a single card (definition ID, rarity, zone, image)
|
||||||
|
**CardDefinition**: ScriptableObject template defining card properties
|
||||||
|
**Card States**: Cards transition through states (Idle → Revealed → Dragging → Placed)
|
||||||
|
**Pending Cards**: Cards waiting to be placed in the album (shown in bottom-right corner)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Opening the Album
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// From any script
|
||||||
|
if (UIPageController.Instance != null)
|
||||||
|
{
|
||||||
|
UIPageController.Instance.PushPage(albumViewPage);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Granting Booster Packs
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Simple API
|
||||||
|
CardSystemManager.Instance.AddBoosterPack(1);
|
||||||
|
|
||||||
|
// Visual grant with animation (from minigames)
|
||||||
|
MinigameBoosterGiver.Instance.GiveBooster(() => {
|
||||||
|
Debug.Log("Booster granted and animation complete!");
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Opening a Booster Pack (Programmatic)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Returns list of cards in the pack
|
||||||
|
List<CardData> cards = CardSystemManager.Instance.OpenBoosterPack();
|
||||||
|
|
||||||
|
// The cards are automatically added to "pending" state
|
||||||
|
// Player must place them in album manually
|
||||||
|
```
|
||||||
|
|
||||||
|
### Checking Player's Collection
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var inventory = CardSystemManager.Instance.GetCardInventory();
|
||||||
|
|
||||||
|
// Check if player owns a specific card
|
||||||
|
CardData ownedCard = inventory.GetCard("CardDefID", CardRarity.Legendary);
|
||||||
|
|
||||||
|
// Get all cards in a zone
|
||||||
|
List<CardData> townCards = inventory.GetCardsByZone(CardZone.Town);
|
||||||
|
|
||||||
|
// Check completion percentage
|
||||||
|
float completion = inventory.GetCompletionPercentage();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## System Architecture
|
||||||
|
|
||||||
|
### Component Hierarchy
|
||||||
|
|
||||||
|
```
|
||||||
|
CardSystemManager (Singleton)
|
||||||
|
├── Inventory Management
|
||||||
|
├── Booster Pack Logic
|
||||||
|
└── Pending Card Queue
|
||||||
|
|
||||||
|
AlbumViewPage (UI Page)
|
||||||
|
├── CornerCardManager (Non-Component)
|
||||||
|
│ └── Manages 3 pending cards in corner
|
||||||
|
├── AlbumNavigationService (Non-Component)
|
||||||
|
│ └── Book page flipping & zone navigation
|
||||||
|
└── CardEnlargeController (Non-Component)
|
||||||
|
└── Backdrop & enlarge/shrink animations
|
||||||
|
|
||||||
|
BoosterOpeningPage (UI Page)
|
||||||
|
└── Manages pack opening flow & card reveals
|
||||||
|
|
||||||
|
Card (MonoBehaviour)
|
||||||
|
├── CardContext (shared state)
|
||||||
|
├── CardAnimator (animations)
|
||||||
|
├── CardDisplay (visuals)
|
||||||
|
└── StateMachine (10 possible states)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Non-Component Controllers
|
||||||
|
|
||||||
|
The system uses **Controller** pattern for complex logic without Unity lifecycle overhead:
|
||||||
|
|
||||||
|
- **CornerCardManager**: Spawns/despawns pending cards, smart selection, shuffle logic
|
||||||
|
- **AlbumNavigationService**: Book page navigation, zone mapping
|
||||||
|
- **CardEnlargeController**: Backdrop visibility, card reparenting for enlarge view
|
||||||
|
- **ProgressBarController**: Booster opening progress visualization
|
||||||
|
|
||||||
|
These are instantiated lazily via C# properties:
|
||||||
|
```csharp
|
||||||
|
private CornerCardManager _cornerCardManager;
|
||||||
|
private CornerCardManager CornerCards => _cornerCardManager ??= new CornerCardManager(...);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Card Flows
|
||||||
|
|
||||||
|
### 1. Booster Opening Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Player opens booster pack
|
||||||
|
↓
|
||||||
|
BoosterOpeningPage spawns cards face-down
|
||||||
|
↓
|
||||||
|
Player clicks a card (IdleState → EnlargedNewState/EnlargedRepeatState)
|
||||||
|
↓
|
||||||
|
Card flips & enlarges, shows NEW or REPEAT badge
|
||||||
|
↓
|
||||||
|
Player clicks to dismiss (→ RevealedState)
|
||||||
|
↓
|
||||||
|
Repeat until all cards revealed
|
||||||
|
↓
|
||||||
|
Cards fly to album icon, added to pending queue
|
||||||
|
↓
|
||||||
|
Page auto-closes (or waits for player)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key States:**
|
||||||
|
- **IdleState**: Face-down, awaiting click
|
||||||
|
- **EnlargedNewState**: NEW badge (first time collection)
|
||||||
|
- **EnlargedRepeatState**: REPEAT badge (duplicate)
|
||||||
|
- **EnlargedLegendaryRepeatState**: Special legendary repeat state
|
||||||
|
- **RevealedState**: Face-up, dismissed from center
|
||||||
|
|
||||||
|
### 2. Album Placement Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
AlbumViewPage opens
|
||||||
|
↓
|
||||||
|
CornerCardManager spawns up to 3 pending cards (face-down)
|
||||||
|
↓
|
||||||
|
Player drags card (PendingFaceDownState → DraggingRevealedState)
|
||||||
|
↓
|
||||||
|
Card data assigned, flips to reveal, book navigates to correct zone page
|
||||||
|
↓
|
||||||
|
Player drops card on matching AlbumCardSlot
|
||||||
|
↓
|
||||||
|
Card placed (→ PlacedInSlotState), registered with AlbumViewPage
|
||||||
|
↓
|
||||||
|
CornerCardManager rebuilds: shuffles remaining cards, spawns new if available
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key States:**
|
||||||
|
- **PendingFaceDownState**: Face-down in corner, no data assigned yet
|
||||||
|
- **DraggingRevealedState**: Data assigned, flipped, dragging to slot
|
||||||
|
- **PlacedInSlotState**: Locked in album slot
|
||||||
|
- **AlbumEnlargedState**: Clicked from slot, enlarged for viewing
|
||||||
|
|
||||||
|
### 3. Album Card Viewing
|
||||||
|
|
||||||
|
```
|
||||||
|
Player clicks placed card in album slot
|
||||||
|
↓
|
||||||
|
Card enlarges (PlacedInSlotState → AlbumEnlargedState)
|
||||||
|
↓
|
||||||
|
Backdrop shown, card reparented to enlarged container
|
||||||
|
↓
|
||||||
|
Card animates to center with dramatic scale increase
|
||||||
|
↓
|
||||||
|
Player clicks card or backdrop to dismiss
|
||||||
|
↓
|
||||||
|
Card shrinks & animates back to slot (→ PlacedInSlotState)
|
||||||
|
↓
|
||||||
|
Backdrop hidden, card reparented back to slot
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Components
|
||||||
|
|
||||||
|
### CardSystemManager
|
||||||
|
|
||||||
|
**Singleton** managing all card data and inventory.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Access
|
||||||
|
CardSystemManager.Instance
|
||||||
|
|
||||||
|
// Key Methods
|
||||||
|
.AddBoosterPack(int count) // Grant booster packs
|
||||||
|
.OpenBoosterPack() // Open a pack, returns CardData[]
|
||||||
|
.GetPendingRevealCards() // Get cards waiting for album placement
|
||||||
|
.GetCardInventory() // Access player's collection
|
||||||
|
.AddCardToInventory(CardData) // Add card to collection
|
||||||
|
.RemoveFromPending(CardData) // Remove from pending queue
|
||||||
|
|
||||||
|
// Events
|
||||||
|
.OnBoosterCountChanged(int newCount)
|
||||||
|
.OnPendingCardAdded(CardData)
|
||||||
|
.OnPendingCardRemoved(CardData)
|
||||||
|
```
|
||||||
|
|
||||||
|
### AlbumViewPage
|
||||||
|
|
||||||
|
**UI Page** for viewing and managing the album.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Setup in scene:
|
||||||
|
// - Assign book reference (BookPro component)
|
||||||
|
// - Assign zone tab container
|
||||||
|
// - Assign card prefab for spawning
|
||||||
|
// - Assign backdrop & enlarged container for card viewing
|
||||||
|
// - Link to BoosterOpeningPage
|
||||||
|
|
||||||
|
// Query Methods (used by Card states)
|
||||||
|
public CardData GetCardForPendingSlot() // Smart card selection
|
||||||
|
public AlbumCardSlot GetTargetSlotForCard(CardData) // Find destination slot
|
||||||
|
public void NavigateToCardPage(CardData, Action) // Flip to correct page
|
||||||
|
public void NotifyCardPlaced(Card) // Cleanup after placement
|
||||||
|
|
||||||
|
// Public Properties
|
||||||
|
public bool IsPageFlipping // For state timing checks
|
||||||
|
```
|
||||||
|
|
||||||
|
### BoosterOpeningPage
|
||||||
|
|
||||||
|
**UI Page** for opening booster packs.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Setup in scene:
|
||||||
|
// - Assign booster pack prefab
|
||||||
|
// - Assign corner slots for waiting boosters (max 3)
|
||||||
|
// - Assign center slot for opening
|
||||||
|
// - Assign card display container
|
||||||
|
// - Assign card prefab
|
||||||
|
// - Assign album icon (dismiss button & tween target)
|
||||||
|
|
||||||
|
// Call before showing:
|
||||||
|
.SetAvailableBoosterCount(int count) // How many boosters player has
|
||||||
|
|
||||||
|
// Flow automatically managed by page
|
||||||
|
```
|
||||||
|
|
||||||
|
### Card States
|
||||||
|
|
||||||
|
Each card uses **AppleMachine** state machine with these states:
|
||||||
|
|
||||||
|
| State | Purpose | Entry Trigger |
|
||||||
|
|-------|---------|---------------|
|
||||||
|
| **IdleState** | Face-down in booster opening | Card spawned for booster reveal |
|
||||||
|
| **EnlargedNewState** | Enlarged with NEW badge | Clicked first-time card |
|
||||||
|
| **EnlargedRepeatState** | Enlarged with REPEAT badge | Clicked duplicate |
|
||||||
|
| **EnlargedLegendaryRepeatState** | Legendary repeat variant | Clicked legendary duplicate |
|
||||||
|
| **RevealedState** | Face-up, dismissed | Dismissed from enlarged state |
|
||||||
|
| **PendingFaceDownState** | Face-down in corner | Spawned in album corner |
|
||||||
|
| **DraggingRevealedState** | Face-up while dragging | Dragged from corner |
|
||||||
|
| **PlacedInSlotState** | Locked in album slot | Dropped on correct slot |
|
||||||
|
| **AlbumEnlargedState** | Enlarged from album | Clicked while in slot |
|
||||||
|
| **DraggingState** | Generic drag state | *(unused in current flow)* |
|
||||||
|
|
||||||
|
### Card Context & Components
|
||||||
|
|
||||||
|
Every card has:
|
||||||
|
- **CardContext**: Shared state, component references, events
|
||||||
|
- **CardAnimator**: Centralized animation methods
|
||||||
|
- **CardDisplay**: Visual rendering (image, frame, overlay, rarity/zone styling)
|
||||||
|
- **AppleMachine**: State machine controller
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Accessing card components
|
||||||
|
var card = GetComponent<Card>();
|
||||||
|
card.Context.CardData // CardData
|
||||||
|
card.Context.Animator // CardAnimator
|
||||||
|
card.Context.CardDisplay // CardDisplay
|
||||||
|
card.Context.StateMachine // AppleMachine
|
||||||
|
card.Context.AlbumViewPage // Injected page reference
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Working with Cards in Code
|
||||||
|
|
||||||
|
### Spawning a Card for Booster Opening
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
GameObject cardObj = Instantiate(cardPrefab, containerTransform);
|
||||||
|
var card = cardObj.GetComponent<Card>();
|
||||||
|
var context = cardObj.GetComponent<CardContext>();
|
||||||
|
|
||||||
|
// Setup card data
|
||||||
|
context.SetupCard(cardData);
|
||||||
|
|
||||||
|
// Start in IdleState for booster reveal
|
||||||
|
card.SetupForBoosterReveal(cardData, isNew); // isNew unused, states query inventory
|
||||||
|
|
||||||
|
// Subscribe to reveal complete event
|
||||||
|
context.BoosterContext.OnRevealFlowComplete += () => {
|
||||||
|
Debug.Log("Card reveal finished!");
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Spawning a Card for Album Corner
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
GameObject cardObj = Instantiate(cardPrefab, slotTransform);
|
||||||
|
var card = cardObj.GetComponent<Card>();
|
||||||
|
|
||||||
|
// Assign to slot FIRST
|
||||||
|
card.AssignToSlot(slot, animateMove: false);
|
||||||
|
|
||||||
|
// Inject AlbumViewPage dependency
|
||||||
|
card.Context.SetAlbumViewPage(albumViewPage);
|
||||||
|
|
||||||
|
// Setup for pending state (no data yet)
|
||||||
|
card.SetupForAlbumPending(); // Starts in PendingFaceDownState
|
||||||
|
```
|
||||||
|
|
||||||
|
### Spawning a Card Already in Album Slot
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
GameObject cardObj = Instantiate(cardPrefab, slotTransform);
|
||||||
|
var card = cardObj.GetComponent<Card>();
|
||||||
|
|
||||||
|
// Setup for album slot (already owned)
|
||||||
|
card.SetupForAlbumSlot(cardData, albumCardSlot); // Starts in PlacedInSlotState
|
||||||
|
|
||||||
|
// Register for enlarge/shrink functionality
|
||||||
|
albumViewPage.RegisterCardInAlbum(card);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transitioning Card States Manually
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Get card component
|
||||||
|
var card = GetComponent<Card>();
|
||||||
|
|
||||||
|
// Change state
|
||||||
|
card.ChangeState(CardStateNames.Revealed);
|
||||||
|
|
||||||
|
// Check current state
|
||||||
|
string currentState = card.GetCurrentStateName();
|
||||||
|
if (currentState == CardStateNames.PlacedInSlot)
|
||||||
|
{
|
||||||
|
Debug.Log("Card is placed in album!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get specific state component
|
||||||
|
var enlargedState = card.GetStateComponent<CardAlbumEnlargedState>(
|
||||||
|
CardStateNames.AlbumEnlarged
|
||||||
|
);
|
||||||
|
if (enlargedState != null)
|
||||||
|
{
|
||||||
|
// Access state-specific methods/properties
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Subscribing to Card Events
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var context = card.Context;
|
||||||
|
|
||||||
|
// Drag events
|
||||||
|
context.OnDragStarted += (ctx) => Debug.Log("Drag started!");
|
||||||
|
context.OnDragEnded += (ctx) => Debug.Log("Drag ended!");
|
||||||
|
|
||||||
|
// Click events (routed through CardDisplay)
|
||||||
|
context.CardDisplay.OnCardClicked += (display) => Debug.Log("Card clicked!");
|
||||||
|
|
||||||
|
// Booster reveal events
|
||||||
|
context.BoosterContext.OnRevealFlowComplete += () => {
|
||||||
|
Debug.Log("Reveal complete!");
|
||||||
|
CardSystemManager.Instance.AddCardToInventory(context.CardData);
|
||||||
|
};
|
||||||
|
|
||||||
|
// State machine events (if needed)
|
||||||
|
context.StateMachine.OnStateChange += (newState) => Debug.Log($"State: {newState.name}");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Animations
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var animator = card.Animator;
|
||||||
|
|
||||||
|
// Built-in animations
|
||||||
|
animator.PopIn(duration: 0.5f, onComplete: () => Debug.Log("Popped in!"));
|
||||||
|
animator.PopOut(duration: 0.3f);
|
||||||
|
animator.PlayEnlarge(targetScale: 2.5f);
|
||||||
|
animator.PlayShrink(targetScale: Vector3.one);
|
||||||
|
|
||||||
|
// Combine animations
|
||||||
|
animator.AnimateLocalPosition(Vector3.zero, duration: 0.5f);
|
||||||
|
animator.AnimateScale(Vector3.one * 1.5f, duration: 0.3f);
|
||||||
|
|
||||||
|
// Flip animation
|
||||||
|
Transform cardBack = card.transform.Find("CardBack");
|
||||||
|
Transform cardFront = card.transform.Find("CardDisplay");
|
||||||
|
animator.PlayFlip(cardBack, cardFront, duration: 0.6f, onComplete: () => {
|
||||||
|
Debug.Log("Flip complete!");
|
||||||
|
});
|
||||||
|
animator.PlayFlipScalePunch(punchScale: 1.1f);
|
||||||
|
|
||||||
|
// Hover effects
|
||||||
|
Vector2 originalPos = animator.GetAnchoredPosition();
|
||||||
|
animator.HoverEnter(liftAmount: 20f, scaleMultiplier: 1.05f);
|
||||||
|
// ... later
|
||||||
|
animator.HoverExit(originalPos);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Extending the System
|
||||||
|
|
||||||
|
### Adding a New Card State
|
||||||
|
|
||||||
|
1. **Create State Script** in `StateMachine/States/`
|
||||||
|
```csharp
|
||||||
|
using Core.SaveLoad;
|
||||||
|
using UI.CardSystem.StateMachine;
|
||||||
|
|
||||||
|
public class MyNewCardState : AppleState, ICardClickHandler
|
||||||
|
{
|
||||||
|
private CardContext _context;
|
||||||
|
|
||||||
|
public override void EnterState()
|
||||||
|
{
|
||||||
|
_context = GetComponentInParent<CardContext>();
|
||||||
|
// Setup animations, visuals, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnCardClicked(CardContext context)
|
||||||
|
{
|
||||||
|
// Handle click behavior
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ExitState()
|
||||||
|
{
|
||||||
|
// Cleanup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Add State Name** to `CardStateNames.cs`
|
||||||
|
```csharp
|
||||||
|
public const string MyNewState = "MyNewCardState";
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Add GameObject** as child of Card prefab's AppleMachine with your state component attached
|
||||||
|
|
||||||
|
4. **Transition to State**
|
||||||
|
```csharp
|
||||||
|
card.ChangeState(CardStateNames.MyNewState);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating a Custom Card Visual Effect
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class MyCardEffect : MonoBehaviour
|
||||||
|
{
|
||||||
|
private CardContext _context;
|
||||||
|
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
_context = GetComponentInParent<CardContext>();
|
||||||
|
|
||||||
|
// Subscribe to state changes
|
||||||
|
_context.StateMachine.OnStateChange += OnStateChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnStateChanged(AppleState newState)
|
||||||
|
{
|
||||||
|
if (newState.name == CardStateNames.EnlargedNew)
|
||||||
|
{
|
||||||
|
// Play custom effect
|
||||||
|
PlaySparkleEffect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaySparkleEffect()
|
||||||
|
{
|
||||||
|
// Your custom effect logic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding Custom Card Slots
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class MyCustomCardSlot : AlbumCardSlot
|
||||||
|
{
|
||||||
|
protected override void OnCardPlaced(Card card)
|
||||||
|
{
|
||||||
|
base.OnCardPlaced(card);
|
||||||
|
|
||||||
|
// Custom logic when card placed
|
||||||
|
PlaySpecialEffect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Cards Not Showing in Corner
|
||||||
|
|
||||||
|
**Check:**
|
||||||
|
1. AlbumViewPage has `cardPrefab` assigned
|
||||||
|
2. AlbumViewPage has `bottomRightSlots` assigned (SlotContainer with 3 slots)
|
||||||
|
3. CardSystemManager has pending cards: `GetPendingRevealCards().Count > 0`
|
||||||
|
4. Page is in album proper (not menu page): Check with `IsInAlbumProper()`
|
||||||
|
|
||||||
|
**Debug:**
|
||||||
|
```csharp
|
||||||
|
Debug.Log($"Pending cards: {CardSystemManager.Instance.GetPendingRevealCards().Count}");
|
||||||
|
Debug.Log($"Is in album: {albumViewPage.IsInAlbumProper()}");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cards Spawning on Top of Each Other
|
||||||
|
|
||||||
|
**Cause:** Corner slots not properly configured with `SlotIndex` property
|
||||||
|
|
||||||
|
**Fix:**
|
||||||
|
- Ensure each slot has unique `SlotIndex` (0, 1, 2)
|
||||||
|
- Verify in inspector: Select each slot → Check `SlotIndex` field
|
||||||
|
|
||||||
|
### Card Won't Flip
|
||||||
|
|
||||||
|
**Check:**
|
||||||
|
1. Card has `CardBack` child GameObject
|
||||||
|
2. Card has `CardDisplay` child GameObject
|
||||||
|
3. Both have proper hierarchy: `Card → CardBack`, `Card → CardDisplay`
|
||||||
|
4. State transitions are correct
|
||||||
|
|
||||||
|
**Debug:**
|
||||||
|
```csharp
|
||||||
|
var cardBack = card.transform.Find("CardBack");
|
||||||
|
var cardDisplay = card.transform.Find("CardDisplay");
|
||||||
|
Debug.Log($"Back found: {cardBack != null}, Display found: {cardDisplay != null}");
|
||||||
|
Debug.Log($"Current state: {card.GetCurrentStateName()}");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Book Won't Flip to Correct Page
|
||||||
|
|
||||||
|
**Check:**
|
||||||
|
1. BookTabButton components configured with correct `zone` and `targetPage`
|
||||||
|
2. AlbumViewPage has `tabContainer` assigned
|
||||||
|
3. BookPro component reference assigned on AlbumViewPage
|
||||||
|
|
||||||
|
**Debug:**
|
||||||
|
```csharp
|
||||||
|
// In AlbumViewPage
|
||||||
|
foreach (var tab in _zoneTabs)
|
||||||
|
{
|
||||||
|
Debug.Log($"Tab: {tab.Zone} → Page {tab.TargetPage}");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cards Not Enlarging When Clicked in Album
|
||||||
|
|
||||||
|
**Check:**
|
||||||
|
1. Card is in `PlacedInSlotState`
|
||||||
|
2. AlbumViewPage has `cardEnlargedBackdrop` and `cardEnlargedContainer` assigned
|
||||||
|
3. Card was registered: `albumViewPage.RegisterCardInAlbum(card)`
|
||||||
|
|
||||||
|
**Debug:**
|
||||||
|
```csharp
|
||||||
|
var enlargedState = card.GetStateComponent<CardAlbumEnlargedState>(CardStateNames.AlbumEnlarged);
|
||||||
|
Debug.Log($"Enlarged state found: {enlargedState != null}");
|
||||||
|
Debug.Log($"Current state: {card.GetCurrentStateName()}");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memory Leaks / Cards Not Destroying
|
||||||
|
|
||||||
|
**Cause:** Event subscriptions not cleaned up
|
||||||
|
|
||||||
|
**Fix Pattern:**
|
||||||
|
```csharp
|
||||||
|
void OnEnable()
|
||||||
|
{
|
||||||
|
card.Context.OnDragStarted += HandleDrag;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDisable()
|
||||||
|
{
|
||||||
|
if (card != null && card.Context != null)
|
||||||
|
{
|
||||||
|
card.Context.OnDragStarted -= HandleDrag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Booster Packs Not Appearing
|
||||||
|
|
||||||
|
**Check:**
|
||||||
|
1. BoosterOpeningPage has `boosterPackPrefab` assigned
|
||||||
|
2. BoosterOpeningPage has `bottomRightSlots` assigned
|
||||||
|
3. Called `SetAvailableBoosterCount()` before showing page
|
||||||
|
|
||||||
|
**Debug:**
|
||||||
|
```csharp
|
||||||
|
Debug.Log($"Booster count: {CardSystemManager.Instance.GetBoosterPackCount()}");
|
||||||
|
Debug.Log($"Booster prefab: {boosterOpeningPage.boosterPackPrefab != null}");
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## State Diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
BOOSTER OPENING FLOW:
|
||||||
|
IdleState ──click──> EnlargedNewState ──click──> RevealedState
|
||||||
|
└──> EnlargedRepeatState ──┘
|
||||||
|
└──> EnlargedLegendaryRepeatState ──┘
|
||||||
|
|
||||||
|
ALBUM PLACEMENT FLOW:
|
||||||
|
PendingFaceDownState ──drag──> DraggingRevealedState ──drop on slot──> PlacedInSlotState
|
||||||
|
│
|
||||||
|
click
|
||||||
|
│
|
||||||
|
↓
|
||||||
|
AlbumEnlargedState
|
||||||
|
│
|
||||||
|
click
|
||||||
|
│
|
||||||
|
↓
|
||||||
|
PlacedInSlotState
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
- **Code Location**: `Assets/Scripts/UI/CardSystem/`
|
||||||
|
- **Card Prefabs**: `Assets/Prefabs/UI/Cards/`
|
||||||
|
- **Card Definitions**: `Assets/Data/CardSystem/Definitions/`
|
||||||
|
- **Album Scene**: `Assets/Scenes/Album.unity`
|
||||||
|
|
||||||
|
**Related Systems:**
|
||||||
|
- [UIPageController Documentation](ui_page_navigation.md) - UI page stack navigation
|
||||||
|
- [DragAndDrop System](../Scripts/UI/DragAndDrop/Core/) - Base drag/drop framework
|
||||||
|
- [Settings System](settings_readme.md) - Card system configuration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated:** November 18, 2025
|
||||||
|
**Version:** 1.0
|
||||||
|
**Contributors:** Development Team
|
||||||
|
|
||||||
Reference in New Issue
Block a user