using UnityEngine; [RequireComponent(typeof(Collider))] public class DraggableObject : MonoBehaviour { [Header("Item")] public ItemType itemType; public Material normalMaterial; public Material highlightMaterial; public Material dragMaterial; public Material inactiveMaterial; public Vector3 startPosition; public bool isLocked; [Header("Drag")] public float dragLift = 0.35f; public float snapDistance = 1.25f; public bool isInteractable = true; private Camera mainCamera; private Renderer itemRenderer; private Plane dragPlane; private Vector3 dragOffset; private Vector3 returnPosition; private Slot highlightedSlot; private bool isDragging; private bool isMouseOver; private void Awake() { mainCamera = Camera.main; itemRenderer = GetComponent(); startPosition = transform.position; returnPosition = startPosition; if (itemRenderer != null && normalMaterial == null) { normalMaterial = itemRenderer.sharedMaterial; } } private void Start() { ApplyNormalMaterial(); } private void OnMouseEnter() { if (!CanInteract()) { return; } isMouseOver = true; ApplyMaterial(highlightMaterial); } private void OnMouseExit() { isMouseOver = false; if (!isDragging && !isLocked) { ApplyNormalMaterial(); } } private void OnMouseDown() { if (!CanInteract()) { return; } if (mainCamera == null) { mainCamera = Camera.main; } // Запоминаем позицию перед перетаскиванием, чтобы вернуть предмет при ошибке. returnPosition = transform.position; dragPlane = new Plane(Vector3.up, new Vector3(0f, startPosition.y, 0f)); if (TryGetMousePointOnDragPlane(out Vector3 mouseWorldPoint)) { dragOffset = transform.position - mouseWorldPoint; dragOffset.y = 0f; } else { dragOffset = Vector3.zero; } isDragging = true; ApplyMaterial(dragMaterial != null ? dragMaterial : highlightMaterial); } private void OnMouseDrag() { if (!isDragging || !CanInteract()) { return; } if (!TryGetMousePointOnDragPlane(out Vector3 mouseWorldPoint)) { return; } // Offset сохраняет относительное положение курсора и предмета без резких скачков. Vector3 targetPosition = mouseWorldPoint + dragOffset; targetPosition.y = startPosition.y + dragLift; transform.position = targetPosition; UpdateSlotPreview(); } private void OnMouseUp() { if (!isDragging) { return; } isDragging = false; // Предмет фиксируется только в ближайшем подходящем слоте. Slot nearestSlot = FindNearestSlot(); ClearHighlightedSlot(); if (nearestSlot != null && nearestSlot.CanAccept(this)) { nearestSlot.PlaceItem(this); return; } transform.position = returnPosition; ApplyNormalMaterial(); } public void LockToSlot(Vector3 slotPosition) { isLocked = true; isInteractable = false; transform.position = slotPosition; ApplyNormalMaterial(); } public void SetInteractable(bool value) { if (isLocked) { isInteractable = false; return; } isInteractable = value; ApplyNormalMaterial(); } public void SetNormalMaterial(Material material) { normalMaterial = material; ApplyNormalMaterial(); } private bool CanInteract() { return isInteractable && !isLocked; } private bool TryGetMousePointOnDragPlane(out Vector3 worldPoint) { worldPoint = Vector3.zero; if (mainCamera == null) { return false; } // Raycast в математическую плоскость делает drag стабильным для ортографической камеры. Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition); if (!dragPlane.Raycast(ray, out float distance)) { return false; } worldPoint = ray.GetPoint(distance); return true; } private void UpdateSlotPreview() { Slot nearestSlot = FindNearestSlot(); if (nearestSlot == highlightedSlot) { return; } ClearHighlightedSlot(); highlightedSlot = nearestSlot; if (highlightedSlot != null) { highlightedSlot.ShowPreview(this); } } private Slot FindNearestSlot() { Slot[] slots = FindObjectsOfType(); Slot nearest = null; float bestDistance = snapDistance; foreach (Slot slot in slots) { float distance = Vector3.Distance(transform.position, slot.transform.position); if (distance <= bestDistance) { bestDistance = distance; nearest = slot; } } return nearest; } private void ClearHighlightedSlot() { if (highlightedSlot != null) { highlightedSlot.ClearPreview(); highlightedSlot = null; } } private void ApplyNormalMaterial() { if (isLocked) { ApplyMaterial(normalMaterial); return; } if (isMouseOver && CanInteract()) { ApplyMaterial(highlightMaterial); return; } if (!isInteractable && inactiveMaterial != null) { ApplyMaterial(inactiveMaterial); return; } ApplyMaterial(normalMaterial); } private void ApplyMaterial(Material material) { if (itemRenderer != null && material != null) { itemRenderer.sharedMaterial = material; } } }