Add a semi-finished booster opening sequence

This commit is contained in:
Michal Pikulski
2025-11-06 15:27:05 +01:00
parent 4e0c9cb4c4
commit 2d10d92bf5
11 changed files with 5540 additions and 100 deletions

View File

@@ -174,7 +174,7 @@ namespace UI.CardSystem
{
// Only store and switch input mode if this is the first time entering
// (when _previousInputMode hasn't been set yet)
if (Input.InputManager.Instance != null && _previousInputMode == default(Input.InputMode))
if (Input.InputManager.Instance != null)
{
// Store the current input mode before switching
_previousInputMode = Input.InputMode.GameAndUI;

View File

@@ -195,12 +195,29 @@ namespace UI.CardSystem
_currentBoosterInCenter = booster;
// Lock the slot so it can't be dragged out
// Lock the slot so it can't be dragged out
centerOpeningSlot.SetLocked(true);
// Enable tap-to-open and reset tap count
booster.ResetTapCount();
booster.SetTapToOpenEnabled(true);
// Configure booster for opening (disables drag, enables tapping, resets tap count)
booster.SetInOpeningSlot(true);
// Subscribe to tap events for visual feedback
booster.OnTapped += OnBoosterTapped;
booster.OnReadyToOpen += OnBoosterReadyToOpen;
Debug.Log($"[BoosterOpeningPage] Booster placed in center, ready for {booster.CurrentTapCount} taps");
}
private void OnBoosterTapped(BoosterPackDraggable booster, int currentTaps, int maxTaps)
{
Debug.Log($"[BoosterOpeningPage] Booster tapped: {currentTaps}/{maxTaps}");
// Calculate shake intensity (increases with each tap)
float shakeIntensity = currentTaps / (float)maxTaps;
float shakeAmount = 10f + (shakeIntensity * 30f); // 10 to 40 units
// TODO: Shake visual feedback
// This would be handled by BoosterPackVisual if we add a shake method
}
/// <summary>
@@ -222,6 +239,10 @@ namespace UI.CardSystem
{
if (_isProcessingOpening) return;
Debug.Log($"[BoosterOpeningPage] Booster ready to open!");
// Trigger the actual opening sequence
booster.TriggerOpen();
StartCoroutine(ProcessBoosterOpening(booster));
}

View File

@@ -16,8 +16,11 @@ namespace UI.CardSystem.DragDrop
[Header("Tap to Open")]
[SerializeField] private bool canTapToOpen = true;
[SerializeField] private int maxTapsToOpen = 3;
[SerializeField] private float tapPulseScale = 1.15f;
[SerializeField] private float tapPulseDuration = 0.2f;
[SerializeField] private ParticleSystem openingParticleSystem;
// Events
// ...existing code...
public event System.Action<BoosterPackDraggable> OnBoosterOpened;
public event System.Action<BoosterPackDraggable, int, int> OnTapped; // (booster, currentTap, maxTaps)
public event System.Action<BoosterPackDraggable> OnReadyToOpen; // Final tap reached
@@ -33,11 +36,30 @@ namespace UI.CardSystem.DragDrop
{
base.OnPointerUpHook(longPress);
// Handle tap-to-open logic (only when in slot and not dragged)
if (canTapToOpen && !_wasDragged && !longPress && CurrentSlot != null)
// Handle tap-to-open logic (only when in slot and not a long press)
if (canTapToOpen && !longPress && CurrentSlot != null)
{
_currentTapCount++;
// Pulse effect on tap (scales visual up and back down)
if (Visual != null)
{
// Calculate pulse intensity based on tap progress
float tapProgress = _currentTapCount / (float)maxTapsToOpen;
float currentPulseScale = 1f + (tapPulseScale - 1f) * (0.5f + tapProgress * 0.5f); // Increases from 1.075 to 1.15
// Save the current scale before pulsing
Vector3 baseScale = Visual.transform.localScale;
Pixelplacement.Tween.Cancel(Visual.transform.GetInstanceID());
Pixelplacement.Tween.LocalScale(Visual.transform, baseScale * currentPulseScale, tapPulseDuration * 0.5f, 0f,
Pixelplacement.Tween.EaseOutBack, completeCallback: () =>
{
// Return to the base scale we had before pulsing
Pixelplacement.Tween.LocalScale(Visual.transform, baseScale, tapPulseDuration * 0.5f, 0f, Pixelplacement.Tween.EaseInBack);
});
}
OnTapped?.Invoke(this, _currentTapCount, maxTapsToOpen);
if (_currentTapCount >= maxTapsToOpen)
@@ -48,8 +70,8 @@ namespace UI.CardSystem.DragDrop
return; // Don't process double-click if tap-to-open is active
}
// Check for double click
if (canOpenOnDoubleClick && !longPress && !_wasDragged)
// ...existing code...
if (canOpenOnDoubleClick && !longPress)
{
float timeSinceLastClick = Time.time - _lastClickTime;
@@ -84,6 +106,12 @@ namespace UI.CardSystem.DragDrop
_isOpening = true;
// Play particle effect
if (openingParticleSystem != null)
{
openingParticleSystem.Play();
}
OnBoosterOpened?.Invoke(this);
// The actual opening logic (calling CardSystemManager) should be handled
@@ -98,8 +126,27 @@ namespace UI.CardSystem.DragDrop
public void ResetOpeningState()
{
_isOpening = false;
_currentTapCount = 0;
}
/// <summary>
/// Set whether this booster is in the opening slot (disables dragging, enables tapping)
/// </summary>
public void SetInOpeningSlot(bool inSlot)
{
SetDraggingEnabled(!inSlot); // Disable dragging when in opening slot
canTapToOpen = inSlot; // Enable tap-to-open when in opening slot
if (inSlot)
{
_currentTapCount = 0; // Reset tap counter when placed
}
else
{
ResetOpeningState(); // Reset completely when removed
}
}
/// <summary>
/// Reset tap count (useful when starting a new opening sequence)
/// </summary>

View File

@@ -21,7 +21,7 @@ namespace UI.DragAndDrop.Core
{
[Header("Draggable Settings")]
[SerializeField] protected float moveSpeed = 50f;
[SerializeField] protected bool smoothMovement = true;
[SerializeField] protected bool smoothMovement = false; // Disabled for instant cursor tracking
[SerializeField] protected float snapDuration = 0.3f;
[Header("Visual")]
@@ -36,6 +36,7 @@ namespace UI.DragAndDrop.Core
protected bool _isHovering;
protected bool _isSelected;
protected bool _wasDragged;
protected bool _isDraggingEnabled = true;
// References
protected Canvas _canvas;
@@ -87,6 +88,16 @@ namespace UI.DragAndDrop.Core
_canvasGroup = GetComponent<CanvasGroup>();
_raycaster = _canvas?.GetComponent<GraphicRaycaster>();
// If no Image component exists, add an invisible one for raycast detection
// Unity UI requires a Graphic component to receive pointer events
if (_imageComponent == null && _canvasGroup != null)
{
_imageComponent = gameObject.AddComponent<Image>();
_imageComponent.color = new Color(1, 1, 1, 0.01f); // Nearly transparent (0 doesn't work)
_imageComponent.raycastTarget = true;
Debug.Log($"[DraggableObject] Added invisible Image to {name} for raycast detection");
}
// Use assigned visual, or find in children recursively if not assigned
if (visual != null)
{
@@ -118,25 +129,44 @@ namespace UI.DragAndDrop.Core
SmoothMoveTowardPointer();
}
ClampToScreen();
// Only clamp for non-overlay canvases (WorldSpace/ScreenSpaceCamera)
if (_canvas != null && _canvas.renderMode != RenderMode.ScreenSpaceOverlay)
{
ClampToScreen();
}
}
protected virtual void SmoothMoveTowardPointer()
{
Vector3 targetPosition = _lastPointerPosition - _dragOffset;
Vector3 direction = (targetPosition - transform.position).normalized;
float distance = Vector3.Distance(transform.position, targetPosition);
float speed = Mathf.Min(moveSpeed, distance / Time.deltaTime);
transform.Translate(direction * speed * Time.deltaTime, Space.World);
if (RectTransform == null)
return;
// For ScreenSpaceOverlay, work with screen/anchoredPosition
if (_canvas != null && _canvas.renderMode == RenderMode.ScreenSpaceOverlay)
{
Vector2 targetPos = (Vector2)_lastPointerPosition - (Vector2)_dragOffset;
Vector2 currentPos = RectTransform.position;
Vector2 direction = (targetPos - currentPos).normalized;
float distance = Vector2.Distance(currentPos, targetPos);
float speed = Mathf.Min(moveSpeed, distance / Time.deltaTime);
RectTransform.position = currentPos + direction * speed * Time.deltaTime;
}
else
{
// For WorldSpace/ScreenSpaceCamera, use world coordinates
Vector3 targetPosition = _lastPointerPosition - _dragOffset;
Vector3 direction = (targetPosition - transform.position).normalized;
float distance = Vector3.Distance(transform.position, targetPosition);
float speed = Mathf.Min(moveSpeed, distance / Time.deltaTime);
transform.Translate(direction * speed * Time.deltaTime, Space.World);
}
}
protected virtual void ClampToScreen()
{
// Skip clamping for ScreenSpaceOverlay - it doesn't use world coordinates
if (_canvas != null && _canvas.renderMode == RenderMode.ScreenSpaceOverlay)
return;
// This method is only called for WorldSpace/ScreenSpaceCamera canvases
if (Camera.main == null || RectTransform == null)
return;
@@ -159,14 +189,31 @@ namespace UI.DragAndDrop.Core
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
// Check if dragging is enabled BEFORE setting any state
if (!_isDraggingEnabled)
return;
_isDragging = true;
_wasDragged = true;
// Calculate offset
Vector3 worldPointer = GetWorldPosition(eventData);
_dragOffset = worldPointer - transform.position;
_lastPointerPosition = worldPointer;
// ...existing code...
if (_canvas != null && _canvas.renderMode == RenderMode.ScreenSpaceOverlay && RectTransform != null)
{
// For overlay, use screen position directly
_dragOffset = (Vector3)eventData.position - RectTransform.position;
_lastPointerPosition = eventData.position;
}
else
{
// For WorldSpace/ScreenSpaceCamera, convert to world coords
Vector3 worldPointer = GetWorldPosition(eventData);
_dragOffset = worldPointer - transform.position;
_lastPointerPosition = worldPointer;
}
// Reset base rotation to identity (0°) for clean dragging
Tween.Rotation(transform, Quaternion.identity, 0.2f, 0f, Tween.EaseOutBack);
// Disable raycasting to allow detecting slots underneath
if (_raycaster != null)
@@ -190,12 +237,25 @@ namespace UI.DragAndDrop.Core
{
if (!_isDragging)
return;
_lastPointerPosition = GetWorldPosition(eventData);
if (!smoothMovement)
// Update last pointer position based on canvas type
if (_canvas != null && _canvas.renderMode == RenderMode.ScreenSpaceOverlay)
{
transform.position = _lastPointerPosition - _dragOffset;
_lastPointerPosition = eventData.position;
if (!smoothMovement && RectTransform != null)
{
RectTransform.position = (Vector2)_lastPointerPosition - (Vector2)_dragOffset;
}
}
else
{
_lastPointerPosition = GetWorldPosition(eventData);
if (!smoothMovement)
{
transform.position = _lastPointerPosition - _dragOffset;
}
}
}
@@ -217,6 +277,12 @@ namespace UI.DragAndDrop.Core
// Find closest slot and snap
FindAndSnapToSlot();
// Snap base rotation back to slot rotation (if in a slot)
if (_currentSlot != null)
{
Tween.Rotation(transform, _currentSlot.transform.rotation, 0.3f, 0f, Tween.EaseOutBack);
}
OnDragEnded?.Invoke(this);
OnDragEndedHook();
@@ -276,12 +342,18 @@ namespace UI.DragAndDrop.Core
DraggableSlot closestSlot = null;
float closestDistance = float.MaxValue;
// Use RectTransform.position for overlay, transform.position for others
Vector3 myPosition = (_canvas != null && _canvas.renderMode == RenderMode.ScreenSpaceOverlay && RectTransform != null)
? RectTransform.position
: transform.position;
foreach (var container in containers)
{
DraggableSlot slot = container.FindClosestSlot(transform.position, this);
DraggableSlot slot = container.FindClosestSlot(myPosition, this);
if (slot != null)
{
float distance = Vector3.Distance(transform.position, slot.WorldPosition);
Vector3 slotPosition = slot.RectTransform != null ? slot.RectTransform.position : slot.transform.position;
float distance = Vector3.Distance(myPosition, slotPosition);
if (distance < closestDistance)
{
closestDistance = distance;
@@ -419,6 +491,18 @@ namespace UI.DragAndDrop.Core
#endregion
#region Dragging Control
/// <summary>
/// Enable or disable dragging functionality
/// </summary>
public virtual void SetDraggingEnabled(bool enabled)
{
_isDraggingEnabled = enabled;
}
#endregion
#region Helper Methods
protected Vector3 GetWorldPosition(PointerEventData eventData)

View File

@@ -89,6 +89,12 @@ namespace UI.DragAndDrop.Core
case OccupantSizeMode.Scale:
Tween.LocalScale(draggable.transform, occupantScale, scaleTransitionDuration, 0f, Tween.EaseOutBack);
// Also scale the visual if it exists (since visual is now independent)
if (draggable.Visual != null)
{
Tween.LocalScale(draggable.Visual.transform, occupantScale, scaleTransitionDuration, 0f, Tween.EaseOutBack);
}
break;
case OccupantSizeMode.None:

View File

@@ -1,5 +1,6 @@
using Pixelplacement;
using UnityEngine;
using UnityEngine.InputSystem; // Added for new Input System
namespace UI.DragAndDrop.Core
{
@@ -23,7 +24,7 @@ namespace UI.DragAndDrop.Core
[Header("Rotation/Tilt Parameters")]
[SerializeField] protected float rotationAmount = 20f;
[SerializeField] protected float rotationSpeed = 20f;
[SerializeField] protected float autoTiltAmount = 30f;
[SerializeField] protected float autoTiltAmount = 10f; // Reduced from 30f
[SerializeField] protected float manualTiltAmount = 20f;
[SerializeField] protected float tiltSpeed = 20f;
@@ -35,7 +36,7 @@ namespace UI.DragAndDrop.Core
[Header("Idle Animation")]
[SerializeField] protected bool useIdleAnimation = true;
[SerializeField] protected float idleAnimationSpeed = 1f;
[SerializeField] protected float idleAnimationSpeed = 0.5f; // Slowed down from 1f
// State
protected DraggableObject _parentDraggable;
@@ -59,6 +60,14 @@ namespace UI.DragAndDrop.Core
Canvas parentCanvas = parent.GetComponentInParent<Canvas>();
Debug.Log($"[DraggableVisual] Initializing visual for {parent.name} at world pos {parent.transform.position}, parent canvas: {(parentCanvas != null ? parentCanvas.name + " (renderMode: " + parentCanvas.renderMode + ")" : "NULL")}");
// CRITICAL: Reparent visual to canvas (not base) so it can move independently
// This enables the delayed follow effect
if (parentCanvas != null)
{
transform.SetParent(parentCanvas.transform, true); // worldPositionStays = true
Debug.Log($"[DraggableVisual] Reparented visual {name} to canvas {parentCanvas.name} for independent movement");
}
// Get components if assigned (don't auto-create Canvas to avoid Unity's auto-reparenting)
if (canvas == null)
canvas = GetComponent<Canvas>();
@@ -71,11 +80,24 @@ namespace UI.DragAndDrop.Core
// Subscribe to parent events
SubscribeToParentEvents();
// Initial position
// Initial position to match parent
transform.position = parent.transform.position;
_lastPosition = transform.position;
Debug.Log($"[DraggableVisual] Visual {name} initialized at world pos {transform.position}, local pos {transform.localPosition}, parent at world pos {parent.transform.position}, local pos {parent.transform.localPosition}");
// Set rotation to match base's current rotation (will be maintained via separate rotation management)
transform.rotation = parent.transform.rotation;
// Reset shake and tilt parent rotations to zero (local space) for clean wobble
if (shakeParent != null)
{
shakeParent.localRotation = Quaternion.identity;
}
if (tiltParent != null)
{
tiltParent.localRotation = Quaternion.identity;
}
Debug.Log($"[DraggableVisual] Visual {name} initialized at world pos {transform.position}, local pos {transform.localPosition}, local rotation {transform.localRotation.eulerAngles}, parent at world pos {parent.transform.position}, local pos {parent.transform.localPosition}, rotation {parent.transform.rotation.eulerAngles}");
_isInitialized = true;
@@ -116,6 +138,7 @@ namespace UI.DragAndDrop.Core
return;
UpdateFollowPosition();
UpdateFollowRotation(); // Track base rotation changes
UpdateRotation();
UpdateTilt();
UpdateVisualContent();
@@ -123,36 +146,100 @@ namespace UI.DragAndDrop.Core
#region Position & Movement
protected virtual void UpdateFollowRotation()
{
if (_parentDraggable == null)
return;
// Smoothly follow base rotation (since we're no longer a child)
// Base rotation changes when picking up (→ 0°) or dropping (→ slot rotation)
transform.rotation = Quaternion.Lerp(transform.rotation, _parentDraggable.transform.rotation, 10f * Time.deltaTime);
}
protected virtual void UpdateFollowPosition()
{
if (_parentDraggable == null)
return;
Vector3 targetPosition = GetTargetPosition();
// Debug log if position is drastically different (likely a clustering issue)
if (Vector3.Distance(transform.position, targetPosition) > 500f)
{
Debug.LogWarning($"[DraggableVisual] Large position delta detected! Visual {name} at {transform.position}, target {targetPosition}, parent {_parentDraggable.name} at {_parentDraggable.transform.position}");
}
// For ScreenSpaceOverlay, use RectTransform.position consistently
Canvas parentCanvas = _parentDraggable.GetComponentInParent<Canvas>();
bool isOverlay = parentCanvas != null && parentCanvas.renderMode == RenderMode.ScreenSpaceOverlay;
if (useFollowDelay)
Vector3 targetPosition;
Vector3 currentPosition;
if (isOverlay && _parentDraggable.RectTransform != null && GetComponent<RectTransform>() != null)
{
transform.position = Vector3.Lerp(transform.position, targetPosition, followSpeed * Time.deltaTime);
// Use RectTransform.position for overlay (screen space)
RectTransform myRect = GetComponent<RectTransform>();
targetPosition = _parentDraggable.RectTransform.position;
currentPosition = myRect.position;
}
else
{
transform.position = targetPosition;
// Use transform.position for WorldSpace/ScreenSpaceCamera
targetPosition = _parentDraggable.transform.position;
currentPosition = transform.position;
}
// Debug log if position is drastically different (likely a clustering issue)
float distance = Vector3.Distance(currentPosition, targetPosition);
if (distance > 500f)
{
Debug.LogWarning($"[DraggableVisual] Large position delta detected! Visual {name} at {currentPosition}, target {targetPosition}, parent {_parentDraggable.name}, distance: {distance}");
}
// Apply follow logic with snappy easing
if (useFollowDelay)
{
// Calculate lerp factor with snappy ease
float rawT = followSpeed * Time.deltaTime;
// Apply EaseOutCubic for snappier movement: 1 - (1-t)^3
float t = 1f - Mathf.Pow(1f - rawT, 3f);
Vector3 newPosition = Vector3.Lerp(currentPosition, targetPosition, t);
if (isOverlay && GetComponent<RectTransform>() != null)
{
GetComponent<RectTransform>().position = newPosition;
}
else
{
transform.position = newPosition;
}
}
else
{
if (isOverlay && GetComponent<RectTransform>() != null)
{
GetComponent<RectTransform>().position = targetPosition;
}
else
{
transform.position = targetPosition;
}
}
// Calculate movement delta for tilt
Vector3 movement = transform.position - _lastPosition;
Vector3 actualCurrentPos = isOverlay && GetComponent<RectTransform>() != null
? GetComponent<RectTransform>().position
: transform.position;
Vector3 movement = actualCurrentPos - _lastPosition;
_movementDelta = Vector3.Lerp(_movementDelta, movement, 25f * Time.deltaTime);
_lastPosition = transform.position;
_lastPosition = actualCurrentPos;
}
protected virtual Vector3 GetTargetPosition()
{
Canvas parentCanvas = _parentDraggable.GetComponentInParent<Canvas>();
bool isOverlay = parentCanvas != null && parentCanvas.renderMode == RenderMode.ScreenSpaceOverlay;
if (isOverlay && _parentDraggable.RectTransform != null)
{
return _parentDraggable.RectTransform.position;
}
return _parentDraggable.transform.position;
}
@@ -162,59 +249,93 @@ namespace UI.DragAndDrop.Core
protected virtual void UpdateRotation()
{
if (_parentDraggable == null)
if (_parentDraggable == null || shakeParent == null)
return;
// Rotation based on movement direction (like Balatro)
Vector3 movementRotation = _parentDraggable.IsDragging
? _movementDelta * rotationAmount
: (_lastPosition - _parentDraggable.transform.position) * rotationAmount;
// Apply rotation based on movement to shakeParent (not main transform)
// This way it's additive on top of the base rotation in transform.rotation
// Negated to make the bottom "drag behind" instead of leading
Vector3 movementRotation = _movementDelta * -rotationAmount; // Flipped direction
_rotationDelta = Vector3.Lerp(_rotationDelta, movementRotation, rotationSpeed * Time.deltaTime);
// Apply Z-axis rotation to shakeParent as local rotation
float clampedZ = Mathf.Clamp(_rotationDelta.x, -60f, 60f);
transform.eulerAngles = new Vector3(transform.eulerAngles.x, transform.eulerAngles.y, clampedZ);
shakeParent.localEulerAngles = new Vector3(0, 0, clampedZ);
}
protected virtual void UpdateTilt()
{
if (tiltParent == null)
return;
// Save slot index when not dragging for idle animation
_savedSlotIndex = _parentDraggable.IsDragging
? _savedSlotIndex
: _parentDraggable.GetSlotIndex();
// Idle animation (sine/cosine wobble)
// Idle animation (sine/cosine wobble with different frequencies)
float idleMultiplier = _parentDraggable.IsHovering ? 0.2f : 1f;
float time = Time.time * idleAnimationSpeed + _savedSlotIndex;
float sineWobble = Mathf.Sin(time) * idleMultiplier;
float cosineWobble = Mathf.Cos(time) * idleMultiplier;
// Use sine for X wobble, cosine for Y wobble (different phases)
float sineWobbleX = Mathf.Sin(time) * idleMultiplier;
float cosineWobbleY = Mathf.Cos(time) * idleMultiplier;
// Use slower cosine for Z rotation wobble (half speed for subtle rotation)
float cosineWobbleZ = Mathf.Cos(time * 0.5f) * idleMultiplier * 0.3f; // Much subtler
// Manual tilt based on pointer position (when hovering)
float manualTiltX = 0f;
float manualTiltY = 0f;
if (_parentDraggable.IsHovering && Camera.main != null)
if (_parentDraggable.IsHovering)
{
Vector3 mouseWorldPos = Camera.main.ScreenToWorldPoint(UnityEngine.Input.mousePosition);
Vector3 offset = transform.position - mouseWorldPos;
Canvas parentCanvas = _parentDraggable.GetComponentInParent<Canvas>();
bool isOverlay = parentCanvas != null && parentCanvas.renderMode == RenderMode.ScreenSpaceOverlay;
Vector3 mousePos;
Vector3 myPos;
// Get mouse position using new Input System
Vector2 mouseScreenPos = Mouse.current != null ? Mouse.current.position.ReadValue() : Vector2.zero;
if (isOverlay)
{
// For overlay, use screen coordinates directly
mousePos = mouseScreenPos;
myPos = GetComponent<RectTransform>() != null
? GetComponent<RectTransform>().position
: transform.position;
}
else if (Camera.main != null)
{
// For WorldSpace/ScreenSpaceCamera, convert to world coords
mousePos = Camera.main.ScreenToWorldPoint(mouseScreenPos);
myPos = transform.position;
}
else
{
mousePos = myPos = Vector3.zero;
}
Vector3 offset = myPos - mousePos;
manualTiltX = (offset.y * -1f) * manualTiltAmount;
manualTiltY = offset.x * manualTiltAmount;
}
// Combine auto and manual tilt
float targetTiltX = manualTiltX + (useIdleAnimation ? sineWobble * autoTiltAmount : 0f);
float targetTiltY = manualTiltY + (useIdleAnimation ? cosineWobble * autoTiltAmount : 0f);
float targetTiltZ = _parentDraggable.IsDragging ? tiltParent.eulerAngles.z : 0f;
// X uses sine wobble, Y uses cosine wobble, Z uses slower cosine for rotation
float targetTiltX = manualTiltX + (useIdleAnimation ? sineWobbleX * autoTiltAmount : 0f);
float targetTiltY = manualTiltY + (useIdleAnimation ? cosineWobbleY * autoTiltAmount : 0f);
float targetTiltZ = _parentDraggable.IsDragging ? tiltParent.localEulerAngles.z : (useIdleAnimation ? cosineWobbleZ * autoTiltAmount : 0f);
// Lerp to target tilt
float lerpX = Mathf.LerpAngle(tiltParent.eulerAngles.x, targetTiltX, tiltSpeed * Time.deltaTime);
float lerpY = Mathf.LerpAngle(tiltParent.eulerAngles.y, targetTiltY, tiltSpeed * Time.deltaTime);
float lerpZ = Mathf.LerpAngle(tiltParent.eulerAngles.z, targetTiltZ, (tiltSpeed / 2f) * Time.deltaTime);
// Lerp to target tilt using LOCAL rotation
float lerpX = Mathf.LerpAngle(tiltParent.localEulerAngles.x, targetTiltX, tiltSpeed * Time.deltaTime);
float lerpY = Mathf.LerpAngle(tiltParent.localEulerAngles.y, targetTiltY, tiltSpeed * Time.deltaTime);
float lerpZ = Mathf.LerpAngle(tiltParent.localEulerAngles.z, targetTiltZ, (tiltSpeed / 2f) * Time.deltaTime);
tiltParent.eulerAngles = new Vector3(lerpX, lerpY, lerpZ);
tiltParent.localEulerAngles = new Vector3(lerpX, lerpY, lerpZ);
}
#endregion
@@ -233,6 +354,21 @@ namespace UI.DragAndDrop.Core
canvas.overrideSorting = true;
}
// Reset shake parent rotation (movement wobble)
if (shakeParent != null)
{
Tween.LocalRotation(shakeParent, Quaternion.identity, 0.2f, 0f, Tween.EaseOutBack);
}
// Reset tilt parent rotation (idle wobble)
if (tiltParent != null)
{
Tween.LocalRotation(tiltParent, Quaternion.identity, 0.2f, 0f, Tween.EaseOutBack);
}
// Reset rotation delta for fresh movement wobble
_rotationDelta = Vector3.zero;
OnDragStartedVisual();
}
@@ -243,7 +379,24 @@ namespace UI.DragAndDrop.Core
canvas.overrideSorting = false;
}
Tween.LocalScale(transform, Vector3.one, scaleTransitionDuration, 0f, Tween.EaseOutBack);
// 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);
}
// Reset shake parent (movement wobble) to zero for fresh start
if (shakeParent != null)
{
Tween.LocalRotation(shakeParent, Quaternion.identity, 0.3f, 0f, Tween.EaseOutBack);
}
// Reset tilt parent (idle wobble) to zero for fresh start
if (tiltParent != null)
{
Tween.LocalRotation(tiltParent, Quaternion.identity, 0.3f, 0f, Tween.EaseOutBack);
}
OnDragEndedVisual();
}