Stash work
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -44,6 +44,8 @@ namespace UI.CardSystem
|
|||||||
private Input.InputMode _previousInputMode;
|
private Input.InputMode _previousInputMode;
|
||||||
private List<StateMachine.Card> _activeCards = new List<StateMachine.Card>();
|
private List<StateMachine.Card> _activeCards = new List<StateMachine.Card>();
|
||||||
private const int MAX_VISIBLE_CARDS = 3;
|
private const int MAX_VISIBLE_CARDS = 3;
|
||||||
|
private List<StateMachine.Card> _pendingCornerCards = new List<StateMachine.Card>();
|
||||||
|
private const int MAX_PENDING_CORNER = 3;
|
||||||
|
|
||||||
internal override void OnManagedStart()
|
internal override void OnManagedStart()
|
||||||
{
|
{
|
||||||
@@ -679,5 +681,87 @@ namespace UI.CardSystem
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SpawnPendingCornerCards()
|
||||||
|
{
|
||||||
|
if (cardPrefab == null || bottomRightSlots == null) return;
|
||||||
|
CleanupPendingCornerCards();
|
||||||
|
for (int i = 0; i < MAX_PENDING_CORNER; i++)
|
||||||
|
{
|
||||||
|
var slot = FindSlotByIndex(i);
|
||||||
|
if (slot == null) break;
|
||||||
|
GameObject cardObj = Instantiate(cardPrefab, bottomRightSlots.transform);
|
||||||
|
var card = cardObj.GetComponent<StateMachine.Card>();
|
||||||
|
if (card != null)
|
||||||
|
{
|
||||||
|
card.SetupForAlbumPending();
|
||||||
|
card.AssignToSlot(slot, true);
|
||||||
|
_pendingCornerCards.Add(card);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Destroy(cardObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CleanupPendingCornerCards()
|
||||||
|
{
|
||||||
|
foreach (var c in _pendingCornerCards)
|
||||||
|
{
|
||||||
|
if (c != null) Destroy(c.gameObject);
|
||||||
|
}
|
||||||
|
_pendingCornerCards.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandlePendingCardDragStart(StateMachine.Card cornerCard)
|
||||||
|
{
|
||||||
|
// Select smart pending card data
|
||||||
|
var selected = SelectSmartPendingCard();
|
||||||
|
if (selected == null)
|
||||||
|
{
|
||||||
|
return; // no pending data
|
||||||
|
}
|
||||||
|
cornerCard.Context.SetupCard(selected);
|
||||||
|
// Navigate album to page
|
||||||
|
int targetPage = FindPageForCard(selected);
|
||||||
|
if (targetPage >= 0)
|
||||||
|
{
|
||||||
|
NavigateToAlbumPage(targetPage);
|
||||||
|
}
|
||||||
|
// Begin flip state
|
||||||
|
cornerCard.ChangeState("FlippingPendingState");
|
||||||
|
}
|
||||||
|
|
||||||
|
private CardData SelectSmartPendingCard()
|
||||||
|
{
|
||||||
|
if (CardSystemManager.Instance == null) return null;
|
||||||
|
var pending = CardSystemManager.Instance.GetPendingRevealCards();
|
||||||
|
if (pending.Count == 0) return null;
|
||||||
|
// Try current page match
|
||||||
|
var pageDefs = GetDefinitionsOnCurrentPage();
|
||||||
|
var match = pending.Find(c => pageDefs.Contains(c.DefinitionId));
|
||||||
|
if (match != null) return match;
|
||||||
|
// Fallback random
|
||||||
|
int idx = Random.Range(0, pending.Count);
|
||||||
|
return pending[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<string> GetDefinitionsOnCurrentPage()
|
||||||
|
{
|
||||||
|
// Placeholder: gather from slots on current page
|
||||||
|
return new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int FindPageForCard(CardData data)
|
||||||
|
{
|
||||||
|
// Placeholder: map definition to page index
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NavigateToAlbumPage(int pageIndex)
|
||||||
|
{
|
||||||
|
// Placeholder: call book/page flip controller
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
Assets/Scripts/UI/CardSystem/CardBack.cs
Normal file
23
Assets/Scripts/UI/CardSystem/CardBack.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace UI.CardSystem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Simple component representing card back visuals; toggle visibility.
|
||||||
|
/// </summary>
|
||||||
|
public class CardBack : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private Image backImage;
|
||||||
|
|
||||||
|
public void Show()
|
||||||
|
{
|
||||||
|
gameObject.SetActive(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Hide()
|
||||||
|
{
|
||||||
|
gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Assets/Scripts/UI/CardSystem/CardBack.cs.meta
Normal file
3
Assets/Scripts/UI/CardSystem/CardBack.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 37d815ba7b02481786cc1953678a3e8e
|
||||||
|
timeCreated: 1763322207
|
||||||
@@ -56,39 +56,65 @@ namespace UI.CardSystem.StateMachine
|
|||||||
protected override void OnDragStartedHook()
|
protected override void OnDragStartedHook()
|
||||||
{
|
{
|
||||||
base.OnDragStartedHook();
|
base.OnDragStartedHook();
|
||||||
|
string current = GetCurrentStateName();
|
||||||
// Transition to dragging state when drag begins
|
if (current == "PendingFaceDownState")
|
||||||
|
{
|
||||||
|
// Notify AlbumViewPage to assign data & flip
|
||||||
|
var albumPage = FindObjectOfType<AlbumViewPage>();
|
||||||
|
if (albumPage != null)
|
||||||
|
{
|
||||||
|
albumPage.HandlePendingCardDragStart(this);
|
||||||
|
}
|
||||||
|
// State change will be triggered by album page after data assignment
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
Logging.Debug($"[Card] Drag started on {CardData?.Name}, transitioning to DraggingState");
|
Logging.Debug($"[Card] Drag started on {CardData?.Name}, transitioning to DraggingState");
|
||||||
ChangeState("DraggingState");
|
ChangeState("DraggingState");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnDragEndedHook()
|
protected override void OnDragEndedHook()
|
||||||
{
|
{
|
||||||
base.OnDragEndedHook();
|
base.OnDragEndedHook();
|
||||||
|
string current = GetCurrentStateName();
|
||||||
// Check if we dropped in a valid album slot
|
if (current == "DraggingState")
|
||||||
|
{
|
||||||
|
// Existing logic
|
||||||
if (CurrentSlot is AlbumCardSlot albumSlot)
|
if (CurrentSlot is AlbumCardSlot albumSlot)
|
||||||
{
|
{
|
||||||
Logging.Debug($"[Card] Dropped in album slot, transitioning to PlacedInSlotState");
|
Logging.Debug($"[Card] Dropped in album slot, transitioning to PlacedInSlotState");
|
||||||
|
|
||||||
// Set the parent slot on PlacedInSlotState
|
|
||||||
var placedState = GetStateComponent<States.CardPlacedInSlotState>("PlacedInSlotState");
|
var placedState = GetStateComponent<States.CardPlacedInSlotState>("PlacedInSlotState");
|
||||||
if (placedState != null)
|
if (placedState != null)
|
||||||
{
|
{
|
||||||
placedState.SetParentSlot(albumSlot);
|
placedState.SetParentSlot(albumSlot);
|
||||||
}
|
}
|
||||||
|
|
||||||
ChangeState("PlacedInSlotState");
|
ChangeState("PlacedInSlotState");
|
||||||
|
|
||||||
// Notify listeners (AlbumViewPage) that this pending card was placed
|
|
||||||
OnPlacedInAlbumSlot?.Invoke(this, albumSlot);
|
OnPlacedInAlbumSlot?.Invoke(this, albumSlot);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logging.Debug($"[Card] Dropped outside valid slot, returning to RevealedState");
|
Logging.Debug("[Card] Dropped outside valid slot, returning to RevealedState");
|
||||||
ChangeState("RevealedState");
|
ChangeState("RevealedState");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (current == "DraggingRevealedState")
|
||||||
|
{
|
||||||
|
// Pending revealed drag state end
|
||||||
|
if (CurrentSlot is AlbumCardSlot albumSlot)
|
||||||
|
{
|
||||||
|
var placedState = GetStateComponent<States.CardPlacedInSlotState>("PlacedInSlotState");
|
||||||
|
if (placedState != null) placedState.SetParentSlot(albumSlot);
|
||||||
|
ChangeState("PlacedInSlotState");
|
||||||
|
OnPlacedInAlbumSlot?.Invoke(this, albumSlot);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Return to corner face-down
|
||||||
|
ChangeState("PendingFaceDownState");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -148,6 +174,17 @@ namespace UI.CardSystem.StateMachine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setup for album pending state (starts at PendingFaceDownState)
|
||||||
|
/// Dragging is ENABLED; state will assign data when dragged
|
||||||
|
/// </summary>
|
||||||
|
public void SetupForAlbumPending()
|
||||||
|
{
|
||||||
|
// Start with no data; state will assign when dragged
|
||||||
|
SetupCard(null, "PendingFaceDownState");
|
||||||
|
SetDraggingEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Transition to a specific state
|
/// Transition to a specific state
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using Core.SaveLoad;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UI.CardSystem.StateMachine.States
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Dragging revealed state for pending cards after flip; minimal overlay of CardDraggingState but no badges.
|
||||||
|
/// </summary>
|
||||||
|
public class CardDraggingRevealedState : AppleState
|
||||||
|
{
|
||||||
|
private CardContext context;
|
||||||
|
private Vector3 originalScale;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
context = GetComponentInParent<CardContext>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnEnterState()
|
||||||
|
{
|
||||||
|
if (context == null) return;
|
||||||
|
if (context.CardDisplay != null)
|
||||||
|
{
|
||||||
|
context.CardDisplay.gameObject.SetActive(true);
|
||||||
|
context.CardDisplay.transform.localRotation = Quaternion.Euler(0,0,0);
|
||||||
|
}
|
||||||
|
originalScale = context.RootTransform.localScale;
|
||||||
|
context.RootTransform.localScale = originalScale * 1.15f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
if (context?.RootTransform != null)
|
||||||
|
{
|
||||||
|
context.RootTransform.localScale = originalScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ce2483293cdd4680b5095afc1fcb2fde
|
||||||
|
timeCreated: 1763322199
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using Core;
|
||||||
|
using Core.SaveLoad;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UI.CardSystem.StateMachine.States
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handles flipping a pending face-down card after data assignment.
|
||||||
|
/// Transitions to DraggingRevealedState when flip completes.
|
||||||
|
/// </summary>
|
||||||
|
public class CardFlippingPendingState : AppleState
|
||||||
|
{
|
||||||
|
private CardContext context;
|
||||||
|
private GameObject cardBack;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
context = GetComponentInParent<CardContext>();
|
||||||
|
if (context != null)
|
||||||
|
{
|
||||||
|
var backTransform = context.RootTransform.Find("CardBack");
|
||||||
|
if (backTransform != null) cardBack = backTransform.gameObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnEnterState()
|
||||||
|
{
|
||||||
|
if (context == null) return;
|
||||||
|
// Ensure card back visible and front hidden at start
|
||||||
|
if (cardBack != null) cardBack.SetActive(true);
|
||||||
|
if (context.CardDisplay != null) context.CardDisplay.gameObject.SetActive(false);
|
||||||
|
|
||||||
|
// Optional: album navigation
|
||||||
|
var albumPage = Object.FindObjectOfType<AlbumViewPage>();
|
||||||
|
if (albumPage != null && context.CardData != null)
|
||||||
|
{
|
||||||
|
int targetPage = albumPage.FindPageForCard(context.CardData); // placeholder; method may be private
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.Animator != null)
|
||||||
|
{
|
||||||
|
Transform back = cardBack != null ? cardBack.transform : null;
|
||||||
|
Transform front = context.CardDisplay != null ? context.CardDisplay.transform : null;
|
||||||
|
context.Animator.PlayFlip(back, front, onComplete: () =>
|
||||||
|
{
|
||||||
|
context.StateMachine.ChangeState("DraggingRevealedState");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (cardBack != null) cardBack.SetActive(false);
|
||||||
|
if (context.CardDisplay != null) context.CardDisplay.gameObject.SetActive(true);
|
||||||
|
context.StateMachine.ChangeState("DraggingRevealedState");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: edffabfce37d42ceac2194c23470acab
|
||||||
|
timeCreated: 1763322190
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using Core.SaveLoad;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UI.CardSystem.StateMachine.States
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Card is in pending face-down state in corner, awaiting drag.
|
||||||
|
/// Front hidden, back visible (assumes CardBack child exists).
|
||||||
|
/// </summary>
|
||||||
|
public class CardPendingFaceDownState : AppleState
|
||||||
|
{
|
||||||
|
private CardContext context;
|
||||||
|
private GameObject cardBack;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
context = GetComponentInParent<CardContext>();
|
||||||
|
if (context != null)
|
||||||
|
{
|
||||||
|
var backTransform = context.RootTransform.Find("CardBack");
|
||||||
|
if (backTransform != null) cardBack = backTransform.gameObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnEnterState()
|
||||||
|
{
|
||||||
|
if (context == null) return;
|
||||||
|
// Hide front
|
||||||
|
if (context.CardDisplay != null)
|
||||||
|
{
|
||||||
|
context.CardDisplay.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
// Show back
|
||||||
|
if (cardBack != null) cardBack.SetActive(true);
|
||||||
|
// Scale
|
||||||
|
context.RootTransform.localScale = context.OriginalScale * 0.8f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6fab9d595905435b82253cd4d1bf49de
|
||||||
|
timeCreated: 1763322180
|
||||||
460
docs/album_placement_flow_proposal.md
Normal file
460
docs/album_placement_flow_proposal.md
Normal file
@@ -0,0 +1,460 @@
|
|||||||
|
# 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! 🎉
|
||||||
|
|
||||||
Reference in New Issue
Block a user