226 lines
7.9 KiB
C#
226 lines
7.9 KiB
C#
|
|
using System;
|
|||
|
|
using AppleHills.Data.CardSystem;
|
|||
|
|
using Pixelplacement;
|
|||
|
|
using Pixelplacement.TweenSystem;
|
|||
|
|
using UnityEngine;
|
|||
|
|
using UnityEngine.EventSystems;
|
|||
|
|
using UnityEngine.UI;
|
|||
|
|
|
|||
|
|
namespace UI.CardSystem
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// Flippable card wrapper that shows a card back, then flips to reveal the CardDisplay front.
|
|||
|
|
/// This component nests an existing CardDisplay prefab to reuse card visuals everywhere.
|
|||
|
|
/// </summary>
|
|||
|
|
public class FlippableCard : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler
|
|||
|
|
{
|
|||
|
|
[Header("Card References")]
|
|||
|
|
[SerializeField] private GameObject cardBackObject; // The card back visual
|
|||
|
|
[SerializeField] private GameObject cardFrontObject; // Your CardDisplay prefab instance
|
|||
|
|
[SerializeField] private CardDisplay cardDisplay; // Reference to CardDisplay component
|
|||
|
|
|
|||
|
|
[Header("Idle Hover Animation")]
|
|||
|
|
[SerializeField] private bool enableIdleHover = true;
|
|||
|
|
[SerializeField] private float idleHoverHeight = 10f;
|
|||
|
|
[SerializeField] private float idleHoverDuration = 1.5f;
|
|||
|
|
[SerializeField] private float hoverScaleMultiplier = 1.05f;
|
|||
|
|
|
|||
|
|
[Header("Flip Animation")]
|
|||
|
|
[SerializeField] private float flipDuration = 0.6f;
|
|||
|
|
[SerializeField] private float flipScalePunch = 1.1f;
|
|||
|
|
|
|||
|
|
// State
|
|||
|
|
private bool _isFlipped = false;
|
|||
|
|
private bool _isFlipping = false;
|
|||
|
|
private bool _isHovering = false;
|
|||
|
|
private TweenBase _idleHoverTween;
|
|||
|
|
private CardData _cardData;
|
|||
|
|
private Vector2 _originalPosition; // Track original spawn position
|
|||
|
|
|
|||
|
|
// Events
|
|||
|
|
public event Action<FlippableCard, CardData> OnCardRevealed;
|
|||
|
|
|
|||
|
|
public bool IsFlipped => _isFlipped;
|
|||
|
|
public CardData CardData => _cardData;
|
|||
|
|
|
|||
|
|
private void Awake()
|
|||
|
|
{
|
|||
|
|
// Auto-find CardDisplay if not assigned
|
|||
|
|
if (cardDisplay == null && cardFrontObject != null)
|
|||
|
|
{
|
|||
|
|
cardDisplay = cardFrontObject.GetComponent<CardDisplay>();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Start with back showing, front hidden
|
|||
|
|
if (cardBackObject != null)
|
|||
|
|
cardBackObject.SetActive(true);
|
|||
|
|
if (cardFrontObject != null)
|
|||
|
|
cardFrontObject.SetActive(false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void Start()
|
|||
|
|
{
|
|||
|
|
// Save the original position so we can return to it after hover
|
|||
|
|
RectTransform rectTransform = GetComponent<RectTransform>();
|
|||
|
|
if (rectTransform != null)
|
|||
|
|
{
|
|||
|
|
_originalPosition = rectTransform.anchoredPosition;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Start idle hover animation
|
|||
|
|
if (enableIdleHover && !_isFlipped)
|
|||
|
|
{
|
|||
|
|
StartIdleHover();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Setup the card data (stores it but doesn't reveal until flipped)
|
|||
|
|
/// </summary>
|
|||
|
|
public void SetupCard(CardData data)
|
|||
|
|
{
|
|||
|
|
_cardData = data;
|
|||
|
|
|
|||
|
|
// Setup the CardDisplay but keep it hidden
|
|||
|
|
if (cardDisplay != null)
|
|||
|
|
{
|
|||
|
|
cardDisplay.SetupCard(data);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Flip the card to reveal the front
|
|||
|
|
/// </summary>
|
|||
|
|
public void FlipToReveal()
|
|||
|
|
{
|
|||
|
|
if (_isFlipped || _isFlipping)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
_isFlipping = true;
|
|||
|
|
|
|||
|
|
// Stop idle hover
|
|||
|
|
StopIdleHover();
|
|||
|
|
|
|||
|
|
// Flip animation: rotate Y 0 -> 90 (hide back) -> 180 (show front)
|
|||
|
|
Transform cardTransform = transform;
|
|||
|
|
|
|||
|
|
// Phase 1: Flip to 90 degrees (edge view, hide back)
|
|||
|
|
Tween.LocalRotation(cardTransform, Quaternion.Euler(0, 90, 0), flipDuration * 0.5f, 0f, Tween.EaseInOut,
|
|||
|
|
completeCallback: () =>
|
|||
|
|
{
|
|||
|
|
// Switch visuals at the edge
|
|||
|
|
if (cardBackObject != null)
|
|||
|
|
cardBackObject.SetActive(false);
|
|||
|
|
if (cardFrontObject != null)
|
|||
|
|
cardFrontObject.SetActive(true);
|
|||
|
|
|
|||
|
|
// Phase 2: Flip from 90 to 180 (show front)
|
|||
|
|
Tween.LocalRotation(cardTransform, Quaternion.Euler(0, 180, 0), flipDuration * 0.5f, 0f, Tween.EaseInOut,
|
|||
|
|
completeCallback: () =>
|
|||
|
|
{
|
|||
|
|
_isFlipped = true;
|
|||
|
|
_isFlipping = false;
|
|||
|
|
|
|||
|
|
// Fire revealed event
|
|||
|
|
OnCardRevealed?.Invoke(this, _cardData);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Scale punch during flip for extra juice
|
|||
|
|
Vector3 originalScale = cardTransform.localScale;
|
|||
|
|
Tween.LocalScale(cardTransform, originalScale * flipScalePunch, flipDuration * 0.5f, 0f, Tween.EaseOutBack,
|
|||
|
|
completeCallback: () =>
|
|||
|
|
{
|
|||
|
|
Tween.LocalScale(cardTransform, originalScale, flipDuration * 0.5f, 0f, Tween.EaseInBack);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Start idle hover animation (gentle bobbing)
|
|||
|
|
/// </summary>
|
|||
|
|
private void StartIdleHover()
|
|||
|
|
{
|
|||
|
|
if (_idleHoverTween != null)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
RectTransform rectTransform = GetComponent<RectTransform>();
|
|||
|
|
if (rectTransform == null)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
Vector2 originalPos = rectTransform.anchoredPosition;
|
|||
|
|
Vector2 targetPos = originalPos + Vector2.up * idleHoverHeight;
|
|||
|
|
|
|||
|
|
_idleHoverTween = Tween.Value(0f, 1f,
|
|||
|
|
(val) =>
|
|||
|
|
{
|
|||
|
|
if (rectTransform != null)
|
|||
|
|
{
|
|||
|
|
float t = Mathf.Sin(val * Mathf.PI * 2f) * 0.5f + 0.5f; // Smooth sine wave
|
|||
|
|
rectTransform.anchoredPosition = Vector2.Lerp(originalPos, targetPos, t);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
idleHoverDuration, 0f, Tween.EaseInOut, Tween.LoopType.Loop);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Stop idle hover animation
|
|||
|
|
/// </summary>
|
|||
|
|
private void StopIdleHover()
|
|||
|
|
{
|
|||
|
|
if (_idleHoverTween != null)
|
|||
|
|
{
|
|||
|
|
_idleHoverTween.Stop();
|
|||
|
|
_idleHoverTween = null;
|
|||
|
|
|
|||
|
|
// Reset to ORIGINAL position (not Vector2.zero!)
|
|||
|
|
RectTransform rectTransform = GetComponent<RectTransform>();
|
|||
|
|
if (rectTransform != null)
|
|||
|
|
{
|
|||
|
|
Tween.AnchoredPosition(rectTransform, _originalPosition, 0.3f, 0f, Tween.EaseOutBack);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#region Pointer Event Handlers
|
|||
|
|
|
|||
|
|
public void OnPointerEnter(PointerEventData eventData)
|
|||
|
|
{
|
|||
|
|
if (_isFlipped || _isFlipping)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
_isHovering = true;
|
|||
|
|
|
|||
|
|
// Scale up slightly on hover
|
|||
|
|
Tween.LocalScale(transform, Vector3.one * hoverScaleMultiplier, 0.2f, 0f, Tween.EaseOutBack);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void OnPointerExit(PointerEventData eventData)
|
|||
|
|
{
|
|||
|
|
if (_isFlipped || _isFlipping)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
_isHovering = false;
|
|||
|
|
|
|||
|
|
// Scale back to normal
|
|||
|
|
Tween.LocalScale(transform, Vector3.one, 0.2f, 0f, Tween.EaseOutBack);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void OnPointerClick(PointerEventData eventData)
|
|||
|
|
{
|
|||
|
|
if (_isFlipped || _isFlipping)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
// Flip on click
|
|||
|
|
FlipToReveal();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
private void OnDestroy()
|
|||
|
|
{
|
|||
|
|
StopIdleHover();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|