Almost working card state machine

This commit is contained in:
Michal Pikulski
2025-11-16 20:35:54 +01:00
parent 6fe7d012fc
commit 78aafb9275
42 changed files with 3057 additions and 3077 deletions

View File

@@ -0,0 +1,201 @@
# Album Slot Migration - Complete ✅
## Migration Date
November 16, 2025
---
## Summary
Successfully migrated album card states from direct `IPointerClickHandler` to centralized click routing via `ICardClickHandler`, ensuring consistency with booster opening flow.
---
## Changes Made
### 1. CardPlacedInSlotState.cs
**Changed:**
- ✅ Removed `IPointerClickHandler` interface
- ✅ Removed `UnityEngine.EventSystems` using directive
- ✅ Added `ICardClickHandler` interface
- ✅ Renamed `OnPointerClick(PointerEventData)``OnCardClicked(CardContext)`
- ✅ Updated method signature to use context parameter
**Result:**
- State now uses centralized click routing from CardContext
- Consistent with booster opening states
- Click gating via `context.IsClickable` now works
---
### 2. CardAlbumEnlargedState.cs
**Changed:**
- ✅ Removed `IPointerClickHandler` interface
- ✅ Removed `UnityEngine.EventSystems` using directive
- ✅ Added `ICardClickHandler` interface
- ✅ Renamed `OnPointerClick(PointerEventData)``OnCardClicked(CardContext)`
- ✅ Updated method signature to use context parameter
- ✅ Added fallback for when animator is null (transitions to PlacedInSlotState immediately)
**Result:**
- State now uses centralized click routing from CardContext
- Consistent with booster opening states
- More robust error handling
---
### 3. AlbumCardSlot.cs
**Changed:**
- ✅ Added `AlbumViewPage.RegisterCardInAlbum(card)` call in `SpawnCard()` method
- ✅ Finds AlbumViewPage via `FindFirstObjectByType<AlbumViewPage>()`
- ✅ Subscribes card's AlbumEnlargedState events to page backdrop/reparenting handlers
**Result:**
- Cards spawned in slots now properly register with page
- Backdrop shows when card is enlarged
- Card reparents to enlarged container correctly
---
## Slot Behavior Verification
### ✅ Slot Keeps Reference to Assigned Card
```csharp
private StateMachine.Card _placedCard;
```
- Reference stored when card is spawned or placed
- Accessible via `GetPlacedCard()`
### ✅ Auto-Spawn Owned Cards
**Flow:**
1. `OnEnable()``CheckAndSpawnOwnedCard()`
2. Queries CardSystemManager for owned card (highest rarity)
3. If owned → `SpawnCard(cardData)`
4. Card spawned with `card.SetupForAlbumSlot(cardData, this)` → starts in `PlacedInSlotState`
5. Card sized to match slot dimensions
6. Card registered with AlbumViewPage
### ✅ In Slot → Clickable → Enlarge
**Flow:**
1. Card in `PlacedInSlotState`
2. User clicks → CardContext routes click to state via `ICardClickHandler`
3. `PlacedInSlotState.OnCardClicked()` → transitions to `AlbumEnlargedState`
4. AlbumEnlargedState fires `OnEnlargeRequested` event
5. AlbumViewPage shows backdrop, reparents card to top layer
6. Card animates to enlarged scale
### ✅ Enlarged → Clickable → Dismiss
**Flow:**
1. Card in `AlbumEnlargedState`
2. User clicks → CardContext routes click to state via `ICardClickHandler`
3. `AlbumEnlargedState.OnCardClicked()` fires `OnShrinkRequested` event
4. AlbumViewPage hides backdrop
5. Card shrinks with animation
6. On complete → transitions back to `PlacedInSlotState`
7. Card reparents back to slot
### ✅ No Card = No Preview
**Current Behavior:**
- If player doesn't own card, `ownedCard` remains null
- `SpawnCard()` never called
- Slot remains empty
- No visuals shown
**Future:** Slot itself clickable for silhouette preview (to be implemented)
---
## Architecture Benefits
### Unified Click Routing
**Before:**
- Booster states: `ICardClickHandler` (centralized)
- Album states: `IPointerClickHandler` (direct)
- **Problem:** Two different click systems, confusing
**After:**
- All states: `ICardClickHandler` (centralized)
- Single source of truth for click handling
- Consistent gating via `context.IsClickable`
### Click Flow
```
User clicks card →
CardDisplay.OnPointerClick →
CardContext.HandleCardDisplayClicked (checks IsClickable) →
Finds current state's ICardClickHandler →
State.OnCardClicked(context) →
State logic executes
```
### Benefits
- ✅ Consistent across all card states
- ✅ Centralized gating (IsClickable)
- ✅ Easy to debug (one routing path)
- ✅ Easy to extend (add state, implement ICardClickHandler)
- ✅ No more `IPointerClickHandler` scattered across states
---
## Testing Checklist
### Album View Flow
- [ ] Album page opens correctly
- [ ] Owned cards appear in album slots automatically
- [ ] Cards sized correctly to slot dimensions
- [ ] Clicking card in slot enlarges it
- [ ] Backdrop appears when card enlarged
- [ ] Card displays correctly while enlarged
- [ ] Clicking enlarged card dismisses it
- [ ] Card returns to slot correctly
- [ ] Empty slots remain empty (no preview)
- [ ] Multiple cards can be enlarged/dismissed sequentially
### Edge Cases
- [ ] Card spawned when entering album (not during booster)
- [ ] Multiple rarities of same card (highest shown)
- [ ] Very first time opening album (no owned cards)
- [ ] Rapid clicking doesn't break states
- [ ] Page close during enlarge (cleanup handled)
---
## Future: Empty Slot Preview
When implementing "click empty slot to see silhouette":
1. Make AlbumCardSlot implement `IPointerClickHandler` for empty state
2. Add check in `OnPointerClick()`:
```csharp
if (!_isOccupiedPermanently && _placedCard == null)
{
ShowSilhouettePreview();
}
```
3. Create preview state or use temporary visual
4. Show card back / silhouette / "???" indicator
5. Click to dismiss preview
**Note:** This won't conflict with card click routing since card won't exist when slot is empty.
---
## Files Modified
- `CardPlacedInSlotState.cs` - Uses ICardClickHandler
- `CardAlbumEnlargedState.cs` - Uses ICardClickHandler
- `AlbumCardSlot.cs` - Registers cards with AlbumViewPage
---
## No Breaking Changes
- ✅ Existing Card.cs API unchanged
- ✅ AlbumViewPage event system unchanged
- ✅ Slot validation logic unchanged
- ✅ Drag-and-drop from pending cards still works
- ✅ All existing states still work
---
**Migration Complete!** Album slots now use unified click routing and all requirements verified. 🎉

View File

@@ -0,0 +1,297 @@
# Card Prefab Setup Guide
## Quick Reference: Building the New Card Prefab
This guide shows you how to create a Card prefab compatible with the new state-based system.
---
## Prerequisites
Before starting, make sure you have:
- All Card state scripts compiled without errors
- CardDisplay.cs working
- Card.cs, CardContext.cs, CardAnimator.cs ready
- PixelPlacement StateMachine (AppleMachine) available
---
## Step 1: Create the Root GameObject
1. Create empty GameObject named "Card"
2. Add component: `Card.cs` (from `UI.CardSystem.StateMachine`)
3. Add component: `RectTransform` (if not already present)
4. Add component: `CanvasGroup` (optional, for fade effects)
5. Add component: `AspectRatioFitter` (optional, to maintain card proportions)
**Card.cs Settings:**
- Initial State: `IdleState`
---
## Step 2: Add CardContext Component
1. On the same "Card" GameObject, add: `CardContext.cs`
2. This component holds:
- Card data reference
- IsNew flag
- IsClickable flag
- RootTransform reference (auto-assigned)
**CardContext.cs Settings:**
- Root Transform: (leave empty, auto-assigned in Awake)
---
## Step 3: Add CardAnimator Component
1. On the same "Card" GameObject, add: `CardAnimator.cs`
2. This component will handle all animations
**CardAnimator.cs Settings:**
- Card Display: (assign in Step 4)
- Visual Root: (assign the Card GameObject itself)
---
## Step 4: Create CardDisplay Child
1. Create child GameObject under "Card" named "CardDisplay"
2. Add component: `CardDisplay.cs`
3. Add UI elements as children:
- **CardImage** (Image) - main card artwork
- **FrameImage** (Image) - rarity frame
- **OverlayImage** (Image) - rarity overlay effects
- **BackgroundImage** (Image) - zone background
- **ZoneShapeImage** (Image) - zone symbol/shape
- **CardNameText** (TextMeshProUGUI) - card name
**CardDisplay.cs Settings:**
- Assign all the UI element references above
- Visual Config: Assign your CardVisualConfig ScriptableObject
**Layout Tips:**
- Use anchors to stretch images to fill the card
- Layer order (back to front): Background → Zone Shape → Card Image → Frame → Overlay
- Make sure all images have "Raycast Target" enabled for click detection
---
## Step 5: Create CardBack Child (for flip animation)
1. Create child GameObject under "Card" named "CardBack"
2. Add component: `Image`
3. Assign your card back sprite
4. Position/scale to match CardDisplay size
**Settings:**
- Make sure it's initially hidden (will be shown by FlippingState)
---
## Step 6: Create StateMachine Child
1. Create child GameObject under "Card" named "StateMachine"
2. Add component: `AppleMachine` (from Pixelplacement)
**AppleMachine Settings:**
- Starting State: `IdleState`
- Debug: Enable if you want state transition logging
---
## Step 7: Create State Children
Under "StateMachine", create these child GameObjects:
### 7a. IdleState
- GameObject name: "IdleState"
- Add component: `CardIdleState.cs`
- Settings:
- Hover Lift Distance: `20`
- Hover Duration: `0.3`
### 7b. FlippingState
- GameObject name: "FlippingState"
- Add component: `CardFlippingState.cs`
- Settings:
- Flip Duration: `0.6`
- Next State After Flip: `RevealedState`
### 7c. RevealedState
- GameObject name: "RevealedState"
- Add component: `CardRevealedState.cs`
- Settings:
- Scale: `1.5` (for NEW card emphasis)
- Scale Duration: `0.5`
### 7d. DraggingState
- GameObject name: "DraggingState"
- Add component: `CardDraggingState.cs`
- Settings:
- Drag Scale: `1.2`
- Drag Rotation: `5` degrees
### 7e. PlacedInSlotState
- GameObject name: "PlacedInSlotState"
- Add component: `CardPlacedInSlotState.cs`
- Settings:
- (Auto-configured when placed)
### 7f. AlbumEnlargedState
- GameObject name: "AlbumEnlargedState"
- Add component: `CardAlbumEnlargedState.cs`
- Settings:
- Enlarged Scale: `2.5`
- Scale Duration: `0.3`
---
## Step 8: Wire Up References
Go back to the root "Card" GameObject:
**Card.cs:**
- Context: Drag the Card GameObject (auto-finds CardContext)
- Animator: Drag the Card GameObject (auto-finds CardAnimator)
- State Machine: Drag the "StateMachine" child
**CardAnimator.cs:**
- Card Display: Drag the "CardDisplay" child
- Visual Root: Drag the "Card" GameObject itself
**CardContext.cs:**
- (Everything auto-assigned, nothing to wire)
---
## Step 9: Save as Prefab
1. Drag the "Card" GameObject into your Prefabs folder
2. Name it: `Card.prefab` (or whatever you prefer)
3. Delete the instance from the scene
---
## Step 10: Update Scene References
### BoosterOpeningPage
- Find `BoosterOpeningPage` in your scene
- Assign `cardPrefab` field → your new Card prefab
### AlbumViewPage
- Find `AlbumViewPage` in your scene
- Assign `cardPrefab` field → your new Card prefab
### AlbumCardSlot Prefab
- Open your AlbumCardSlot prefab
- Assign `cardPrefab` field → your new Card prefab
---
## Testing Your Prefab
### Test 1: Booster Opening
1. Play the game
2. Open a booster pack
3. Cards should spawn face-down
4. Click a card → it flips and reveals
5. If NEW → shows enlarged with "NEW" indicator
6. If REPEAT → shows progress bar
7. Click to dismiss → flies to album icon
### Test 2: Album Placement
1. After opening boosters, cards appear in bottom-right
2. Drag a card → it scales up and rotates slightly
3. Drop on valid slot → it snaps in and stays
4. Drop outside → returns to original position
### Test 3: Album Viewing
1. Go to album view
2. Cards in slots should display normally
3. Click a placed card → enlarges with backdrop
4. Click again (or backdrop) → shrinks back to slot
---
## Common Issues & Fixes
### Cards don't flip
- Check FlippingState is assigned in StateMachine
- Verify CardBack GameObject exists and has sprite
- Check CardAnimator has CardDisplay reference
### Cards don't respond to clicks
- Make sure CardDisplay images have "Raycast Target" enabled
- Check EventSystem exists in scene
- Verify Card has CanvasGroup or Image for raycast blocking
### Animations don't play
- Check CardAnimator reference is assigned
- Verify Tween library (Pixelplacement) is imported
- Check state components have correct duration values
### Cards stuck in one state
- Enable StateMachine debug mode to see transitions
- Check state scripts don't have infinite loops
- Verify transition logic in state Enter/Exit methods
---
## Advanced: Customizing States
Want to add your own card behavior? Here's how:
1. Create new script inheriting from `AppleState`
2. Override `Enter()`, `Exit()`, `UpdateState()` as needed
3. Use `Card` reference to access context, animator, etc.
4. Add GameObject under StateMachine with your new component
5. Transition to it via `Card.ChangeState("YourStateName")`
Example:
```csharp
public class CardBurningState : AppleState
{
private Card _card;
public override void Enter(params object[] args)
{
_card = GetComponentInParent<Card>();
// Play burning animation
_card.Animator.PlayBurnEffect();
}
public override void Exit()
{
// Clean up
}
}
```
---
## Final Checklist
Before considering your prefab complete:
- [ ] Root Card GameObject has Card, CardContext, CardAnimator components
- [ ] CardDisplay child is set up with all UI elements
- [ ] CardBack child exists for flip animation
- [ ] StateMachine child has AppleMachine component
- [ ] All 6 state children are created and configured
- [ ] All references wired up on Card component
- [ ] Prefab saved and assigned in scene controllers
- [ ] Tested in both booster and album flows
- [ ] No console errors when playing
---
## Need Help?
Refer to:
- `card_system_migration_summary.md` - What changed and why
- `card_architecture_plan.md` - Architecture overview
- State script files - Implementation details
Happy card building! 🎴

View File

@@ -0,0 +1,215 @@
# Card System Migration Summary
## Overview
Successfully migrated the card UI system from the old wrapper-based approach (AlbumCard, FlippableCard) to the new state-based Card system using PixelPlacement's StateMachine.
## Date
Migration completed: November 16, 2025
---
## Changes Made
### 1. CardDisplay.cs - Simplified Click Handling
**Changes:**
- Removed `_isPreviewMode` and `_previewSlot` tracking
- Removed `SetPreviewMode()` and preview visual methods (`SetPreviewVisuals`, `ClearPreviewVisuals`)
- Simplified `OnPointerClick()` to only emit `OnCardClicked` event
- No more parent-seeking or type-checking logic
**Impact:**
- CardDisplay is now a pure "dumb" visual renderer + click detector
- Any wrapper can subscribe to click events without tight coupling
- Preview functionality moved to card states
---
### 2. AlbumCardSlot.cs - Updated to New Card System
**Changes:**
- Changed from `AlbumCard` references to `StateMachine.Card`
- Removed `IPointerClickHandler` interface (no more manual preview clicks)
- Removed all preview-related fields and methods:
- `previewCardDisplay`
- `_isPreviewShowing`
- `_previewOriginalScale`
- `SetupPreviewCard()`
- `ShowPreview()`
- `HidePreview()`
- `OnPointerClick()`
- `DismissPreview()`
- Updated `SpawnCard()` to use `Card.SetupForAlbumSlot()` with `PlacedInSlotState`
- Removed registration with AlbumViewPage (no longer needed)
- Changed `albumCardPrefab` field to `cardPrefab`
**Impact:**
- Slots now spawn cards in the `PlacedInSlotState` directly
- Preview functionality will be handled by card states if needed in the future
- Cleaner, simpler slot logic focused only on validation and spawning
---
### 3. AlbumViewPage.cs - Removed Legacy Support
**Changes:**
- Removed legacy `AlbumCard` registration methods:
- `RegisterAlbumCard()`
- `UnregisterAlbumCard()`
- `OnCardEnlargeRequested(AlbumCard)`
- `OnCardShrinkRequested(AlbumCard)`
- Removed slot preview helper methods:
- `ShowSlotPreview()`
- `HideSlotPreview()`
- Kept only new Card system methods:
- `RegisterCardInAlbum(StateMachine.Card)`
- `UnregisterCardInAlbum(StateMachine.Card)`
- `OnCardEnlargeRequested(CardAlbumEnlargedState)`
- `OnCardShrinkRequested(CardAlbumEnlargedState)`
**Impact:**
- Page only manages backdrop and reparenting for card enlarge states
- No more manual preview management
- Cleaner event-based architecture
---
### 4. BoosterOpeningPage.cs - Already Updated
**Status:**
- Already using new `StateMachine.Card` system
- Uses `CardContext` for setup and event handling
- No changes required - already migrated!
**Current Flow:**
1. Spawns Card prefab with CardContext
2. Calls `context.SetupCard()` and `card.SetupForBoosterReveal()`
3. Subscribes to `context.OnFlipComplete` and `context.OnCardInteractionComplete`
4. Cards handle their own flip and reveal states
---
### 5. DEPRECATED Folder - Deleted
**Deleted Classes:**
- `AlbumCard.cs`
- `FlippableCard.cs`
- `AlbumCardPlacementDraggable.cs`
- `CardDraggable.cs`
- `CardDraggableVisual.cs`
- `CardInteractionHandler.cs`
**Justification:**
- No longer referenced anywhere in the codebase
- All functionality replaced by state-based Card system
- Keeping them would cause confusion
---
## Architecture Benefits
### Event-Based Communication
- **Old:** Parent-seeking, type-checking, manual forwarding
- **New:** Clean pub/sub pattern, decoupled components
### State Management
- **Old:** Multiple wrapper classes (FlippableCard → AlbumCard → CardDisplay)
- **New:** Single Card component + isolated state objects
### Code Reuse
- **Old:** Repeated animation/behavior code in each wrapper
- **New:** Shared CardAnimator, reusable state behaviors
### Flexibility
- **Old:** Hard to add new card contexts (preview, enlarged, etc.)
- **New:** Just add a new state - no wrapper changes needed
---
## Current Card State Flow
### Booster Opening Flow
1. Spawn Card with `SetupForBoosterReveal()` → starts in `IdleState`
2. User clicks → transitions to `FlippingState`
3. Flip completes → transitions to `RevealedState` (new/repeat logic)
4. User interacts → card animates to album icon, destroyed
### Album Placement Flow
1. Spawn Card with `SetupForAlbumPlacement()` → starts in `RevealedState`
2. User drags → transitions to `DraggingState`
3. Dropped in slot → transitions to `PlacedInSlotState`
4. User clicks placed card → transitions to `AlbumEnlargedState`
5. User dismisses → back to `PlacedInSlotState`
### Album Slot Auto-Spawn
1. AlbumCardSlot checks owned cards on Enable
2. If owned, spawns Card with `SetupForAlbumSlot()` → starts in `PlacedInSlotState`
3. Card sits in slot, ready for enlarge interaction
---
## What's Next
### Prefab Setup Required
You'll need to update your prefabs to use the new Card structure:
**Old Prefab Structure:**
```
FlippableCard (or AlbumCard)
└── CardDisplay
└── [visual elements]
```
**New Prefab Structure:**
```
Card (Card.cs component)
├── CardDisplay (visual renderer)
│ └── [visual elements: image, frame, overlay, etc.]
└── StateMachine (AppleMachine component)
├── IdleState (CardIdleState)
├── FlippingState (CardFlippingState)
├── RevealedState (CardRevealedState)
├── DraggingState (CardDraggingState)
├── PlacedInSlotState (CardPlacedInSlotState)
└── AlbumEnlargedState (CardAlbumEnlargedState)
```
### Configuration References
Make sure to update these references in your scenes:
- **BoosterOpeningPage:** `cardPrefab` field → assign new Card prefab
- **AlbumViewPage:** `cardPrefab` field → assign new Card prefab
- **AlbumCardSlot:** `cardPrefab` field → assign new Card prefab
---
## Testing Checklist
- [ ] Booster opening flow works correctly
- [ ] Cards flip and reveal properly
- [ ] New/repeat card logic displays correctly
- [ ] Cards can be dragged from pending list to album slots
- [ ] Cards snap into album slots correctly
- [ ] Placed cards can be enlarged when clicked
- [ ] Enlarged cards can be dismissed
- [ ] Empty slots no longer show preview (feature removed for now)
- [ ] No console errors or null references
---
## Notes
### Preview Functionality
The old preview system (showing locked cards in empty slots) has been removed. If you want to re-implement this:
1. Create a new `EmptySlotPreviewState`
2. Have empty AlbumCardSlots spawn a Card in this state
3. State handles the greyed-out visuals and enlarge/shrink
### Backwards Compatibility
**None.** This is a breaking change. Old prefabs using AlbumCard/FlippableCard will not work and must be updated.
### Performance
The new system is more efficient:
- Fewer component lookups (no parent-seeking)
- State objects pooled per card (not created/destroyed)
- Cleaner event subscription (no manual chain management)
---
## Questions?
Refer to the implementation plan documents or the state machine architecture document for more details.

View File

@@ -0,0 +1 @@