# Card State Machine Implementation Summary ## Architecture Overview **Isolated State Pattern** using Pixelplacement StateMachine: ``` Card (RectTransform - primary animation target) ├─ CardDisplay (always visible - shows card front) ├─ CardContext (shared references + events) ├─ CardAnimator (reusable animations) └─ CardStateMachine (AppleMachine) ├─ IdleState/ │ └─ CardBackVisual ← State owns this ├─ FlippingState/ │ └─ CardBackVisual ← State owns this ├─ RevealedState/ │ ├─ NewCardIdleBadge ← State owns this │ └─ RepeatCardIdleBadge ← State owns this ├─ EnlargedNewState/ │ └─ NewCardBadge ← State owns this ├─ EnlargedRepeatState/ │ ├─ RepeatText ← State owns this │ └─ ProgressBarContainer/ │ └─ ProgressBarUI (prefab with ProgressBarController) ├─ DraggingState/ (no child visuals) ├─ PlacedInSlotState/ (no child visuals) └─ AlbumEnlargedState/ (no child visuals) ``` ## State Flow Diagrams ### **Booster Opening Flow:** ↓ [player clicks] 2. FlippingState (flip animation + scale punch) 1. IdleState (card back in assigned slot, hover enabled) ↓ [player clicks - flip animation plays within IdleState] 3a. IF NEW CARD: ↓ [determine path based on card status] 2a. IF NEW CARD: 3b. IF LEGENDARY REPEAT: → [tap] → Fires OnCardDismissed → Shrink → RevealedState 3c. IF REPEAT (won't upgrade, e.g., 2/5): 2b. IF LEGENDARY REPEAT: → Skip enlarge → RevealedState (can't upgrade) 2c. IF REPEAT (won't upgrade, e.g., 2/5): → EnlargedRepeatState 3d. IF REPEAT (WILL upgrade, e.g., 5/5): → [tap] → Fires OnCardDismissed → Shrink → RevealedState 2d. IF REPEAT (WILL upgrade, e.g., 5/5): → EnlargedRepeatState → Show progress bar (5/5) + blink → AUTO-UPGRADE (no tap needed) → Fires OnUpgradeTriggered → Update inventory → Check if new/repeat at higher rarity: IF NEW at higher: → EnlargedNewState (higher rarity) 4. RevealedState (normal size, waiting) → [tap] → Fires OnCardDismissed → Shrink → RevealedState 5. [When all cards complete] → Fires OnCardInteractionComplete → Waits for all 3 cards to finish 4. [When all cards complete] → BoosterOpeningPage animates cards to album → Destroy ``` ### **Album Placement Flow:** ``` 1. IdleState (card back in corner, hover enabled) ↓ [player clicks] 2. FlippingState (reveals which card it is) → OnFlipComplete ↓ 3. RevealedState → OnCardInteractionComplete ↓ [player drags] 4. DraggingState (scaled up during drag) ↓ [drop in slot] 5. PlacedInSlotState (in album permanently) ↓ [player clicks] 6. AlbumEnlargedState → Fires OnEnlargeRequested (page shows backdrop, reparents) ↓ [player taps] → Fires OnShrinkRequested (page prepares) → Shrink animation ↓ 5. PlacedInSlotState (back in slot) ``` ## Key Design Decisions ### 1. State-Owned Visuals - State-specific GameObjects (CardBackVisual, NewCardBadge, etc.) are **children of their state GameObject** - When state activates → children activate automatically - When state deactivates → children deactivate automatically - **No manual visibility management needed!** ### 2. Transform Animation - **Root Card.transform** is animated for position/scale (affects all children via Unity hierarchy) - **State child visuals** can be animated independently (e.g., rotating CardBackVisual during flip) - States decide WHAT to animate, CardAnimator provides HOW ### 3. Shared Resources via CardContext ```csharp public class CardContext : MonoBehaviour { // Component references public CardDisplay CardDisplay { get; } public CardAnimator Animator { get; } public AppleMachine StateMachine { get; } public Transform RootTransform { get; } // Card data public CardData CardData { get; } public bool IsNewCard { get; set; } public int RepeatCardCount { get; set; } public bool IsClickable { get; set; } // Prevents multi-flip in booster opening // Events for external coordination (BoosterOpeningPage) public event Action OnFlipComplete; public event Action OnCardDismissed; public event Action OnCardInteractionComplete; public event Action OnUpgradeTriggered; // Helper methods public void FireFlipComplete(); public void FireCardDismissed(); public void FireCardInteractionComplete(); public void FireUpgradeTriggered(); } ``` ### 4. State Transitions States explicitly transition via `_context.StateMachine.ChangeState("StateName")` Example: ```csharp // In CardFlippingState.OnFlipComplete(): if (_context.IsNewCard) _context.StateMachine.ChangeState("EnlargedNewState"); else if (_context.RepeatCardCount > 0) _context.StateMachine.ChangeState("EnlargedRepeatState"); ``` ### 5. Progress Bar Architecture **ProgressBarController Component:** - Auto-detects child Image elements (5 images in GridLayout) - Fills from bottom to top (element[0] = bottom) - Blinks newest element with configurable timing - Callback when animation completes **Usage:** ```csharp progressBar.ShowProgress(currentCount, maxCount, OnProgressComplete); ``` ## Files Created ### **Core Components:** - `StateMachine/CardContext.cs` - Shared context + events - `StateMachine/CardAnimator.cs` - Reusable animation methods (enlarge, shrink, flip, idle hover, etc.) - `ProgressBarController.cs` - Progress bar UI controller with blink animation ### **Settings:** - `Core/Settings/CardSystemSettings.cs` - ScriptableObject for all card animation timings - `Core/Settings/ICardSystemSettings.cs` - Interface for settings access ### **State Implementations:** - `States/CardIdleState.cs` - Owns CardBackVisual, idle hover, click to flip (with click blocking) - `States/CardFlippingState.cs` - Owns CardBackVisual, flip animation, Legendary shortcut - `States/CardRevealedState.cs` - Owns NewCardIdleBadge + RepeatCardIdleBadge, fires OnCardInteractionComplete - `States/CardEnlargedNewState.cs` - Owns NewCardBadge, tap to shrink - `States/CardEnlargedRepeatState.cs` - Owns RepeatText + ProgressBarUI, auto-upgrade logic - `States/CardDraggingState.cs` - Drag handling for album placement - `States/CardPlacedInSlotState.cs` - In album slot, click to enlarge - `States/CardAlbumEnlargedState.cs` - Enlarged from album, tap to shrink ## Prefab Assembly Instructions ### **Card Prefab Hierarchy:** ``` Card (RectTransform) ├─ CardDisplay (existing prefab) ├─ CardContext (component) ├─ FlippingState (CardFlippingState component) │ └─ CardBackVisual (Image) ├─ CardAnimator (component) └─ CardStateMachine (AppleMachine) ├─ IdleState (CardIdleState component) │ └─ CardBackVisual (Image) ├─ RevealedState (CardRevealedState component) │ ├─ NewCardIdleBadge (Image/Text - "NEW!") │ └─ RepeatCardIdleBadge (Image/Text - "REPEAT") │ └─ ProgressBarContainer (GameObject) │ └─ ProgressBarUI (prefab instance) │ └─ NewCardBadge (Image/Text - "NEW CARD") ├─ EnlargedRepeatState (CardEnlargedRepeatState component) │ ├─ RepeatText (Image/Text - "REPEAT CARD") │ └─ ProgressBarUI (ProgressBarController component + 5 Image children) ├─ DraggingState (CardDraggingState component) ProgressBarUI ├─ GridLayoutGroup (1 column, 5 rows, "Lower Right" corner start) ├─ ProgressElement1 (Image) ### **ProgressBarUI Prefab:** ``` ProgressBarUI (GameObject with ProgressBarController component) └─ ProgressElement5 (Image) ├─ VerticalLayoutGroup (Reverse Arrangement enabled) └─ Children (5 Images, auto-detected): ├─ ProgressElement1 (Image) - First child = 1/5 ├─ ProgressElement2 (Image) ├─ ProgressElement3 (Image) ├─ ProgressElement4 (Image) └─ ProgressElement5 (Image) - Last child = 5/5 ``` ### **Component References to Assign:** **CardContext:** - cardDisplay → CardDisplay component - cardAnimator → CardAnimator component **CardIdleState:** - cardBackVisual → CardBackVisual child GameObject **CardFlippingState:** - cardBackVisual → CardBackVisual child GameObject **CardRevealedState:** - progressBarContainer → ProgressBarContainer child GameObject - progressBar → ProgressBarController component (on ProgressBarUI prefab) - repeatCardIdleBadge → RepeatCardIdleBadge child GameObject **CardEnlargedNewState:** - newCardBadge → NewCardBadge child GameObject **CardEnlargedRepeatState:** - progressBar → ProgressBarController component (on ProgressBarUI child GameObject) - repeatText → RepeatText child GameObject ## Integration with BoosterOpeningPage ```csharp // When spawning cards: Card card = Instantiate(cardPrefab); CardContext context = card.GetComponent(); // Setup card data context.SetupCard(cardData, isNew: isNewCard, repeatCount: ownedCount); // All cards start clickable context.IsClickable = true; // Subscribe to events context.OnFlipComplete += OnCardFlipComplete; context.OnCardDismissed += OnCardDismissed; context.OnCardInteractionComplete += OnCardInteractionComplete; context.OnUpgradeTriggered += OnCardUpgraded; // Start in IdleState context.StateMachine.ChangeState("IdleState"); // When a card starts flipping, block all others private void OnCardFlipComplete(CardContext flippingCard) { // Disable all cards to prevent multi-flip foreach (CardContext card in _allCards) { card.IsClickable = false; } } // Track completion private void OnCardInteractionComplete(CardContext card) { _cardsCompletedInteraction++; if (_cardsCompletedInteraction == 3) { AnimateCardsToAlbum(); // All cards revealed, animate to album } else { // Re-enable unflipped cards foreach (CardContext c in _allCards) { if (c.StateMachine.CurrentState.name == "IdleState") { c.IsClickable = true; } } } } ``` ## Benefits vs Old System | Aspect | Old System | New System | |--------|-----------|------------| | Components per card | FlippableCard + AlbumCard + wrappers | 1 Card + states | | Animation code duplication | ~200 lines across 5 files | 0 (shared CardAnimator) | | State tracking | 12+ boolean flags (_isFlipped, _isFlipping, _isWaitingForTap, etc.) | 1 active state name | | Visual element management | Manual SetActive() in 8+ places | Automatic via state activation | | Adding new behaviors | Modify 3-4 components + events | Add 1 new state GameObject | | Prefab nesting | FlippableCard → AlbumCard → CardDisplay (5 layers) | Card → States (flat hierarchy) | | Debugging state | Check 12 booleans across files | Look at active state name in inspector | | Progress bar logic | 50 lines in FlippableCard.ShowProgressBar() | Isolated in ProgressBarController | | Upgrade logic | TriggerUpgradeTransition (80 lines in FlippableCard) | TriggerUpgrade (isolated in CardEnlargedRepeatState) | | Event coordination | 4 events on FlippableCard, 2 on AlbumCard | 4 events on CardContext (centralized) | ## Testing Checklist - [ ] Booster opening: NEW card shows badge → tap → shrinks → shows NEW idle badge - [ ] Booster opening: REPEAT card (2/5) shows REPEAT text + progress → blink → tap → shrinks → shows REPEAT idle badge - [ ] Booster opening: REPEAT card (5/5) auto-upgrades → shows NEW at higher rarity - [ ] Booster opening: Legendary repeat skips enlarge - [ ] Booster opening: Click blocking prevents multi-flip - [ ] Booster opening: All 3 cards complete → animate to album - [ ] Album placement: Card in corner → click → reveals → drag → place in slot - [ ] Album placement: Card in slot → click → enlarges → tap → shrinks back - [ ] Cascading upgrades (Common → Uncommon → Rare in one reveal) - [ ] Progress bar shows correctly (1/5, 2/5, 3/5, 4/5, 5/5) - [ ] Progress bar blinks newest element - [ ] Idle hover animation works in both flows - [ ] Hover scale works on pointer enter/exit ## Integration Work Remaining 1. Update BoosterOpeningPage to use new Card prefab instead of FlippableCard 2. Update AlbumViewPage to use new Card prefab instead of AlbumCard 3. Migrate album placement drag/drop to use DraggingState 4. Remove old FlippableCard.cs and AlbumCard.cs after migration 5. **(Optional)** Add Jiggle() animation to CardAnimator for clicking inactive cards ## Migration Path **Phase 1: Side-by-side (Current)** - New state machine exists alongside old FlippableCard/AlbumCard - Can test new system without breaking existing functionality **Phase 2: Booster Opening Migration** - Update BoosterOpeningPage to spawn new Card prefab - Remove FlippableCard references - Test all booster flows **Phase 3: Album Migration** - Update AlbumViewPage to spawn new Card prefab - Remove AlbumCard references - Test album placement and enlarge **Phase 4: Cleanup** - Delete FlippableCard.cs - Delete AlbumCard.cs - Delete old wrapper components - Clean up unused prefab variants ## Example: Adding New Card Behavior **Scenario:** Add a "Trading" state where card shows trade UI? **Old system:** 1. Modify FlippableCard.cs (add boolean, methods, events) 2. Modify AlbumCard.cs (add pass-through logic) 3. Update 3-4 wrapper components 4. Add new events and subscriptions 5. Manually manage trade UI visibility **New system:** 1. Create `CardTradingState.cs`: ```csharp public class CardTradingState : AppleState { [SerializeField] private GameObject tradeUI; public override void OnEnterState() { // tradeUI automatically activates with state! } } ``` 2. Add TradingState GameObject under CardStateMachine 3. Add trade UI as child of TradingState 4. Call `ChangeState("TradingState")` from wherever needed **Done! Zero other files modified.** --- ## Summary The new state machine implementation successfully replicates all core FlippableCard/AlbumCard functionality with: - ✅ Cleaner architecture (state pattern vs boolean soup) - ✅ Less code duplication (shared CardAnimator) - ✅ Easier debugging (visible state names) - ✅ Simpler extension (add states vs modify monoliths) - ✅ Better separation of concerns (each state owns its visuals) **Status: Core implementation complete, ready for prefab assembly and integration testing.**