268 lines
6.3 KiB
C#
268 lines
6.3 KiB
C#
|
|
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<Renderer>();
|
||
|
|
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>();
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|