using Pixelplacement; using Pixelplacement.TweenSystem; using UnityEngine; using System; using AppleHills.Core.Settings; using Core; 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 { private Transform _transform; private RectTransform _rectTransform; private ICardSystemSettings _settings; private TweenBase _activeIdleHoverTween; private void Awake() { _transform = transform; _rectTransform = GetComponent(); _settings = GameManager.GetSettingsObject(); } #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 ?? _settings.DefaultAnimationDuration, 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 ?? _settings.DefaultAnimationDuration, 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 ?? _settings.DefaultAnimationDuration, 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 ?? _settings.DefaultAnimationDuration, 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 = null, Action onComplete = null) { float flipDuration = duration ?? _settings.FlipDuration; // Phase 1: Rotate both to 90 degrees (edge view) if (cardBack != null) { Tween.LocalRotation(cardBack, Quaternion.Euler(0, 90, 0), flipDuration * 0.5f, 0f, Tween.EaseInOut); } if (cardFront != null) { Tween.LocalRotation(cardFront, Quaternion.Euler(0, 90, 0), flipDuration * 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), flipDuration * 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 = null, float? duration = null) { float punch = punchScale ?? _settings.FlipScalePunch; float flipDuration = duration ?? _settings.FlipDuration; Vector3 originalScale = _transform.localScale; Tween.LocalScale(_transform, originalScale * punch, flipDuration * 0.5f, 0f, Tween.EaseOutBack, completeCallback: () => { Tween.LocalScale(_transform, originalScale, flipDuration * 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 = null, float? duration = null, Action onComplete = null) { float scale = targetScale ?? _settings.NewCardEnlargedScale; float scaleDuration = duration ?? _settings.ScaleDuration; Tween.LocalScale(_transform, Vector3.one * scale, scaleDuration, 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 = null, Action onComplete = null) { float scaleDuration = duration ?? _settings.ScaleDuration; Tween.LocalScale(_transform, targetScale, scaleDuration, 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. /// Only starts if not already running, or kills and restarts. /// public TweenBase StartIdleHover(float hoverHeight = 10f, float duration = 1.5f, bool restartIfActive = false) { // If already running, either skip or restart if (_activeIdleHoverTween != null) { if (!restartIfActive) { // Already running, skip return _activeIdleHoverTween; } // Kill existing and restart _activeIdleHoverTween.Stop(); _activeIdleHoverTween = null; } if (_rectTransform != null) { Vector2 originalPos = _rectTransform.anchoredPosition; Vector2 targetPos = originalPos + Vector2.up * hoverHeight; _activeIdleHoverTween = 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 _activeIdleHoverTween; } return null; } /// /// Stop idle hover animation and return to original position /// public void StopIdleHover(Vector2 originalPosition, float duration = 0.3f) { // Stop the tracked tween if it exists if (_activeIdleHoverTween != null) { _activeIdleHoverTween.Stop(); _activeIdleHoverTween = null; } // Return to original position if (_rectTransform != null) { Tween.AnchoredPosition(_rectTransform, originalPosition, duration, 0f, Tween.EaseInOut); } } #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 } }