using Pixelplacement; using Pixelplacement.TweenSystem; using UI.DragAndDrop.Core; using UnityEngine; using UnityEngine.UI; namespace UI.CardSystem.DragDrop { /// /// Visual representation for BoosterPackDraggable. /// Displays the booster pack sprite and handles opening animations. /// public class BoosterPackVisual : DraggableVisual { [Header("Booster Pack Visual")] [SerializeField] private Image packImage; [SerializeField] private Sprite packSprite; [SerializeField] private ParticleSystem glowEffect; [SerializeField] private Transform glowTransform; [Header("Opening Animation")] [SerializeField] private float openingScalePunch = 0.5f; [SerializeField] private float openingRotationPunch = 360f; [SerializeField] private float openingDuration = 0.5f; private BoosterPackDraggable _boosterDraggable; // Effect tracking private int _glowRotationTweenId = -1; private float _defaultGlowRate = 10f; public override void Initialize(DraggableObject parent) { base.Initialize(parent); _boosterDraggable = parent as BoosterPackDraggable; // Get pack image if not assigned if (packImage == null) { packImage = GetComponentInChildren(); } // Set initial sprite if (packImage != null && packSprite != null) { packImage.sprite = packSprite; } // Subscribe to booster events if (_boosterDraggable != null) { _boosterDraggable.OnBoosterOpened += HandleBoosterOpened; _boosterDraggable.OnTapped += HandleTapped; } // Start glow effect if available if (glowEffect != null && !glowEffect.isPlaying) { glowEffect.Play(); } } protected override void UpdateVisualContent() { // Update glow rotation for visual interest (skip if effects suppressed) if (glowTransform != null && !_effectsSuppressed) { glowTransform.Rotate(Vector3.forward * 30f * Time.deltaTime); } } private void HandleBoosterOpened(BoosterPackDraggable booster) { PlayOpeningAnimation(); } private void HandleTapped(BoosterPackDraggable booster, int currentTap, int maxTaps) { PlayShakeAnimation(currentTap, maxTaps); } /// /// Play progressive shake animation based on tap intensity /// This is always allowed, even when effects are suppressed /// public void PlayShakeAnimation(int intensity, int maxIntensity) { float normalizedIntensity = (float)intensity / maxIntensity; float shakeAmount = Mathf.Lerp(5f, 30f, normalizedIntensity); float shakeDuration = 0.15f; // Shake rotation Vector3 shakeRotation = new Vector3( Random.Range(-shakeAmount, shakeAmount), Random.Range(-shakeAmount, shakeAmount), Random.Range(-shakeAmount, shakeAmount) ); Tween.Rotation(transform, transform.eulerAngles + shakeRotation, shakeDuration, 0f, Tween.EaseOutBack, completeCallback: () => { Tween.Rotation(transform, Vector3.zero, shakeDuration, 0f, Tween.EaseInBack); }); // NOTE: Scale pulse is handled by BoosterPackDraggable.OnPointerUpHook() // We don't need a duplicate scale tween here - it would conflict! // Extra glow burst on final tap (only if effects not suppressed) if (intensity == maxIntensity && glowEffect != null && !_effectsSuppressed) { var emission = glowEffect.emission; emission.rateOverTimeMultiplier = 50f; } } /// /// Play the booster pack opening animation /// public void PlayOpeningAnimation() { // Scale punch Vector3 targetScale = transform.localScale * (1f + openingScalePunch); Tween.LocalScale(transform, targetScale, openingDuration / 2f, 0f, Tween.EaseOutBack, completeCallback: () => { Tween.LocalScale(transform, Vector3.one, openingDuration / 2f, 0f, Tween.EaseInBack); }); // Rotation Tween.Rotation(transform, transform.eulerAngles + Vector3.forward * openingRotationPunch, openingDuration, 0f, Tween.EaseOutBack); // Glow burst if (glowEffect != null) { glowEffect.Stop(); glowEffect.Play(); } } /// /// Set the booster pack sprite /// public void SetPackSprite(Sprite sprite) { packSprite = sprite; if (packImage != null) { packImage.sprite = packSprite; } } protected override void OnPointerEnterVisual() { base.OnPointerEnterVisual(); // Extra glow when hovering (skip if effects suppressed) if (glowEffect != null && !_effectsSuppressed) { var emission = glowEffect.emission; emission.rateOverTimeMultiplier = 20f; } } protected override void OnPointerExitVisual() { base.OnPointerExitVisual(); // Restore normal glow (skip if effects suppressed) if (glowEffect != null && !_effectsSuppressed) { var emission = glowEffect.emission; emission.rateOverTimeMultiplier = _defaultGlowRate; } } #region Effect Management Overrides protected override void OnEffectsSuppressed() { base.OnEffectsSuppressed(); // Stop glow effect if (glowEffect != null) { glowEffect.Stop(); } // Cancel glow rotation tween if tracked if (_glowRotationTweenId != -1) { Tween.Stop(_glowRotationTweenId); _glowRotationTweenId = -1; } // Reset glow rotation to zero if (glowTransform != null) { glowTransform.localRotation = Quaternion.identity; } } protected override void OnEffectsResumed() { base.OnEffectsResumed(); // Resume glow effect if (glowEffect != null && !glowEffect.isPlaying) { glowEffect.Play(); } } protected override void OnEffectsReset() { base.OnEffectsReset(); // Stop glow effect if (glowEffect != null) { glowEffect.Stop(); } // Reset glow rotation if (glowTransform != null) { glowTransform.localRotation = Quaternion.identity; } // NOTE: Tap pulse scale is handled by BoosterPackDraggable, not here } #endregion #region Event Handler Overrides (Block when in Opening Slot) protected override void HandleDragStarted(DraggableObject draggable) { // Don't call base if effects are suppressed (in opening slot) if (_effectsSuppressed) return; base.HandleDragStarted(draggable); } protected override void HandleDragEnded(DraggableObject draggable) { // ALWAYS reset canvas sorting, even when effects are suppressed if (canvas != null) { canvas.overrideSorting = false; } // Skip other drag end effects if suppressed (in opening slot) if (_effectsSuppressed) return; base.HandleDragEnded(draggable); } protected override void HandlePointerEnter(DraggableObject draggable) { // Don't call base if effects are suppressed (in opening slot) if (_effectsSuppressed) return; base.HandlePointerEnter(draggable); } protected override void HandlePointerExit(DraggableObject draggable) { // Don't call base if effects are suppressed (in opening slot) if (_effectsSuppressed) return; base.HandlePointerExit(draggable); } protected override void HandlePointerDown(DraggableObject draggable) { // Don't call base if effects are suppressed (in opening slot) // This allows the BoosterPackDraggable to handle tap pulse itself if (_effectsSuppressed) return; base.HandlePointerDown(draggable); } protected override void HandlePointerUp(DraggableObject draggable, bool longPress) { // Don't call base if effects are suppressed (in opening slot) // This allows the BoosterPackDraggable to handle tap pulse itself if (_effectsSuppressed) return; base.HandlePointerUp(draggable, longPress); } protected override void HandleSelection(DraggableObject draggable, bool selected) { // Don't call base if effects are suppressed (in opening slot) if (_effectsSuppressed) return; base.HandleSelection(draggable, selected); } #endregion protected override void OnDestroy() { base.OnDestroy(); if (_boosterDraggable != null) { _boosterDraggable.OnBoosterOpened -= HandleBoosterOpened; } } } }