From 1fdff3450b7ddba48f5ff8df3cf3abfae21aec1c Mon Sep 17 00:00:00 2001 From: Michal Pikulski Date: Tue, 11 Nov 2025 20:25:23 +0100 Subject: [PATCH] Update the card kerfufle --- .../Scripts/UI/CardSystem/StateMachine.meta | 3 + .../UI/CardSystem/StateMachine/Card.cs | 122 ++++++ .../UI/CardSystem/StateMachine/Card.cs.meta | 3 + .../StateMachine/CardAnimationConfig.cs | 1 + .../StateMachine/CardAnimationConfig.cs.meta | 3 + .../CardSystem/StateMachine/CardAnimator.cs | 359 ++++++++++++++++++ .../StateMachine/CardAnimator.cs.meta | 3 + .../UI/CardSystem/StateMachine/CardContext.cs | 66 ++++ .../StateMachine/CardContext.cs.meta | 3 + .../UI/CardSystem/StateMachine/States.meta | 3 + .../States/CardAlbumEnlargedState.cs | 93 +++++ .../States/CardAlbumEnlargedState.cs.meta | 3 + .../StateMachine/States/CardDraggingState.cs | 73 ++++ .../States/CardDraggingState.cs.meta | 3 + .../States/CardEnlargedNewState.cs | 64 ++++ .../States/CardEnlargedNewState.cs.meta | 3 + .../States/CardEnlargedRepeatState.cs | 86 +++++ .../States/CardEnlargedRepeatState.cs.meta | 3 + .../StateMachine/States/CardFlippingState.cs | 81 ++++ .../States/CardFlippingState.cs.meta | 3 + .../StateMachine/States/CardIdleState.cs | 118 ++++++ .../StateMachine/States/CardIdleState.cs.meta | 3 + .../States/CardInteractionHandler.cs | 65 ++++ .../States/CardInteractionHandler.cs.meta | 3 + .../States/CardPlacedInSlotState.cs | 53 +++ .../States/CardPlacedInSlotState.cs.meta | 3 + .../StateMachine/States/CardRevealedState.cs | 33 ++ .../States/CardRevealedState.cs.meta | 3 + 28 files changed, 1259 insertions(+) create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine.meta create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/Card.cs create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/Card.cs.meta create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/CardAnimationConfig.cs create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/CardAnimationConfig.cs.meta create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/CardAnimator.cs create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/CardAnimator.cs.meta create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/CardContext.cs create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/CardContext.cs.meta create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/States.meta create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/States/CardAlbumEnlargedState.cs create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/States/CardAlbumEnlargedState.cs.meta create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/States/CardDraggingState.cs create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/States/CardDraggingState.cs.meta create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/States/CardEnlargedNewState.cs create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/States/CardEnlargedNewState.cs.meta create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/States/CardEnlargedRepeatState.cs create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/States/CardEnlargedRepeatState.cs.meta create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/States/CardFlippingState.cs create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/States/CardFlippingState.cs.meta create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/States/CardIdleState.cs create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/States/CardIdleState.cs.meta create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/States/CardInteractionHandler.cs create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/States/CardInteractionHandler.cs.meta create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/States/CardPlacedInSlotState.cs create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/States/CardPlacedInSlotState.cs.meta create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/States/CardRevealedState.cs create mode 100644 Assets/Scripts/UI/CardSystem/StateMachine/States/CardRevealedState.cs.meta diff --git a/Assets/Scripts/UI/CardSystem/StateMachine.meta b/Assets/Scripts/UI/CardSystem/StateMachine.meta new file mode 100644 index 00000000..3cfad9ee --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 80f8dd01edcd4742b3edbb5c7fcecd12 +timeCreated: 1762884650 \ No newline at end of file diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/Card.cs b/Assets/Scripts/UI/CardSystem/StateMachine/Card.cs new file mode 100644 index 00000000..6be11ad5 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/Card.cs @@ -0,0 +1,122 @@ +using AppleHills.Data.CardSystem; +using Core.SaveLoad; +using UnityEngine; + +namespace UI.CardSystem.StateMachine +{ + /// + /// Main Card controller component. + /// Orchestrates the card state machine, context, and animator. + /// This is the single entry point for working with cards. + /// + public class Card : MonoBehaviour + { + [Header("Components")] + [SerializeField] private CardContext context; + [SerializeField] private CardAnimator animator; + [SerializeField] private AppleMachine stateMachine; + + [Header("Configuration")] + [SerializeField] private string initialState = "IdleState"; + + // Public accessors + public CardContext Context => context; + public CardAnimator Animator => animator; + public AppleMachine StateMachine => stateMachine; + public CardData CardData => context?.CardData; + + private void Awake() + { + // Auto-find components if not assigned + if (context == null) + context = GetComponent(); + + if (animator == null) + animator = GetComponent(); + + if (stateMachine == null) + stateMachine = GetComponentInChildren(); + } + + /// + /// Setup the card with data and optional initial state + /// + public void SetupCard(CardData data, bool isNew = false, string startState = null) + { + if (context != null) + { + context.SetupCard(data, isNew); + } + + // Start the state machine with specified or default state + string targetState = startState ?? initialState; + if (stateMachine != null && !string.IsNullOrEmpty(targetState)) + { + stateMachine.ChangeState(targetState); + } + } + + /// + /// Setup for booster reveal flow (starts at IdleState, will flip on click) + /// + public void SetupForBoosterReveal(CardData data, bool isNew) + { + SetupCard(data, isNew, "IdleState"); + } + + /// + /// Setup for album placement (starts at PlacedInSlotState) + /// + public void SetupForAlbumSlot(CardData data, AlbumCardSlot slot) + { + SetupCard(data, false, "PlacedInSlotState"); + + // Set the parent slot on the PlacedInSlotState + var placedState = GetStateComponent("PlacedInSlotState"); + if (placedState != null) + { + placedState.SetParentSlot(slot); + } + } + + /// + /// Transition to a specific state + /// + public void ChangeState(string stateName) + { + if (stateMachine != null) + { + stateMachine.ChangeState(stateName); + } + } + + /// + /// Get a specific state component by name + /// + public T GetStateComponent(string stateName) where T : AppleState + { + if (stateMachine == null) return null; + + Transform stateTransform = stateMachine.transform.Find(stateName); + if (stateTransform != null) + { + return stateTransform.GetComponent(); + } + + return null; + } + + /// + /// Get the current active state name + /// + public string GetCurrentStateName() + { + if (stateMachine?.currentState != null) + { + return stateMachine.currentState.name; + } + return "None"; + } + } +} + diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/Card.cs.meta b/Assets/Scripts/UI/CardSystem/StateMachine/Card.cs.meta new file mode 100644 index 00000000..b7567245 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/Card.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d97dd4e4bc3246e9bed5ac227f13de10 +timeCreated: 1762884900 \ No newline at end of file diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/CardAnimationConfig.cs b/Assets/Scripts/UI/CardSystem/StateMachine/CardAnimationConfig.cs new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/CardAnimationConfig.cs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/CardAnimationConfig.cs.meta b/Assets/Scripts/UI/CardSystem/StateMachine/CardAnimationConfig.cs.meta new file mode 100644 index 00000000..f8de6a33 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/CardAnimationConfig.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ddd745ae12984de9974292d201c89d9c +timeCreated: 1762884650 \ No newline at end of file diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/CardAnimator.cs b/Assets/Scripts/UI/CardSystem/StateMachine/CardAnimator.cs new file mode 100644 index 00000000..db66b21b --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/CardAnimator.cs @@ -0,0 +1,359 @@ +using Pixelplacement; +using Pixelplacement.TweenSystem; +using UnityEngine; +using System; + +namespace UI.CardSystem.StateMachine +{ + /// + /// Handles common card animations that can be reused across states. + /// Centralizes animation logic to avoid duplication. + /// Animates the CARD ROOT TRANSFORM (all states follow the card). + /// + public class CardAnimator : MonoBehaviour + { + [Header("Animation Settings")] + [SerializeField] private float defaultDuration = 0.3f; + + private Transform _transform; + private RectTransform _rectTransform; + + private void Awake() + { + _transform = transform; + _rectTransform = GetComponent(); + } + + #region Scale Animations + + /// + /// Animate scale to target value + /// + public TweenBase AnimateScale(Vector3 targetScale, float? duration = null, Action onComplete = null) + { + return Tween.LocalScale(_transform, targetScale, duration ?? defaultDuration, 0f, + Tween.EaseInOut, completeCallback: onComplete); + } + + /// + /// Pulse scale animation (scale up then back to normal) + /// + public void PulseScale(float pulseAmount = 1.1f, float duration = 0.2f, Action onComplete = null) + { + Vector3 originalScale = _transform.localScale; + Vector3 pulseScale = originalScale * pulseAmount; + + Tween.LocalScale(_transform, pulseScale, duration, 0f, Tween.EaseOutBack, + completeCallback: () => + { + Tween.LocalScale(_transform, originalScale, duration, 0f, Tween.EaseInBack, + completeCallback: onComplete); + }); + } + + /// + /// Pop-in animation (scale from 0 to 1 with overshoot) + /// + public TweenBase PopIn(float duration = 0.5f, Action onComplete = null) + { + _transform.localScale = Vector3.zero; + return Tween.LocalScale(_transform, Vector3.one, duration, 0f, + Tween.EaseOutBack, completeCallback: onComplete); + } + + /// + /// Pop-out animation (scale from current to 0) + /// + public TweenBase PopOut(float duration = 0.3f, Action onComplete = null) + { + return Tween.LocalScale(_transform, Vector3.zero, duration, 0f, + Tween.EaseInBack, completeCallback: onComplete); + } + + #endregion + + #region Position Animations (RectTransform) + + /// + /// Animate anchored position (for UI elements) + /// + public TweenBase AnimateAnchoredPosition(Vector2 targetPosition, float? duration = null, Action onComplete = null) + { + if (_rectTransform == null) + { + Debug.LogWarning("CardAnimator: No RectTransform found for anchored position animation"); + return null; + } + + return Tween.AnchoredPosition(_rectTransform, targetPosition, duration ?? defaultDuration, 0f, + Tween.EaseInOut, completeCallback: onComplete); + } + + /// + /// Animate local position + /// + public TweenBase AnimateLocalPosition(Vector3 targetPosition, float? duration = null, Action onComplete = null) + { + return Tween.LocalPosition(_transform, targetPosition, duration ?? defaultDuration, 0f, + Tween.EaseInOut, completeCallback: onComplete); + } + + #endregion + + #region Rotation Animations + + /// + /// Animate local rotation to target + /// + public TweenBase AnimateLocalRotation(Quaternion targetRotation, float? duration = null, Action onComplete = null) + { + return Tween.LocalRotation(_transform, targetRotation, duration ?? defaultDuration, 0f, + Tween.EaseInOut, completeCallback: onComplete); + } + + /// + /// Rotate a child object (typically used by states for CardBackVisual, etc.) + /// + public TweenBase AnimateChildRotation(Transform childTransform, Quaternion targetRotation, + float duration, Action onComplete = null) + { + return Tween.LocalRotation(childTransform, targetRotation, duration, 0f, + Tween.EaseInOut, completeCallback: onComplete); + } + + #endregion + + #region Flip Animations + + /// + /// Play card flip animation - rotates card back from 0° to 90°, then card front from 180° to 0° + /// Based on FlippableCard.FlipToReveal() + /// + public void PlayFlip(Transform cardBack, Transform cardFront, float duration = 0.6f, Action onComplete = null) + { + // Phase 1: Rotate both to 90 degrees (edge view) + if (cardBack != null) + { + Tween.LocalRotation(cardBack, Quaternion.Euler(0, 90, 0), duration * 0.5f, 0f, Tween.EaseInOut); + } + + if (cardFront != null) + { + Tween.LocalRotation(cardFront, Quaternion.Euler(0, 90, 0), duration * 0.5f, 0f, Tween.EaseInOut, + completeCallback: () => + { + // At edge (90°), switch visibility + if (cardBack != null) + cardBack.gameObject.SetActive(false); + if (cardFront != null) + cardFront.gameObject.SetActive(true); + + // Phase 2: Rotate front from 90 to 0 (show at correct orientation) + Tween.LocalRotation(cardFront, Quaternion.Euler(0, 0, 0), duration * 0.5f, 0f, Tween.EaseInOut, + completeCallback: onComplete); + }); + } + } + + /// + /// Play scale punch during flip animation for extra juice + /// Based on FlippableCard.FlipToReveal() + /// + public void PlayFlipScalePunch(float punchScale = 1.1f, float duration = 0.6f) + { + Vector3 originalScale = _transform.localScale; + + Tween.LocalScale(_transform, originalScale * punchScale, duration * 0.5f, 0f, Tween.EaseOutBack, + completeCallback: () => + { + Tween.LocalScale(_transform, originalScale, duration * 0.5f, 0f, Tween.EaseInBack); + }); + } + + #endregion + + #region Enlarge/Shrink Animations + + /// + /// Enlarge card to specified scale + /// Based on FlippableCard.EnlargeCard() and AlbumCard.EnlargeCard() + /// + public void PlayEnlarge(float targetScale = 2.5f, float duration = 0.3f, Action onComplete = null) + { + Tween.LocalScale(_transform, Vector3.one * targetScale, duration, 0f, Tween.EaseOutBack, + completeCallback: onComplete); + } + + /// + /// Shrink card back to original scale + /// Based on AlbumCard.ShrinkCard() and FlippableCard.ReturnToNormalSize() + /// + public void PlayShrink(Vector3 targetScale, float duration = 0.3f, Action onComplete = null) + { + Tween.LocalScale(_transform, targetScale, duration, 0f, Tween.EaseInBack, + completeCallback: onComplete); + } + + #endregion + + #region Combined Animations + + /// + /// Hover enter animation (lift and scale) + /// For RectTransform UI elements + /// + public void HoverEnter(float liftAmount = 20f, float scaleMultiplier = 1.05f, + float duration = 0.2f, Action onComplete = null) + { + if (_rectTransform != null) + { + Vector2 currentPos = _rectTransform.anchoredPosition; + Vector2 targetPos = currentPos + Vector2.up * liftAmount; + + Tween.AnchoredPosition(_rectTransform, targetPos, duration, 0f, Tween.EaseOutBack); + Tween.LocalScale(_transform, Vector3.one * scaleMultiplier, duration, 0f, + Tween.EaseOutBack, completeCallback: onComplete); + } + else + { + // Fallback for non-RectTransform + Vector3 currentPos = _transform.localPosition; + Vector3 targetPos = currentPos + Vector3.up * liftAmount; + + Tween.LocalPosition(_transform, targetPos, duration, 0f, Tween.EaseOutBack); + Tween.LocalScale(_transform, Vector3.one * scaleMultiplier, duration, 0f, + Tween.EaseOutBack, completeCallback: onComplete); + } + } + + /// + /// Hover exit animation (return to original position and scale) + /// + public void HoverExit(Vector2 originalPosition, float duration = 0.2f, Action onComplete = null) + { + if (_rectTransform != null) + { + Tween.AnchoredPosition(_rectTransform, originalPosition, duration, 0f, Tween.EaseInBack); + Tween.LocalScale(_transform, Vector3.one, duration, 0f, + Tween.EaseInBack, completeCallback: onComplete); + } + else + { + Tween.LocalPosition(_transform, originalPosition, duration, 0f, Tween.EaseInBack); + Tween.LocalScale(_transform, Vector3.one, duration, 0f, + Tween.EaseInBack, completeCallback: onComplete); + } + } + + /// + /// Idle hover animation (gentle bobbing loop) + /// Returns the TweenBase so caller can stop it later + /// + public TweenBase StartIdleHover(float hoverHeight = 10f, float duration = 1.5f) + { + if (_rectTransform != null) + { + Vector2 originalPos = _rectTransform.anchoredPosition; + Vector2 targetPos = originalPos + Vector2.up * hoverHeight; + + return Tween.Value(0f, 1f, + (val) => + { + if (_rectTransform != null) + { + float t = Mathf.Sin(val * Mathf.PI * 2f) * 0.5f + 0.5f; + _rectTransform.anchoredPosition = Vector2.Lerp(originalPos, targetPos, t); + } + }, + duration, 0f, Tween.EaseInOut, Tween.LoopType.Loop); + } + + return null; + } + + #endregion + + #region Flip Animations (Two-Phase) + + /// + /// Flip animation: Phase 1 - Rotate card back to edge (0° to 90°) + /// Used by FlippingState to hide the back + /// + public void FlipPhase1_HideBack(Transform cardBackTransform, float duration, Action onHalfwayComplete) + { + Tween.LocalRotation(cardBackTransform, Quaternion.Euler(0, 90, 0), duration, 0f, + Tween.EaseInOut, completeCallback: onHalfwayComplete); + } + + /// + /// Flip animation: Phase 2 - Rotate card front from back to face (180° to 90° to 0°) + /// Used by FlippingState to reveal the front + /// + public void FlipPhase2_RevealFront(Transform cardFrontTransform, float duration, Action onComplete) + { + // First rotate from 180 to 90 (edge) + Tween.LocalRotation(cardFrontTransform, Quaternion.Euler(0, 90, 0), duration, 0f, + Tween.EaseInOut, + completeCallback: () => + { + // Then rotate from 90 to 0 (face) + Tween.LocalRotation(cardFrontTransform, Quaternion.Euler(0, 0, 0), duration, 0f, + Tween.EaseInOut, completeCallback: onComplete); + }); + } + + /// + /// Scale punch during flip (makes flip more juicy) + /// + public void FlipScalePunch(float punchMultiplier = 1.1f, float totalDuration = 0.6f) + { + Vector3 originalScale = _transform.localScale; + Vector3 punchScale = originalScale * punchMultiplier; + + Tween.LocalScale(_transform, punchScale, totalDuration * 0.5f, 0f, Tween.EaseOutBack, + completeCallback: () => + { + Tween.LocalScale(_transform, originalScale, totalDuration * 0.5f, 0f, Tween.EaseInBack); + }); + } + + #endregion + + #region Utility + + /// + /// Stop all active tweens on this transform + /// + public void StopAllAnimations() + { + Tween.Stop(_transform.GetInstanceID()); + if (_rectTransform != null) + Tween.Stop(_rectTransform.GetInstanceID()); + } + + /// + /// Reset transform to default values + /// + public void ResetTransform() + { + StopAllAnimations(); + _transform.localPosition = Vector3.zero; + _transform.localRotation = Quaternion.identity; + _transform.localScale = Vector3.one; + + if (_rectTransform != null) + _rectTransform.anchoredPosition = Vector2.zero; + } + + /// + /// Get current anchored position (useful for saving before hover) + /// + public Vector2 GetAnchoredPosition() + { + return _rectTransform != null ? _rectTransform.anchoredPosition : Vector2.zero; + } + + #endregion + } +} + diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/CardAnimator.cs.meta b/Assets/Scripts/UI/CardSystem/StateMachine/CardAnimator.cs.meta new file mode 100644 index 00000000..ae75af75 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/CardAnimator.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5eacab725f4346d091696042b9cd2a82 +timeCreated: 1762887143 \ No newline at end of file diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/CardContext.cs b/Assets/Scripts/UI/CardSystem/StateMachine/CardContext.cs new file mode 100644 index 00000000..c4e2ccb1 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/CardContext.cs @@ -0,0 +1,66 @@ +using AppleHills.Data.CardSystem; +using Core.SaveLoad; +using UnityEngine; + +namespace UI.CardSystem.StateMachine +{ + /// + /// Shared context for card states. + /// Provides access to common components and data that states need. + /// + public class CardContext : MonoBehaviour + { + [Header("Core Components")] + [SerializeField] private CardDisplay cardDisplay; + [SerializeField] private CardAnimator cardAnimator; + private AppleMachine stateMachine; + + [Header("Card Data")] + private CardData cardData; + + // Public accessors + public CardDisplay CardDisplay => cardDisplay; + public CardAnimator Animator => cardAnimator; + public AppleMachine StateMachine => stateMachine; + public Transform RootTransform => transform; + public CardData CardData => cardData; + + // Runtime state + public bool IsNewCard { get; set; } + public int RepeatCardCount { get; set; } + + private void Awake() + { + // Auto-find components if not assigned + if (cardDisplay == null) + cardDisplay = GetComponentInChildren(); + + if (cardAnimator == null) + cardAnimator = GetComponent(); + + if (stateMachine == null) + stateMachine = GetComponentInChildren(); + } + + /// + /// Setup the card with data + /// + public void SetupCard(CardData data, bool isNew = false, int repeatCount = 0) + { + cardData = data; + IsNewCard = isNew; + RepeatCardCount = repeatCount; + + if (cardDisplay != null) + { + cardDisplay.SetupCard(data); + } + } + + /// + /// Get the card display component + /// + public CardDisplay GetCardDisplay() => cardDisplay; + } +} + diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/CardContext.cs.meta b/Assets/Scripts/UI/CardSystem/StateMachine/CardContext.cs.meta new file mode 100644 index 00000000..66595281 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/CardContext.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3b3126aaaa66448fa3d5bd772aaf5784 +timeCreated: 1762884650 \ No newline at end of file diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/States.meta b/Assets/Scripts/UI/CardSystem/StateMachine/States.meta new file mode 100644 index 00000000..c48a44fe --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/States.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 43f3b0a00e934598a6a58abad11930a4 +timeCreated: 1762884650 \ No newline at end of file diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/States/CardAlbumEnlargedState.cs b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardAlbumEnlargedState.cs new file mode 100644 index 00000000..b0919c69 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardAlbumEnlargedState.cs @@ -0,0 +1,93 @@ +using Core; +using Core.SaveLoad; +using UnityEngine; +using UnityEngine.EventSystems; +using UI.CardSystem.StateMachine; + +namespace UI.CardSystem.StateMachine.States +{ + /// + /// Album enlarged state - card is enlarged when clicked from album slot. + /// Different from EnlargedNewState as it doesn't show "NEW" badge. + /// + public class CardAlbumEnlargedState : AppleState, IPointerClickHandler + { + private CardContext _context; + private Vector3 _originalScale; + private Transform _originalParent; + private Vector3 _originalLocalPosition; + private Quaternion _originalLocalRotation; + + // Events for page to manage backdrop and reparenting + public event System.Action OnEnlargeRequested; + public event System.Action OnShrinkRequested; + + private void Awake() + { + _context = GetComponentInParent(); + } + + public override void OnEnterState() + { + // Store original transform for restoration + _originalScale = _context.RootTransform.localScale; + _originalParent = _context.RootTransform.parent; + _originalLocalPosition = _context.RootTransform.localPosition; + _originalLocalRotation = _context.RootTransform.localRotation; + + // Notify page to show backdrop and reparent card to top layer + OnEnlargeRequested?.Invoke(this); + + // Enlarge the card + if (_context.Animator != null) + { + _context.Animator.PlayEnlarge(); + } + + Logging.Debug($"[CardAlbumEnlargedState] Card enlarged from album: {_context.CardData?.Name}"); + } + + public void OnPointerClick(PointerEventData eventData) + { + // Click to shrink back + Logging.Debug($"[CardAlbumEnlargedState] Card clicked while enlarged, shrinking back"); + + // Notify page to prepare for shrink + OnShrinkRequested?.Invoke(this); + + // Shrink animation, then transition back + if (_context.Animator != null) + { + _context.Animator.PlayShrink(_originalScale, onComplete: () => + { + _context.StateMachine.ChangeState("PlacedInSlotState"); + }); + } + } + + /// + /// Get original parent for restoration + /// + public Transform GetOriginalParent() => _originalParent; + + /// + /// Get original local position for restoration + /// + public Vector3 GetOriginalLocalPosition() => _originalLocalPosition; + + /// + /// Get original local rotation for restoration + /// + public Quaternion GetOriginalLocalRotation() => _originalLocalRotation; + + private void OnDisable() + { + // Restore original scale when exiting + if (_context?.RootTransform != null) + { + _context.RootTransform.localScale = _originalScale; + } + } + } +} + diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/States/CardAlbumEnlargedState.cs.meta b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardAlbumEnlargedState.cs.meta new file mode 100644 index 00000000..83a4fc58 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardAlbumEnlargedState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5f33526d9bb8458d8dc5ba41a88561da +timeCreated: 1762884900 \ No newline at end of file diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/States/CardDraggingState.cs b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardDraggingState.cs new file mode 100644 index 00000000..c424038c --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardDraggingState.cs @@ -0,0 +1,73 @@ +using Core.SaveLoad; +using UnityEngine; +using UnityEngine.EventSystems; +using Core; + +namespace UI.CardSystem.StateMachine.States +{ + /// + /// Dragging state - handles card being dragged for album placement. + /// Integrates with existing drag/drop system. + /// + public class CardDraggingState : AppleState + { + [Header("Drag Settings")] + [SerializeField] private float dragScale = 1.1f; + + private CardContext _context; + private Vector3 _originalScale; + private Vector3 _dragStartPosition; + + private void Awake() + { + _context = GetComponentInParent(); + } + + public override void OnEnterState() + { + // Store original transform + _originalScale = _context.RootTransform.localScale; + _dragStartPosition = _context.RootTransform.position; + + // Scale up slightly during drag for visual feedback + _context.RootTransform.localScale = _originalScale * dragScale; + + Logging.Debug($"[CardDraggingState] Entered drag state for card: {_context.CardData?.Name}"); + } + + /// + /// Update drag position (called by external drag handler) + /// + public void UpdateDragPosition(Vector3 worldPosition) + { + _context.RootTransform.position = worldPosition; + } + + /// + /// Called when drag is released and card snaps to slot + /// + public void OnDroppedInSlot() + { + _context.StateMachine.ChangeState("PlacedInSlotState"); + } + + /// + /// Called when drag is released but not over valid slot + /// + public void OnDroppedOutsideSlot() + { + // Return to revealed state + _context.StateMachine.ChangeState("RevealedState"); + } + + private void OnDisable() + { + // Restore original scale when exiting drag + if (_context?.RootTransform != null) + { + _context.RootTransform.localScale = _originalScale; + } + } + } +} + diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/States/CardDraggingState.cs.meta b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardDraggingState.cs.meta new file mode 100644 index 00000000..f6e877d3 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardDraggingState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b17e4e1d7139446c9c4e0a813067331c +timeCreated: 1762884899 \ No newline at end of file diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/States/CardEnlargedNewState.cs b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardEnlargedNewState.cs new file mode 100644 index 00000000..62f8f61d --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardEnlargedNewState.cs @@ -0,0 +1,64 @@ +using Core.SaveLoad; +using UnityEngine; +using UnityEngine.EventSystems; + +namespace UI.CardSystem.StateMachine.States +{ + /// + /// Enlarged state for NEW cards - shows "NEW CARD" badge and waits for tap to dismiss. + /// Owns the NewCardBadge as a child GameObject. + /// + public class CardEnlargedNewState : AppleState, IPointerClickHandler + { + [Header("State-Owned Visuals")] + [SerializeField] private GameObject newCardBadge; + + private CardContext _context; + private Vector3 _originalScale; + + private void Awake() + { + _context = GetComponentInParent(); + } + + public override void OnEnterState() + { + // Store original scale + _originalScale = _context.RootTransform.localScale; + + // Show NEW badge + if (newCardBadge != null) + { + newCardBadge.SetActive(true); + } + + // Enlarge the card + if (_context.Animator != null) + { + _context.Animator.PlayEnlarge(); + } + } + + public void OnPointerClick(PointerEventData eventData) + { + // Tap to dismiss - shrink back and transition to revealed state + if (_context.Animator != null) + { + _context.Animator.PlayShrink(_originalScale, onComplete: () => + { + _context.StateMachine.ChangeState("RevealedState"); + }); + } + } + + private void OnDisable() + { + // Hide NEW badge when leaving state + if (newCardBadge != null) + { + newCardBadge.SetActive(false); + } + } + } +} + diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/States/CardEnlargedNewState.cs.meta b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardEnlargedNewState.cs.meta new file mode 100644 index 00000000..2d87932d --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardEnlargedNewState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 698741a53f314b598af359a81d914ed3 +timeCreated: 1762884651 \ No newline at end of file diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/States/CardEnlargedRepeatState.cs b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardEnlargedRepeatState.cs new file mode 100644 index 00000000..1eae9bf7 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardEnlargedRepeatState.cs @@ -0,0 +1,86 @@ +using Core.SaveLoad; +using TMPro; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; + +namespace UI.CardSystem.StateMachine.States +{ + /// + /// Enlarged state for REPEAT cards - shows progress bar toward next rarity upgrade. + /// Owns the ProgressBarUI as a child GameObject. + /// + public class CardEnlargedRepeatState : AppleState, IPointerClickHandler + { + [Header("State-Owned Visuals")] + [SerializeField] private GameObject progressBarContainer; + [SerializeField] private Image progressBarFill; + [SerializeField] private TextMeshProUGUI progressText; + [SerializeField] private int cardsToUpgrade = 5; + + private CardContext _context; + private Vector3 _originalScale; + + private void Awake() + { + _context = GetComponentInParent(); + } + + public override void OnEnterState() + { + // Store original scale + _originalScale = _context.RootTransform.localScale; + + // Show progress bar + if (progressBarContainer != null) + { + progressBarContainer.SetActive(true); + UpdateProgressBar(); + } + + // Enlarge the card + if (_context.Animator != null) + { + _context.Animator.PlayEnlarge(); + } + } + + private void UpdateProgressBar() + { + int currentCount = _context.RepeatCardCount; + float progress = (float)currentCount / cardsToUpgrade; + + if (progressBarFill != null) + { + progressBarFill.fillAmount = progress; + } + + if (progressText != null) + { + progressText.text = $"{currentCount}/{cardsToUpgrade}"; + } + } + + public void OnPointerClick(PointerEventData eventData) + { + // Tap to dismiss - shrink back and transition to revealed state + if (_context.Animator != null) + { + _context.Animator.PlayShrink(_originalScale, onComplete: () => + { + _context.StateMachine.ChangeState("RevealedState"); + }); + } + } + + private void OnDisable() + { + // Hide progress bar when leaving state + if (progressBarContainer != null) + { + progressBarContainer.SetActive(false); + } + } + } +} + diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/States/CardEnlargedRepeatState.cs.meta b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardEnlargedRepeatState.cs.meta new file mode 100644 index 00000000..83fe4ca3 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardEnlargedRepeatState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 257f0c81caa14481812a8ca0397bf567 +timeCreated: 1762884651 \ No newline at end of file diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/States/CardFlippingState.cs b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardFlippingState.cs new file mode 100644 index 00000000..2696d30f --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardFlippingState.cs @@ -0,0 +1,81 @@ +using Core.SaveLoad; +using UnityEngine; + +namespace UI.CardSystem.StateMachine.States +{ + /// + /// Flipping state - handles the card flip animation from back to front. + /// Owns the CardBackVisual as a child GameObject. + /// + public class CardFlippingState : AppleState + { + [Header("State-Owned Visuals")] + [SerializeField] private GameObject cardBackVisual; + + private CardContext _context; + + private void Awake() + { + _context = GetComponentInParent(); + } + + public override void OnEnterState() + { + // Activate card back, hide card front + if (cardBackVisual != null) + { + cardBackVisual.SetActive(true); + } + + if (_context.CardDisplay != null) + { + _context.CardDisplay.gameObject.SetActive(false); + } + + // Play flip animation + scale punch + if (_context.Animator != null) + { + _context.Animator.PlayFlip( + cardBack: cardBackVisual.transform, + cardFront: _context.CardDisplay.transform, + onComplete: OnFlipComplete + ); + + _context.Animator.PlayFlipScalePunch(); + } + } + + private void OnFlipComplete() + { + // Transition based on whether this is a new card or repeat + if (_context.IsNewCard) + { + _context.StateMachine.ChangeState("EnlargedNewState"); + } + else if (_context.RepeatCardCount > 0) + { + _context.StateMachine.ChangeState("EnlargedRepeatState"); + } + else + { + _context.StateMachine.ChangeState("RevealedState"); + } + } + + private void OnDisable() + { + // Hide card back when leaving state + if (cardBackVisual != null) + { + cardBackVisual.SetActive(false); + } + + // Ensure card front is visible + if (_context?.CardDisplay != null) + { + _context.CardDisplay.gameObject.SetActive(true); + } + } + } +} + diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/States/CardFlippingState.cs.meta b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardFlippingState.cs.meta new file mode 100644 index 00000000..0f92aa31 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardFlippingState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7223d6cab8d94f74b00a6a538291850a +timeCreated: 1762884650 \ No newline at end of file diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/States/CardIdleState.cs b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardIdleState.cs new file mode 100644 index 00000000..ba479161 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardIdleState.cs @@ -0,0 +1,118 @@ +using Core.SaveLoad; +using Pixelplacement.TweenSystem; +using UnityEngine; +using UnityEngine.EventSystems; + +namespace UI.CardSystem.StateMachine.States +{ + /// + /// Idle state - card back is showing with gentle hover animation. + /// Waiting for click to flip and reveal. + /// Based on FlippableCard's idle behavior. + /// + public class CardIdleState : AppleState, IPointerClickHandler, IPointerEnterHandler, IPointerExitHandler + { + [Header("State-Owned Visuals")] + [SerializeField] private GameObject cardBackVisual; + + [Header("Idle Hover Settings")] + [SerializeField] private bool enableIdleHover = true; + [SerializeField] private float idleHoverHeight = 10f; + [SerializeField] private float idleHoverDuration = 1.5f; + [SerializeField] private float hoverScaleMultiplier = 1.05f; + + private CardContext _context; + private TweenBase _idleHoverTween; + private Vector2 _originalPosition; + + private void Awake() + { + _context = GetComponentInParent(); + } + + public override void OnEnterState() + { + // Show card back, hide card front + if (cardBackVisual != null) + { + cardBackVisual.SetActive(true); + // Ensure card back is at 0° rotation (facing camera) + cardBackVisual.transform.localRotation = Quaternion.Euler(0, 0, 0); + } + + if (_context.CardDisplay != null) + { + _context.CardDisplay.gameObject.SetActive(false); + // Ensure card front starts at 180° rotation (flipped away) + _context.CardDisplay.transform.localRotation = Quaternion.Euler(0, 180, 0); + } + + // Save original position for hover animation + RectTransform rectTransform = _context.RootTransform.GetComponent(); + if (rectTransform != null) + { + _originalPosition = rectTransform.anchoredPosition; + } + + // Start idle hover animation + if (enableIdleHover && _context.Animator != null) + { + _idleHoverTween = _context.Animator.StartIdleHover(idleHoverHeight, idleHoverDuration); + } + } + + public void OnPointerEnter(PointerEventData eventData) + { + // Scale up slightly on hover + if (_context.Animator != null) + { + _context.Animator.AnimateScale(Vector3.one * hoverScaleMultiplier, 0.2f); + } + } + + public void OnPointerExit(PointerEventData eventData) + { + // Scale back to normal + if (_context.Animator != null) + { + _context.Animator.AnimateScale(Vector3.one, 0.2f); + } + } + + public void OnPointerClick(PointerEventData eventData) + { + // Click to flip - transition to flipping state + _context.StateMachine.ChangeState("FlippingState"); + } + + private void OnDisable() + { + // Stop idle hover animation when leaving state + if (_idleHoverTween != null) + { + _idleHoverTween.Stop(); + _idleHoverTween = null; + + // Return to original position + RectTransform rectTransform = _context.RootTransform.GetComponent(); + if (rectTransform != null && _context.Animator != null) + { + _context.Animator.AnimateAnchoredPosition(_originalPosition, 0.3f); + } + } + + // Reset scale + if (_context.Animator != null) + { + _context.Animator.AnimateScale(Vector3.one, 0.2f); + } + + // Hide card back when leaving state + if (cardBackVisual != null) + { + cardBackVisual.SetActive(false); + } + } + } +} + diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/States/CardIdleState.cs.meta b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardIdleState.cs.meta new file mode 100644 index 00000000..be874b44 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardIdleState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7da1bdc06be348f2979d3b92cc7ce723 +timeCreated: 1762884650 \ No newline at end of file diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/States/CardInteractionHandler.cs b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardInteractionHandler.cs new file mode 100644 index 00000000..e29f56c3 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardInteractionHandler.cs @@ -0,0 +1,65 @@ +using UnityEngine; +using UnityEngine.EventSystems; + +namespace UI.CardSystem.StateMachine.States +{ + /// + /// Optional helper component for handling drag/drop integration with existing DragDrop system. + /// Can be attached to Card root to bridge between state machine and drag system. + /// + public class CardInteractionHandler : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler + { + private CardContext _context; + private CardDraggingState _draggingState; + private bool _isDragging; + + private void Awake() + { + _context = GetComponent(); + } + + public void OnBeginDrag(PointerEventData eventData) + { + // Only allow drag from certain states + var currentState = _context.StateMachine.currentState?.name; + if (currentState != "RevealedState" && currentState != "PlacedInSlotState") + { + return; + } + + // Transition to dragging state + _context.StateMachine.ChangeState("DraggingState"); + _draggingState = _context.StateMachine.currentState?.GetComponent(); + _isDragging = true; + } + + public void OnDrag(PointerEventData eventData) + { + if (!_isDragging || _draggingState == null) return; + + // Update drag position + Vector3 worldPosition; + RectTransformUtility.ScreenPointToWorldPointInRectangle( + transform as RectTransform, + eventData.position, + eventData.pressEventCamera, + out worldPosition + ); + + _draggingState.UpdateDragPosition(worldPosition); + } + + public void OnEndDrag(PointerEventData eventData) + { + if (!_isDragging || _draggingState == null) return; + + _isDragging = false; + + // Check if dropped over a valid slot + // This would integrate with your existing AlbumCardSlot system + // For now, just return to revealed state + _draggingState.OnDroppedOutsideSlot(); + } + } +} + diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/States/CardInteractionHandler.cs.meta b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardInteractionHandler.cs.meta new file mode 100644 index 00000000..2774d399 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardInteractionHandler.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 860a5494378b465a9cc05b2b3d585bf9 +timeCreated: 1762884900 \ No newline at end of file diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/States/CardPlacedInSlotState.cs b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardPlacedInSlotState.cs new file mode 100644 index 00000000..c27eefc5 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardPlacedInSlotState.cs @@ -0,0 +1,53 @@ +using Core; +using Core.SaveLoad; +using UnityEngine.EventSystems; + +namespace UI.CardSystem.StateMachine.States +{ + /// + /// Placed in slot state - card is in an album slot and can be clicked to enlarge. + /// Manages the parent slot reference. + /// + public class CardPlacedInSlotState : AppleState, IPointerClickHandler + { + private CardContext _context; + private AlbumCardSlot _parentSlot; + + private void Awake() + { + _context = GetComponentInParent(); + } + + public override void OnEnterState() + { + Logging.Debug($"[CardPlacedInSlotState] Card placed in slot: {_context.CardData?.Name}"); + + // Card is now part of the album, no special visuals needed + // Just wait for interaction + } + + /// + /// Set the parent slot this card belongs to + /// + public void SetParentSlot(AlbumCardSlot slot) + { + _parentSlot = slot; + } + + /// + /// Get the parent slot + /// + public AlbumCardSlot GetParentSlot() + { + return _parentSlot; + } + + public void OnPointerClick(PointerEventData eventData) + { + // Click to enlarge when in album + Logging.Debug($"[CardPlacedInSlotState] Card clicked in slot, transitioning to enlarged state"); + _context.StateMachine.ChangeState("AlbumEnlargedState"); + } + } +} + diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/States/CardPlacedInSlotState.cs.meta b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardPlacedInSlotState.cs.meta new file mode 100644 index 00000000..ac3264b3 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardPlacedInSlotState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 11a4dc9bbeed4623baf1675ab5679bd9 +timeCreated: 1762884899 \ No newline at end of file diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/States/CardRevealedState.cs b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardRevealedState.cs new file mode 100644 index 00000000..1c4e3ff4 --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardRevealedState.cs @@ -0,0 +1,33 @@ +using Core.SaveLoad; +using UnityEngine; +using UnityEngine.EventSystems; + +namespace UI.CardSystem.StateMachine.States +{ + /// + /// Revealed state - card is flipped and visible, waiting for interaction. + /// Can be clicked to enlarge or dragged to place in album. + /// + public class CardRevealedState : AppleState, IPointerClickHandler + { + private CardContext _context; + + private void Awake() + { + _context = GetComponentInParent(); + } + + public override void OnEnterState() + { + // Card is simply visible and interactable + // No special animations needed + } + + public void OnPointerClick(PointerEventData eventData) + { + // Click to enlarge + _context.StateMachine.ChangeState("EnlargedNewState"); + } + } +} + diff --git a/Assets/Scripts/UI/CardSystem/StateMachine/States/CardRevealedState.cs.meta b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardRevealedState.cs.meta new file mode 100644 index 00000000..6b332e6b --- /dev/null +++ b/Assets/Scripts/UI/CardSystem/StateMachine/States/CardRevealedState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 891aad90d6cc41869e497f94d1408859 +timeCreated: 1762884650 \ No newline at end of file