Working visual part
This commit is contained in:
@@ -12,8 +12,8 @@ namespace UI.DragAndDrop.Core
|
||||
/// Handles drag logic, slot snapping, and events.
|
||||
/// Spawns and manages a separate DraggableVisual for rendering.
|
||||
/// Touch-compatible via Unity's pointer event system.
|
||||
/// Note: Optionally uses Image or CanvasGroup for automatic raycast toggling during drag.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Image))]
|
||||
public abstract class DraggableObject : MonoBehaviour,
|
||||
IBeginDragHandler, IDragHandler, IEndDragHandler,
|
||||
IPointerEnterHandler, IPointerExitHandler,
|
||||
@@ -25,9 +25,7 @@ namespace UI.DragAndDrop.Core
|
||||
[SerializeField] protected float snapDuration = 0.3f;
|
||||
|
||||
[Header("Visual")]
|
||||
[SerializeField] protected GameObject visualPrefab;
|
||||
[SerializeField] protected bool instantiateVisual = true;
|
||||
[SerializeField] protected Transform visualParent;
|
||||
[SerializeField] protected DraggableVisual visual;
|
||||
|
||||
[Header("Selection")]
|
||||
[SerializeField] protected bool isSelectable = true;
|
||||
@@ -42,6 +40,7 @@ namespace UI.DragAndDrop.Core
|
||||
// References
|
||||
protected Canvas _canvas;
|
||||
protected Image _imageComponent;
|
||||
protected CanvasGroup _canvasGroup;
|
||||
protected GraphicRaycaster _raycaster;
|
||||
protected DraggableSlot _currentSlot;
|
||||
protected DraggableVisual _visualInstance;
|
||||
@@ -72,20 +71,36 @@ namespace UI.DragAndDrop.Core
|
||||
public Vector3 WorldPosition => transform.position;
|
||||
public RectTransform RectTransform => transform as RectTransform;
|
||||
|
||||
protected virtual void Start()
|
||||
protected virtual void Awake()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
protected virtual void Initialize()
|
||||
{
|
||||
Debug.Log($"[DraggableObject] Initializing {name} at world pos {transform.position}, local pos {transform.localPosition}, parent: {(transform.parent != null ? transform.parent.name : "NULL")}");
|
||||
|
||||
_canvas = GetComponentInParent<Canvas>();
|
||||
Debug.Log($"[DraggableObject] {name} found canvas: {(_canvas != null ? _canvas.name : "NULL")}, canvas pos: {(_canvas != null ? _canvas.transform.position.ToString() : "N/A")}");
|
||||
|
||||
_imageComponent = GetComponent<Image>();
|
||||
_canvasGroup = GetComponent<CanvasGroup>();
|
||||
_raycaster = _canvas?.GetComponent<GraphicRaycaster>();
|
||||
|
||||
if (instantiateVisual && visualPrefab != null)
|
||||
// Use assigned visual, or find in children recursively if not assigned
|
||||
if (visual != null)
|
||||
{
|
||||
SpawnVisual();
|
||||
_visualInstance = visual;
|
||||
}
|
||||
else
|
||||
{
|
||||
_visualInstance = GetComponentInChildren<DraggableVisual>(true);
|
||||
}
|
||||
|
||||
// Initialize the visual if found
|
||||
if (_visualInstance != null)
|
||||
{
|
||||
_visualInstance.Initialize(this);
|
||||
}
|
||||
|
||||
// If we're already in a slot, register with it
|
||||
@@ -96,18 +111,6 @@ namespace UI.DragAndDrop.Core
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void SpawnVisual()
|
||||
{
|
||||
Transform parent = visualParent != null ? visualParent : _canvas.transform;
|
||||
GameObject visualObj = Instantiate(visualPrefab, parent);
|
||||
_visualInstance = visualObj.GetComponent<DraggableVisual>();
|
||||
|
||||
if (_visualInstance != null)
|
||||
{
|
||||
_visualInstance.Initialize(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
if (_isDragging && smoothMovement)
|
||||
@@ -130,6 +133,10 @@ namespace UI.DragAndDrop.Core
|
||||
|
||||
protected virtual void ClampToScreen()
|
||||
{
|
||||
// Skip clamping for ScreenSpaceOverlay - it doesn't use world coordinates
|
||||
if (_canvas != null && _canvas.renderMode == RenderMode.ScreenSpaceOverlay)
|
||||
return;
|
||||
|
||||
if (Camera.main == null || RectTransform == null)
|
||||
return;
|
||||
|
||||
@@ -166,6 +173,8 @@ namespace UI.DragAndDrop.Core
|
||||
_raycaster.enabled = false;
|
||||
if (_imageComponent != null)
|
||||
_imageComponent.raycastTarget = false;
|
||||
if (_canvasGroup != null)
|
||||
_canvasGroup.blocksRaycasts = false;
|
||||
|
||||
// Notify current slot we're leaving
|
||||
if (_currentSlot != null)
|
||||
@@ -202,6 +211,8 @@ namespace UI.DragAndDrop.Core
|
||||
_raycaster.enabled = true;
|
||||
if (_imageComponent != null)
|
||||
_imageComponent.raycastTarget = true;
|
||||
if (_canvasGroup != null)
|
||||
_canvasGroup.blocksRaycasts = true;
|
||||
|
||||
// Find closest slot and snap
|
||||
FindAndSnapToSlot();
|
||||
@@ -323,6 +334,8 @@ namespace UI.DragAndDrop.Core
|
||||
if (slot == null)
|
||||
return;
|
||||
|
||||
Debug.Log($"[DraggableObject] Assigning {name} to slot {slot.name}, animate={animate}, current pos={transform.position}, slot pos={slot.transform.position}");
|
||||
|
||||
DraggableSlot previousSlot = _currentSlot;
|
||||
_currentSlot = slot;
|
||||
|
||||
@@ -336,6 +349,8 @@ namespace UI.DragAndDrop.Core
|
||||
{
|
||||
transform.SetParent(slot.transform);
|
||||
transform.localPosition = _isSelected ? new Vector3(0, selectionOffset, 0) : Vector3.zero;
|
||||
transform.localRotation = Quaternion.identity;
|
||||
Debug.Log($"[DraggableObject] {name} assigned to slot {slot.name}, new world pos={transform.position}, local pos={transform.localPosition}");
|
||||
}
|
||||
|
||||
OnSlotChanged?.Invoke(this, slot);
|
||||
@@ -352,10 +367,12 @@ namespace UI.DragAndDrop.Core
|
||||
if (RectTransform != null)
|
||||
{
|
||||
Tween.LocalPosition(RectTransform, targetLocalPos, snapDuration, 0f, Tween.EaseOutBack);
|
||||
Tween.LocalRotation(transform, Quaternion.identity, snapDuration, 0f, Tween.EaseOutBack);
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.localPosition = targetLocalPos;
|
||||
transform.localRotation = Quaternion.identity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,16 +10,24 @@ namespace UI.DragAndDrop.Core
|
||||
/// </summary>
|
||||
public class DraggableSlot : MonoBehaviour
|
||||
{
|
||||
public enum OccupantSizeMode
|
||||
{
|
||||
None, // Don't modify occupant size
|
||||
MatchSlotSize, // Set occupant RectTransform size to match slot size
|
||||
Scale // Apply scale multiplier to occupant
|
||||
}
|
||||
|
||||
[Header("Slot Settings")]
|
||||
[SerializeField] private int slotIndex;
|
||||
[SerializeField] private bool isLocked;
|
||||
[SerializeField] private bool hideImageOnPlay = false;
|
||||
|
||||
[Header("Type Filtering")]
|
||||
[SerializeField] private bool filterByType;
|
||||
[SerializeField] private string[] allowedTypeNames;
|
||||
|
||||
[Header("Scale Control")]
|
||||
[SerializeField] private bool applyScaleToOccupant = false;
|
||||
[Header("Occupant Size Control")]
|
||||
[SerializeField] private OccupantSizeMode occupantSizeMode = OccupantSizeMode.None;
|
||||
[SerializeField] private Vector3 occupantScale = Vector3.one;
|
||||
[SerializeField] private float scaleTransitionDuration = 0.3f;
|
||||
|
||||
@@ -37,6 +45,18 @@ namespace UI.DragAndDrop.Core
|
||||
public Vector3 WorldPosition => transform.position;
|
||||
public RectTransform RectTransform => transform as RectTransform;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (hideImageOnPlay)
|
||||
{
|
||||
UnityEngine.UI.Image image = GetComponent<UnityEngine.UI.Image>();
|
||||
if (image != null)
|
||||
{
|
||||
Destroy(image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to occupy this slot with a draggable object
|
||||
/// </summary>
|
||||
@@ -54,10 +74,27 @@ namespace UI.DragAndDrop.Core
|
||||
_occupant = draggable;
|
||||
draggable.transform.SetParent(transform);
|
||||
|
||||
// Apply scale if configured
|
||||
if (applyScaleToOccupant)
|
||||
// Apply size modification based on mode
|
||||
switch (occupantSizeMode)
|
||||
{
|
||||
Tween.LocalScale(draggable.transform, occupantScale, scaleTransitionDuration, 0f, Tween.EaseOutBack);
|
||||
case OccupantSizeMode.MatchSlotSize:
|
||||
if (draggable.RectTransform != null && RectTransform != null)
|
||||
{
|
||||
Vector2 targetSize = RectTransform.sizeDelta;
|
||||
Tween.Value(draggable.RectTransform.sizeDelta, targetSize,
|
||||
(val) => draggable.RectTransform.sizeDelta = val,
|
||||
scaleTransitionDuration, 0f, Tween.EaseOutBack);
|
||||
}
|
||||
break;
|
||||
|
||||
case OccupantSizeMode.Scale:
|
||||
Tween.LocalScale(draggable.transform, occupantScale, scaleTransitionDuration, 0f, Tween.EaseOutBack);
|
||||
break;
|
||||
|
||||
case OccupantSizeMode.None:
|
||||
default:
|
||||
// Don't modify size
|
||||
break;
|
||||
}
|
||||
|
||||
OnOccupied?.Invoke(draggable);
|
||||
|
||||
@@ -56,11 +56,12 @@ namespace UI.DragAndDrop.Core
|
||||
{
|
||||
_parentDraggable = parent;
|
||||
|
||||
// Get or add required components
|
||||
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")}");
|
||||
|
||||
// Get components if assigned (don't auto-create Canvas to avoid Unity's auto-reparenting)
|
||||
if (canvas == null)
|
||||
canvas = GetComponent<Canvas>();
|
||||
if (canvas == null)
|
||||
canvas = gameObject.AddComponent<Canvas>();
|
||||
|
||||
if (canvasGroup == null)
|
||||
canvasGroup = GetComponent<CanvasGroup>();
|
||||
@@ -74,6 +75,8 @@ namespace UI.DragAndDrop.Core
|
||||
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}");
|
||||
|
||||
_isInitialized = true;
|
||||
|
||||
OnInitialized();
|
||||
@@ -127,6 +130,12 @@ namespace UI.DragAndDrop.Core
|
||||
|
||||
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}");
|
||||
}
|
||||
|
||||
if (useFollowDelay)
|
||||
{
|
||||
transform.position = Vector3.Lerp(transform.position, targetPosition, followSpeed * Time.deltaTime);
|
||||
|
||||
3
Assets/Scripts/UI/DragAndDrop/Editor.meta
Normal file
3
Assets/Scripts/UI/DragAndDrop/Editor.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b9473a4ece8425d97676fe7630d25ac
|
||||
timeCreated: 1762428015
|
||||
@@ -0,0 +1,95 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UI.DragAndDrop.Core;
|
||||
|
||||
namespace UI.DragAndDrop.Editor
|
||||
{
|
||||
[CustomEditor(typeof(DraggableObject), true)]
|
||||
public class DraggableObjectEditor : UnityEditor.Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
|
||||
DraggableObject draggable = (DraggableObject)target;
|
||||
|
||||
// Only show button in edit mode
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Editor Tools", EditorStyles.boldLabel);
|
||||
|
||||
if (GUILayout.Button("Snap to Parent Slot"))
|
||||
{
|
||||
SnapToParentSlot(draggable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SnapToParentSlot(DraggableObject draggable)
|
||||
{
|
||||
// Find parent slot
|
||||
DraggableSlot parentSlot = draggable.GetComponentInParent<DraggableSlot>();
|
||||
|
||||
if (parentSlot == null)
|
||||
{
|
||||
Debug.LogWarning("No parent DraggableSlot found!");
|
||||
return;
|
||||
}
|
||||
|
||||
Undo.RecordObject(draggable.transform, "Snap to Parent Slot");
|
||||
|
||||
// Reset position and rotation
|
||||
draggable.transform.localPosition = Vector3.zero;
|
||||
draggable.transform.localRotation = Quaternion.identity;
|
||||
|
||||
// Apply slot's size mode
|
||||
RectTransform draggableRect = draggable.GetComponent<RectTransform>();
|
||||
RectTransform slotRect = parentSlot.GetComponent<RectTransform>();
|
||||
|
||||
if (draggableRect != null && slotRect != null)
|
||||
{
|
||||
// Use reflection to access private fields
|
||||
System.Reflection.FieldInfo modeField = typeof(DraggableSlot).GetField("occupantSizeMode",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
System.Reflection.FieldInfo scaleField = typeof(DraggableSlot).GetField("occupantScale",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
|
||||
if (modeField != null && scaleField != null)
|
||||
{
|
||||
var sizeMode = modeField.GetValue(parentSlot);
|
||||
var occupantScale = (Vector3)scaleField.GetValue(parentSlot);
|
||||
|
||||
// Get enum type
|
||||
System.Type enumType = sizeMode.GetType();
|
||||
string modeName = System.Enum.GetName(enumType, sizeMode);
|
||||
|
||||
Undo.RecordObject(draggableRect, "Apply Slot Size Mode");
|
||||
|
||||
switch (modeName)
|
||||
{
|
||||
case "MatchSlotSize":
|
||||
draggableRect.sizeDelta = slotRect.sizeDelta;
|
||||
draggableRect.localScale = Vector3.one;
|
||||
Debug.Log($"Matched slot size: {slotRect.sizeDelta}");
|
||||
break;
|
||||
|
||||
case "Scale":
|
||||
draggableRect.localScale = occupantScale;
|
||||
Debug.Log($"Applied scale: {occupantScale}");
|
||||
break;
|
||||
|
||||
case "None":
|
||||
default:
|
||||
// Keep current size
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorUtility.SetDirty(draggable.gameObject);
|
||||
Debug.Log($"Snapped {draggable.name} to parent slot {parentSlot.name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 290619d598ef4b199862482abc7188a3
|
||||
timeCreated: 1762428015
|
||||
Reference in New Issue
Block a user