diff --git a/Assets/Prefabs/UI/CardsSystem/Boosters/BoosterPackPrefab.prefab b/Assets/Prefabs/UI/CardsSystem/Boosters/BoosterPackPrefab.prefab index e97da279..cdde1335 100644 --- a/Assets/Prefabs/UI/CardsSystem/Boosters/BoosterPackPrefab.prefab +++ b/Assets/Prefabs/UI/CardsSystem/Boosters/BoosterPackPrefab.prefab @@ -96,8 +96,8 @@ MonoBehaviour: canOpenOnDoubleClick: 0 canTapToOpen: 0 maxTapsToOpen: 3 - tapPulseScale: 1.15 - tapPulseDuration: 0.2 + tapPulseScale: 2 + tapPulseDuration: 0.5 openingParticleSystem: {fileID: 0} --- !u!1 &7174819457781356441 GameObject: diff --git a/Assets/Resources/PerformanceTestRunInfo.json b/Assets/Resources/PerformanceTestRunInfo.json deleted file mode 100644 index 6dd2406e..00000000 --- a/Assets/Resources/PerformanceTestRunInfo.json +++ /dev/null @@ -1 +0,0 @@ -{"TestSuite":"","Date":0,"Player":{"Development":false,"ScreenWidth":0,"ScreenHeight":0,"ScreenRefreshRate":0,"Fullscreen":false,"Vsync":0,"AntiAliasing":0,"Batchmode":false,"RenderThreadingMode":"MultiThreaded","GpuSkinning":false,"Platform":"","ColorSpace":"","AnisotropicFiltering":"","BlendWeights":"","GraphicsApi":"","ScriptingBackend":"IL2CPP","AndroidTargetSdkVersion":"AndroidApiLevelAuto","AndroidBuildSystem":"Gradle","BuildTarget":"Android","StereoRenderingPath":"MultiPass"},"Hardware":{"OperatingSystem":"","DeviceModel":"","DeviceName":"","ProcessorType":"","ProcessorCount":0,"GraphicsDeviceName":"","SystemMemorySizeMB":0},"Editor":{"Version":"6000.2.6f1","Branch":"6000.2/staging","Changeset":"cc51a95c0300","Date":1758053328},"Dependencies":["com.moolt.packages.net@0.0.3","com.unity.2d.sprite@1.0.0","com.unity.2d.spriteshape@12.0.1","com.unity.addressables@2.7.3","com.unity.addressables.android@1.0.7","com.unity.cinemachine@3.1.4","com.unity.device-simulator.devices@1.0.0","com.unity.feature.2d@2.0.1","com.unity.film-internal-utilities@0.18.4-preview","com.unity.graphtoolkit@0.4.0-exp.2","com.unity.ide.rider@3.0.38","com.unity.ide.visualstudio@2.0.23","com.unity.inputsystem@1.14.2","com.unity.multiplayer.center@1.0.0","com.unity.render-pipelines.universal@17.2.0","com.unity.timeline@1.8.9","com.unity.ugui@2.0.0","com.unity.modules.accessibility@1.0.0","com.unity.modules.ai@1.0.0","com.unity.modules.androidjni@1.0.0","com.unity.modules.animation@1.0.0","com.unity.modules.assetbundle@1.0.0","com.unity.modules.audio@1.0.0","com.unity.modules.cloth@1.0.0","com.unity.modules.director@1.0.0","com.unity.modules.imageconversion@1.0.0","com.unity.modules.imgui@1.0.0","com.unity.modules.jsonserialize@1.0.0","com.unity.modules.particlesystem@1.0.0","com.unity.modules.physics@1.0.0","com.unity.modules.physics2d@1.0.0","com.unity.modules.screencapture@1.0.0","com.unity.modules.terrain@1.0.0","com.unity.modules.terrainphysics@1.0.0","com.unity.modules.tilemap@1.0.0","com.unity.modules.ui@1.0.0","com.unity.modules.uielements@1.0.0","com.unity.modules.umbra@1.0.0","com.unity.modules.unityanalytics@1.0.0","com.unity.modules.unitywebrequest@1.0.0","com.unity.modules.unitywebrequestassetbundle@1.0.0","com.unity.modules.unitywebrequestaudio@1.0.0","com.unity.modules.unitywebrequesttexture@1.0.0","com.unity.modules.unitywebrequestwww@1.0.0","com.unity.modules.vehicles@1.0.0","com.unity.modules.video@1.0.0","com.unity.modules.vr@1.0.0","com.unity.modules.wind@1.0.0","com.unity.modules.xr@1.0.0","com.unity.modules.subsystems@1.0.0","com.unity.modules.hierarchycore@1.0.0","com.unity.render-pipelines.core@17.2.0","com.unity.shadergraph@17.2.0","com.unity.render-pipelines.universal-config@17.0.3","com.unity.test-framework@1.6.0","com.unity.ext.nunit@2.0.5","com.unity.2d.animation@12.0.2","com.unity.2d.pixel-perfect@5.1.0","com.unity.2d.psdimporter@11.0.1","com.unity.2d.tilemap@1.0.0","com.unity.2d.tilemap.extras@5.0.1","com.unity.2d.aseprite@2.0.1","com.unity.splines@2.8.2","com.unity.profiling.core@1.0.2","com.unity.scriptablebuildpipeline@2.4.2","com.unity.2d.common@11.0.1","com.unity.mathematics@1.3.2","com.unity.searcher@4.9.3","com.unity.burst@1.8.24","com.unity.collections@2.5.7","com.unity.rendering.light-transport@1.0.1","com.unity.settings-manager@2.1.0","com.unity.nuget.mono-cecil@1.11.5","com.unity.test-framework.performance@3.1.0"],"Results":[]} \ No newline at end of file diff --git a/Assets/Resources/PerformanceTestRunInfo.json.meta b/Assets/Resources/PerformanceTestRunInfo.json.meta deleted file mode 100644 index 2b7c79ce..00000000 --- a/Assets/Resources/PerformanceTestRunInfo.json.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 8c268bc0f4efc034db35b348994f2a52 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/PerformanceTestRunSettings.json b/Assets/Resources/PerformanceTestRunSettings.json deleted file mode 100644 index 49438ae1..00000000 --- a/Assets/Resources/PerformanceTestRunSettings.json +++ /dev/null @@ -1 +0,0 @@ -{"MeasurementCount":-1} \ No newline at end of file diff --git a/Assets/Resources/PerformanceTestRunSettings.json.meta b/Assets/Resources/PerformanceTestRunSettings.json.meta deleted file mode 100644 index c97bdcc3..00000000 --- a/Assets/Resources/PerformanceTestRunSettings.json.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 341eafcd16915de49acdfe16b2ffaba4 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/UI/CardSystem/DragDrop/BoosterPackDraggable.cs b/Assets/Scripts/UI/CardSystem/DragDrop/BoosterPackDraggable.cs index 1802f9eb..f76ae8a7 100644 --- a/Assets/Scripts/UI/CardSystem/DragDrop/BoosterPackDraggable.cs +++ b/Assets/Scripts/UI/CardSystem/DragDrop/BoosterPackDraggable.cs @@ -134,16 +134,47 @@ namespace UI.CardSystem.DragDrop /// public void SetInOpeningSlot(bool inSlot) { + Debug.Log($"[BoosterPackDraggable] SetInOpeningSlot({inSlot}) called on {name}"); + SetDraggingEnabled(!inSlot); // Disable dragging when in opening slot + Debug.Log($"[BoosterPackDraggable] SetDraggingEnabled({!inSlot}) called"); + canTapToOpen = inSlot; // Enable tap-to-open when in opening slot if (inSlot) { _currentTapCount = 0; // Reset tap counter when placed + + // Suppress visual effects (idle animations, glow, etc.) when in opening slot + // But allow slot tween and tap pulse to still work + if (Visual != null) + { + Visual.SuppressEffects(); + + // Play one-time placement tween to animate into slot + // The visual will follow the parent to its slot position, then lock in place + // Get target scale from current slot if it has scale mode + Vector3 targetScale = Vector3.one; + if (CurrentSlot != null && CurrentSlot.GetComponent() != null) + { + // Access the slot's occupant scale if it's in Scale mode + // For now, use Vector3.one as default + targetScale = Vector3.one; + } + + Visual.PlayPlacementTween(0.3f, targetScale); + } } else { ResetOpeningState(); // Reset completely when removed + + // Resume visual effects when removed from opening slot + if (Visual != null) + { + Visual.StopPlacementTween(); // Stop any ongoing placement tween + Visual.ResumeEffects(); + } } } diff --git a/Assets/Scripts/UI/CardSystem/DragDrop/BoosterPackVisual.cs b/Assets/Scripts/UI/CardSystem/DragDrop/BoosterPackVisual.cs index 5240efdd..a2ec3492 100644 --- a/Assets/Scripts/UI/CardSystem/DragDrop/BoosterPackVisual.cs +++ b/Assets/Scripts/UI/CardSystem/DragDrop/BoosterPackVisual.cs @@ -1,4 +1,5 @@ using Pixelplacement; +using Pixelplacement.TweenSystem; using UI.DragAndDrop.Core; using UnityEngine; using UnityEngine.UI; @@ -24,6 +25,10 @@ namespace UI.CardSystem.DragDrop private BoosterPackDraggable _boosterDraggable; + // Effect tracking + private int _glowRotationTweenId = -1; + private float _defaultGlowRate = 10f; + public override void Initialize(DraggableObject parent) { base.Initialize(parent); @@ -58,8 +63,8 @@ namespace UI.CardSystem.DragDrop protected override void UpdateVisualContent() { - // Update glow rotation for visual interest - if (glowTransform != null) + // Update glow rotation for visual interest (skip if effects suppressed) + if (glowTransform != null && !_effectsSuppressed) { glowTransform.Rotate(Vector3.forward * 30f * Time.deltaTime); } @@ -77,6 +82,7 @@ namespace UI.CardSystem.DragDrop /// /// Play progressive shake animation based on tap intensity + /// This is always allowed, even when effects are suppressed /// public void PlayShakeAnimation(int intensity, int maxIntensity) { @@ -98,17 +104,11 @@ namespace UI.CardSystem.DragDrop shakeDuration, 0f, Tween.EaseInBack); }); - // Scale punch (gets bigger with each tap) - float punchScale = 1f + (normalizedIntensity * 0.2f); - Tween.LocalScale(transform, Vector3.one * punchScale, - shakeDuration / 2f, 0f, Tween.EaseOutBack, - completeCallback: () => { - Tween.LocalScale(transform, Vector3.one, - shakeDuration / 2f, 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 - if (intensity == maxIntensity && glowEffect != null) + // Extra glow burst on final tap (only if effects not suppressed) + if (intensity == maxIntensity && glowEffect != null && !_effectsSuppressed) { var emission = glowEffect.emission; emission.rateOverTimeMultiplier = 50f; @@ -155,8 +155,8 @@ namespace UI.CardSystem.DragDrop { base.OnPointerEnterVisual(); - // Extra glow when hovering - if (glowEffect != null) + // Extra glow when hovering (skip if effects suppressed) + if (glowEffect != null && !_effectsSuppressed) { var emission = glowEffect.emission; emission.rateOverTimeMultiplier = 20f; @@ -167,14 +167,141 @@ namespace UI.CardSystem.DragDrop { base.OnPointerExitVisual(); - // Restore normal glow - if (glowEffect != null) + // Restore normal glow (skip if effects suppressed) + if (glowEffect != null && !_effectsSuppressed) { var emission = glowEffect.emission; - emission.rateOverTimeMultiplier = 10f; + 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) + { + // Don't call base if effects are 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(); diff --git a/Assets/Scripts/UI/DragAndDrop/Core/DraggableObject.cs b/Assets/Scripts/UI/DragAndDrop/Core/DraggableObject.cs index 3d9278b4..be85b873 100644 --- a/Assets/Scripts/UI/DragAndDrop/Core/DraggableObject.cs +++ b/Assets/Scripts/UI/DragAndDrop/Core/DraggableObject.cs @@ -190,13 +190,20 @@ namespace UI.DragAndDrop.Core if (eventData.button != PointerEventData.InputButton.Left) return; + Debug.Log($"[DraggableObject] OnBeginDrag called on {name}. _isDraggingEnabled={_isDraggingEnabled}"); + // Check if dragging is enabled BEFORE setting any state if (!_isDraggingEnabled) + { + Debug.Log($"[DraggableObject] OnBeginDrag blocked - dragging disabled on {name}"); return; + } _isDragging = true; _wasDragged = true; + Debug.Log($"[DraggableObject] Drag started on {name}"); + // ...existing code... if (_canvas != null && _canvas.renderMode == RenderMode.ScreenSpaceOverlay && RectTransform != null) { @@ -498,7 +505,26 @@ namespace UI.DragAndDrop.Core /// public virtual void SetDraggingEnabled(bool enabled) { + Debug.Log($"[DraggableObject] SetDraggingEnabled({enabled}) called on {name}. _isDragging={_isDragging}, _isDraggingEnabled={_isDraggingEnabled}"); + _isDraggingEnabled = enabled; + + // If disabling dragging while actively dragging, stop the drag + if (!enabled && _isDragging) + { + Debug.Log($"[DraggableObject] Stopping active drag on {name}"); + _isDragging = false; + + // Re-enable raycasting + if (_raycaster != null) + _raycaster.enabled = true; + if (_imageComponent != null) + _imageComponent.raycastTarget = true; + if (_canvasGroup != null) + _canvasGroup.blocksRaycasts = true; + } + + Debug.Log($"[DraggableObject] After SetDraggingEnabled: _isDragging={_isDragging}, _isDraggingEnabled={_isDraggingEnabled}"); } #endregion diff --git a/Assets/Scripts/UI/DragAndDrop/Core/DraggableVisual.cs b/Assets/Scripts/UI/DragAndDrop/Core/DraggableVisual.cs index eb0510ad..1be3ba56 100644 --- a/Assets/Scripts/UI/DragAndDrop/Core/DraggableVisual.cs +++ b/Assets/Scripts/UI/DragAndDrop/Core/DraggableVisual.cs @@ -1,4 +1,5 @@ using Pixelplacement; +using Pixelplacement.TweenSystem; using UnityEngine; using UnityEngine.InputSystem; // Added for new Input System @@ -46,9 +47,17 @@ namespace UI.DragAndDrop.Core protected int _savedSlotIndex; protected Vector3 _lastPosition; + // Effect tracking + protected bool _effectsSuppressed; + protected bool _isPlayingPlacementTween; // Tracks if a one-time placement tween is active + protected TweenBase _placementPositionTween; + protected TweenBase _placementScaleTween; + protected TweenBase _activeTweenId; // Track active scale tween for cancellation + // Properties public DraggableObject ParentDraggable => _parentDraggable; public bool IsInitialized => _isInitialized; + public bool EffectsSuppressed => _effectsSuppressed; /// /// Initialize the visual with its parent draggable object @@ -136,11 +145,18 @@ namespace UI.DragAndDrop.Core { if (!_isInitialized || _parentDraggable == null) return; - - UpdateFollowPosition(); - UpdateFollowRotation(); // Track base rotation changes - UpdateRotation(); - UpdateTilt(); + + // Skip follow position/rotation when effects are suppressed (e.g., in opening slot) + // UNLESS we're currently playing a placement tween (allow it to complete) + // The visual should stay locked in place after placement tween completes + if (!_effectsSuppressed || _isPlayingPlacementTween) + { + UpdateFollowPosition(); + UpdateFollowRotation(); // Track base rotation changes + UpdateRotation(); + UpdateTilt(); + } + UpdateVisualContent(); } @@ -275,7 +291,10 @@ namespace UI.DragAndDrop.Core : _parentDraggable.GetSlotIndex(); // Idle animation (sine/cosine wobble with different frequencies) + // Disabled if effects are suppressed float idleMultiplier = _parentDraggable.IsHovering ? 0.2f : 1f; + idleMultiplier = _effectsSuppressed ? 0f : idleMultiplier; + float time = Time.time * idleAnimationSpeed + _savedSlotIndex; // Use sine for X wobble, cosine for Y wobble (different phases) @@ -346,7 +365,10 @@ namespace UI.DragAndDrop.Core { if (useScaleAnimations) { - Tween.LocalScale(transform, Vector3.one * scaleOnDrag, scaleTransitionDuration, 0f, Tween.EaseOutBack); + if (_activeTweenId != null) + _activeTweenId.Stop(); + + _activeTweenId = Tween.LocalScale(transform, Vector3.one * scaleOnDrag, scaleTransitionDuration, 0f, Tween.EaseOutBack); } if (canvas != null) @@ -382,7 +404,11 @@ namespace UI.DragAndDrop.Core // Only reset scale if NOT in a slot (let slots handle their own scaling) if (draggable.CurrentSlot == null) { - Tween.LocalScale(transform, Vector3.one, scaleTransitionDuration, 0f, Tween.EaseOutBack); + // TODO: Fix this repetetive stuff + if (_activeTweenId != null) + _activeTweenId.Stop(); + + _activeTweenId = Tween.LocalScale(transform, Vector3.one, scaleTransitionDuration, 0f, Tween.EaseOutBack); } @@ -405,7 +431,10 @@ namespace UI.DragAndDrop.Core { if (useScaleAnimations) { - Tween.LocalScale(transform, Vector3.one * scaleOnHover, scaleTransitionDuration, 0f, Tween.EaseOutBack); + if (_activeTweenId != null) + _activeTweenId.Stop(); + + _activeTweenId = Tween.LocalScale(transform, Vector3.one * scaleOnHover, scaleTransitionDuration, 0f, Tween.EaseOutBack); } // Punch rotation effect @@ -421,7 +450,10 @@ namespace UI.DragAndDrop.Core { if (!draggable.WasDragged && useScaleAnimations) { - Tween.LocalScale(transform, Vector3.one, scaleTransitionDuration, 0f, Tween.EaseOutBack); + if (_activeTweenId != null) + _activeTweenId.Stop(); + + _activeTweenId = Tween.LocalScale(transform, Vector3.one, scaleTransitionDuration, 0f, Tween.EaseOutBack); } OnPointerExitVisual(); @@ -431,7 +463,10 @@ namespace UI.DragAndDrop.Core { if (useScaleAnimations) { - Tween.LocalScale(transform, Vector3.one * scaleOnDrag, scaleTransitionDuration, 0f, Tween.EaseOutBack); + if (_activeTweenId != null) + _activeTweenId.Stop(); + + _activeTweenId = Tween.LocalScale(transform, Vector3.one * scaleOnDrag, scaleTransitionDuration, 0f, Tween.EaseOutBack); } OnPointerDownVisual(); @@ -443,7 +478,10 @@ namespace UI.DragAndDrop.Core if (useScaleAnimations) { - Tween.LocalScale(transform, Vector3.one * targetScale, scaleTransitionDuration, 0f, Tween.EaseOutBack); + if (_activeTweenId != null) + _activeTweenId.Stop(); + + _activeTweenId = Tween.LocalScale(transform, Vector3.one * targetScale, scaleTransitionDuration, 0f, Tween.EaseOutBack); } OnPointerUpVisual(longPress); @@ -462,7 +500,11 @@ namespace UI.DragAndDrop.Core if (useScaleAnimations) { float targetScale = selected ? scaleOnDrag : scaleOnHover; - Tween.LocalScale(transform, Vector3.one * targetScale, scaleTransitionDuration, 0f, Tween.EaseOutBack); + + if (_activeTweenId != null) + _activeTweenId.Stop(); + + _activeTweenId = Tween.LocalScale(transform, Vector3.one * targetScale, scaleTransitionDuration, 0f, Tween.EaseOutBack); } OnSelectionVisual(selected); @@ -520,6 +562,146 @@ namespace UI.DragAndDrop.Core #endregion + #region Effect Management API + + /// + /// Suppress all ongoing visual effects (idle animations, glow, etc.) + /// This does NOT affect tweens initiated externally (like slot placement). + /// Subclasses should override this to stop their specific effects. + /// + public virtual void SuppressEffects() + { + _effectsSuppressed = true; + + // Cancel any active scale tween we're tracking + if (_activeTweenId != null) + { + _activeTweenId.Stop(); + _activeTweenId = null; + } + + // Cancel ALL tweens on this transform to prevent slot scale tween from conflicting + Tween.Cancel(transform.GetInstanceID()); + + OnEffectsSuppressed(); + } + + /// + /// Resume all visual effects that were suppressed + /// + public virtual void ResumeEffects() + { + _effectsSuppressed = false; + OnEffectsResumed(); + } + + /// + /// Reset all visual effects to their default state + /// + public virtual void ResetEffects() + { + _effectsSuppressed = false; + + // Cancel any active scale tween we're tracking + if (_activeTweenId != null) + { + _activeTweenId.Stop(); + _activeTweenId = null; + } + + // Reset scale to default + transform.localScale = Vector3.one; + + // Reset shake parent rotation + if (shakeParent != null) + { + shakeParent.localRotation = Quaternion.identity; + } + + // Reset tilt parent rotation + if (tiltParent != null) + { + tiltParent.localRotation = Quaternion.identity; + } + + OnEffectsReset(); + } + + /// + /// Play a one-time placement tween to move visual to target position and scale. + /// This works even when effects are suppressed, and allows the visual to follow + /// the parent during the tween, then locks in place after completion. + /// + /// Duration of the tween + /// Target scale (if null, uses current parent scale) + public virtual void PlayPlacementTween(float duration = 0.3f, Vector3? targetScale = null) + { + // Cancel any existing placement tweens + StopPlacementTween(); + + _isPlayingPlacementTween = true; + + // The position tween is handled by UpdateFollowPosition naturally + // We just need to wait for the duration, then lock in place + + // If target scale provided, tween the scale + if (targetScale.HasValue) + { + _placementScaleTween = Tween.LocalScale(transform, targetScale.Value, duration, 0f, Tween.EaseOutBack); + } + + // Use a dummy tween to track completion + _placementPositionTween = Tween.Value(0f, 1f, (val) => { }, duration, 0f, Tween.EaseOutBack, + completeCallback: () => + { + _isPlayingPlacementTween = false; + _placementPositionTween = null; + OnPlacementTweenComplete(); + }); + } + + /// + /// Stop any active placement tween and immediately lock in place + /// + public virtual void StopPlacementTween() + { + if (_placementPositionTween != null) + { + _placementPositionTween.Stop(); + _placementPositionTween = null; + } + + if (_placementScaleTween != null) + { + _placementScaleTween.Stop(); + _placementScaleTween = null; + } + + _isPlayingPlacementTween = false; + } + + /// + /// Called when placement tween completes. Override for custom behavior. + /// + protected virtual void OnPlacementTweenComplete() { } + + /// + /// Called when effects are suppressed. Override to stop specific effects. + /// + protected virtual void OnEffectsSuppressed() { } + + /// + /// Called when effects are resumed. Override to restart specific effects. + /// + protected virtual void OnEffectsResumed() { } + + /// + /// Called when effects are reset. Override to reset specific effects. + /// + protected virtual void OnEffectsReset() { } + + #endregion + protected virtual void OnDestroy() { UnsubscribeFromParentEvents();