Working card dragging into album slots, persistent slots

This commit is contained in:
Michal Pikulski
2025-11-07 11:24:19 +01:00
parent 0d8702a5f6
commit 77f7d1ee97
21 changed files with 5093 additions and 3257 deletions

View File

@@ -0,0 +1,247 @@
using System;
using System.Collections;
using AppleHills.Data.CardSystem;
using Data.CardSystem;
using Pixelplacement;
using UI.DragAndDrop.Core;
using UnityEngine;
namespace UI.CardSystem
{
/// <summary>
/// Draggable card for album reveal system.
/// Handles both tap and drag-hold interactions for revealing cards.
/// Auto-snaps to matching album slot on release/tap.
/// </summary>
public class AlbumCardPlacementDraggable : DraggableObject
{
[Header("Album Card Settings")]
[SerializeField] private FlippableCard flippableCard;
[SerializeField] private float holdRevealDelay = 0.1f;
private CardData _cardData;
private bool _isRevealed = false;
private bool _isDragRevealing = false;
private bool _waitingForPlacementTap = false;
private Coroutine _holdRevealCoroutine;
private bool _isHolding = false; // Track if pointer is currently down
// Events
public event Action<AlbumCardPlacementDraggable, CardData> OnCardRevealed;
public event Action<AlbumCardPlacementDraggable, CardData> OnCardPlacedInAlbum;
public CardData CardData => _cardData;
public bool IsRevealed => _isRevealed;
public CardZone Zone => _cardData?.Zone ?? CardZone.AppleHills;
protected override void Initialize()
{
base.Initialize();
// Auto-find FlippableCard if not assigned
if (flippableCard == null)
{
flippableCard = GetComponent<FlippableCard>();
}
}
/// <summary>
/// Setup the card data (stores it but doesn't reveal until tapped/dragged)
/// </summary>
public void SetupCard(CardData data)
{
_cardData = data;
if (flippableCard != null)
{
flippableCard.SetupCard(data);
}
}
/// <summary>
/// Reveal the card (flip to show front)
/// </summary>
public void RevealCard()
{
if (_isRevealed) return;
_isRevealed = true;
if (flippableCard != null)
{
flippableCard.FlipToReveal();
}
OnCardRevealed?.Invoke(this, _cardData);
}
/// <summary>
/// Snap to the matching album slot
/// </summary>
public void SnapToAlbumSlot()
{
if (_cardData == null)
{
Debug.LogWarning("[AlbumCardPlacementDraggable] Cannot snap to slot - no card data assigned.");
return;
}
// Find all album card slots in the scene
AlbumCardSlot[] allSlots = FindObjectsByType<AlbumCardSlot>(FindObjectsSortMode.None);
AlbumCardSlot matchingSlot = null;
foreach (var slot in allSlots)
{
if (slot.CanAcceptCard(_cardData))
{
matchingSlot = slot;
break;
}
}
if (matchingSlot != null)
{
SetDraggingEnabled(false);
// NEW FLOW: Extract AlbumCard FIRST, then tween it
if (flippableCard != null)
{
AlbumCard extractedCard = flippableCard.ExtractAlbumCard(matchingSlot.transform);
if (extractedCard != null)
{
// Notify slot that card was placed
matchingSlot.OnCardPlaced(extractedCard);
// NOW tween the extracted AlbumCard into position
TweenExtractedCardToSlot(extractedCard, () =>
{
// After animation completes
Debug.Log($"[AlbumCardPlacementDraggable] Card placement animation complete for {_cardData.Name}");
// Notify that card was placed
OnCardPlacedInAlbum?.Invoke(this, _cardData);
// Destroy this wrapper (the AlbumPlacementCard)
Destroy(gameObject);
});
}
else
{
Debug.LogWarning("[AlbumCardPlacementDraggable] Failed to extract AlbumCard from wrapper!");
}
}
}
else
{
Debug.LogWarning($"[AlbumCardPlacementDraggable] Could not find matching slot for card '{_cardData.Name}' (Zone: {_cardData.Zone}, Index: {_cardData.CollectionIndex})");
}
}
/// <summary>
/// Tween the extracted AlbumCard into its slot position
/// Tweens from current size to slot size - AspectRatioFitter handles width
/// </summary>
private void TweenExtractedCardToSlot(AlbumCard card, System.Action onComplete)
{
Transform cardTransform = card.transform;
RectTransform cardRect = cardTransform as RectTransform;
if (cardRect != null)
{
// Get target height from slot
RectTransform slotRect = cardTransform.parent as RectTransform;
float targetHeight = slotRect != null ? slotRect.rect.height : cardRect.sizeDelta.y;
// Tween from current size to target size (AspectRatioFitter will adjust width)
Vector2 targetSize = new Vector2(cardRect.sizeDelta.x, targetHeight);
Tween.Size(cardRect, targetSize, snapDuration, 0f, Tween.EaseOutBack);
// Tween position and rotation to slot center
Tween.LocalPosition(cardRect, Vector3.zero, snapDuration, 0f, Tween.EaseOutBack);
Tween.LocalRotation(cardTransform, Quaternion.identity, snapDuration, 0f, Tween.EaseOutBack,
completeCallback: () =>
{
Debug.Log($"[AlbumCardPlacementDraggable] Tween complete for extracted card {card.name}, final height: {cardRect.sizeDelta.y}");
onComplete?.Invoke();
});
}
else
{
// No RectTransform, just reset and call callback
cardTransform.localPosition = Vector3.zero;
cardTransform.localRotation = Quaternion.identity;
onComplete?.Invoke();
}
}
protected override void OnPointerDownHook()
{
base.OnPointerDownHook();
_isHolding = true;
// Start hold-reveal timer if card not yet revealed
if (!_isRevealed && _holdRevealCoroutine == null)
{
_holdRevealCoroutine = StartCoroutine(HoldRevealTimer());
}
}
protected override void OnPointerUpHook(bool longPress)
{
base.OnPointerUpHook(longPress);
_isHolding = false;
// Cancel hold timer if running
if (_holdRevealCoroutine != null)
{
StopCoroutine(_holdRevealCoroutine);
_holdRevealCoroutine = null;
}
// Handle tap (not dragged)
if (!_wasDragged)
{
if (!_isRevealed)
{
// First tap: reveal the card
RevealCard();
_waitingForPlacementTap = true;
}
else if (_waitingForPlacementTap)
{
// Second tap: snap to slot
_waitingForPlacementTap = false;
SnapToAlbumSlot();
}
}
else if (_isDragRevealing)
{
// Was drag-revealed, auto-snap on release
_isDragRevealing = false;
SnapToAlbumSlot();
}
}
/// <summary>
/// Coroutine to reveal card after holding for specified duration
/// </summary>
private IEnumerator HoldRevealTimer()
{
yield return new WaitForSeconds(holdRevealDelay);
// If still holding after delay, reveal the card
if (!_isRevealed && _isHolding)
{
RevealCard();
_isDragRevealing = true;
Debug.Log("[AlbumCardDraggable] Card revealed via hold");
}
_holdRevealCoroutine = null;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 706803638ea24880bae19c87d3851ce6
timeCreated: 1762470947

View File

@@ -0,0 +1,163 @@
using AppleHills.Data.CardSystem;
using Data.CardSystem;
using UI.DragAndDrop.Core;
using UnityEngine;
namespace UI.CardSystem
{
/// <summary>
/// Specialized slot for album pages that only accepts a specific card.
/// Validates cards based on their CardDefinition.
/// Self-populates with owned cards when enabled.
/// </summary>
public class AlbumCardSlot : DraggableSlot
{
[Header("Album Slot Configuration")]
[SerializeField] private CardDefinition targetCardDefinition; // Which card this slot accepts
[SerializeField] private GameObject albumCardPrefab; // Prefab to spawn when card is owned
private bool _isOccupiedPermanently = false;
private AlbumCard _placedCard;
/// <summary>
/// Set the target card this slot should accept
/// </summary>
public void SetTargetCard(CardDefinition definition)
{
targetCardDefinition = definition;
}
/// <summary>
/// Check if this slot can accept a specific card
/// </summary>
public bool CanAcceptCard(CardData cardData)
{
if (cardData == null || targetCardDefinition == null) return false;
if (_isOccupiedPermanently) return false;
// Card must match this slot's target definition
return cardData.DefinitionId == targetCardDefinition.Id;
}
/// <summary>
/// Called when a card is successfully placed in this slot
/// </summary>
public void OnCardPlaced(AlbumCard albumCard = null)
{
_isOccupiedPermanently = true;
if (albumCard != null)
{
_placedCard = albumCard;
albumCard.SetParentSlot(this);
// Register with AlbumViewPage for enlarge/shrink handling
AlbumViewPage albumPage = FindObjectOfType<AlbumViewPage>();
if (albumPage != null)
{
albumPage.RegisterAlbumCard(albumCard);
}
}
}
/// <summary>
/// Check if this slot has a placed card
/// </summary>
public bool HasPlacedCard()
{
return _placedCard != null;
}
/// <summary>
/// Get the placed card (if any)
/// </summary>
public AlbumCard GetPlacedCard()
{
return _placedCard;
}
private void OnEnable()
{
// Check if we should spawn a card for this slot
CheckAndSpawnOwnedCard();
}
/// <summary>
/// Check if player owns the card for this slot and spawn it if so
/// </summary>
private void CheckAndSpawnOwnedCard()
{
// Guard: need CardSystemManager and target definition
if (CardSystemManager.Instance == null || targetCardDefinition == null)
return;
// Guard: don't spawn if already occupied
if (_isOccupiedPermanently || _placedCard != null)
return;
// Guard: need prefab to spawn
if (albumCardPrefab == null)
{
Debug.LogWarning($"[AlbumCardSlot] No albumCardPrefab assigned for slot targeting {targetCardDefinition.name}");
return;
}
// Check if player owns this card at ANY rarity (prioritize highest rarity)
CardData ownedCard = null;
// Check in order: Legendary > Rare > Normal
foreach (CardRarity rarity in new[] { CardRarity.Legendary, CardRarity.Rare, CardRarity.Normal })
{
CardData card = CardSystemManager.Instance.GetCardInventory().GetCard(targetCardDefinition.Id, rarity);
if (card != null)
{
ownedCard = card;
break; // Found highest rarity owned
}
}
// Spawn card if owned
if (ownedCard != null)
{
SpawnAlbumCard(ownedCard);
}
}
/// <summary>
/// Spawn an AlbumCard in this slot
/// </summary>
private void SpawnAlbumCard(CardData cardData)
{
GameObject cardObj = Instantiate(albumCardPrefab, transform);
AlbumCard albumCard = cardObj.GetComponent<AlbumCard>();
if (albumCard != null)
{
albumCard.SetupCard(cardData);
albumCard.SetParentSlot(this);
_placedCard = albumCard;
_isOccupiedPermanently = true;
// Register with AlbumViewPage for enlarge/shrink handling
AlbumViewPage albumPage = FindObjectOfType<AlbumViewPage>();
if (albumPage != null)
{
albumPage.RegisterAlbumCard(albumCard);
}
Debug.Log($"[AlbumCardSlot] Spawned owned card '{cardData.Name}' ({cardData.Rarity}) in slot");
}
else
{
Debug.LogWarning($"[AlbumCardSlot] Spawned prefab has no AlbumCard component!");
Destroy(cardObj);
}
}
/// <summary>
/// Get the target card definition for this slot
/// </summary>
public CardDefinition TargetCardDefinition => targetCardDefinition;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 514a349ba18d4842bc4292cb034f0d76
timeCreated: 1762470924