Working card dragging into album slots, persistent slots
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 706803638ea24880bae19c87d3851ce6
|
||||
timeCreated: 1762470947
|
||||
163
Assets/Scripts/UI/CardSystem/DragDrop/AlbumCardSlot.cs
Normal file
163
Assets/Scripts/UI/CardSystem/DragDrop/AlbumCardSlot.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 514a349ba18d4842bc4292cb034f0d76
|
||||
timeCreated: 1762470924
|
||||
Reference in New Issue
Block a user