Unity小功能——UGUI拖动到指定区域
功能包含:
一,限定拖动区域,
1,无限制
2,屏幕内
3,父物体内
二,拖动位置
1,鼠标到指定位置
2,拖动的物品到指定区域
三,吸附物品释放等功能
四,具体功能需要自行扩展
在以下内容中添加功能
public UnityEvent onDragEntry; //进入
public UnityEvent onDragExit; //离开
public UnityEvent onDragNotExit; //未离开
脚本
DragHandle.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
namespace MinorFunction
{
public enum DragHandleMode
{
Free, // 自由拖拽
WithinScreen, // 在屏幕内拖拽
WithinParent, // 在父对象内拖拽
}
[RequireComponent(typeof(CanvasGroup))]
public class DragHandle : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
public DragHandleMode dragMode = DragHandleMode.Free;
// 是否偏移
public bool isOffset = true;
// 是否位置重叠判定
public bool isOverlap = true;
// 拖动置顶层级
public bool isDragTop = false;
// 偏移向量
private Vector2 offset = Vector2.zero;
public UnityEvent onBeginDrag;// 开始拖动
public UnityEvent onDrag;// 拖动
public UnityEvent onEndDrag; // 拖动结束
[HideInInspector]
public SlotHandle currentSlotHandle;
public SlotHandle[] slotHandles;
public void OnBeginDrag(PointerEventData eventData)
{
slotHandles = Object.FindObjectsOfType<SlotHandle>();
offset = eventData.position - (Vector2)transform.position;
gameObject.GetComponent<CanvasGroup>().blocksRaycasts = false;
if (isDragTop)
{
transform.SetAsLastSibling();
}
onBeginDrag?.Invoke();
}
public void OnDrag(PointerEventData eventData)
{
switch (dragMode)
{
case DragHandleMode.Free:
SetFreeDrag(eventData);
break;
case DragHandleMode.WithinScreen:
SetDragWithinScreen(eventData);
break;
case DragHandleMode.WithinParent:
SetDragWithinParent(eventData);
break;
}
onDrag?.Invoke();
}
public void OnEndDrag(PointerEventData eventData)
{
gameObject.GetComponent<CanvasGroup>().blocksRaycasts = true;
if (isOverlap)
{
bool overlap = false;
int overlapIndex = 0;
for (int i = 0; i < slotHandles.Length; i++)
{
if (IsRectanglesOverlapping(GetComponent<RectTransform>(), slotHandles[i].GetComponent<RectTransform>()))
{
overlap = true;
overlapIndex = i;
break;
}
}
if (overlap)
{
//Debug.Log($"{gameObject.name} 有重叠 {slotHandles[overlapIndex].gameObject.name}");
slotHandles[overlapIndex].ModifyDrag(this);
}
else
{
if (currentSlotHandle)
{
RemoveSlotHandle();
currentSlotHandle = null;
}
//Debug.Log("无任何重叠");
}
}
else
{
if (currentSlotHandle)
{
if (!currentSlotHandle.isEnter)
{
RemoveSlotHandle();
currentSlotHandle = null;
}
}
}
onEndDrag?.Invoke();
}
/// <summary>
/// 移除SlotHandle
/// </summary>
public void RemoveSlotHandle()
{
currentSlotHandle.onDragExit?.Invoke(this);
currentSlotHandle.currentDragHandles.Remove(this);
currentSlotHandle = null;
}
#region 拖动模式
// 自由拖动
public void SetFreeDrag(PointerEventData eventData)
{
if (isOffset)
{
transform.position = eventData.position - offset;
}
else
{
transform.position = eventData.position;
}
}
// 设置屏幕内拖动
public void SetDragWithinScreen(PointerEventData eventData)
{
RectTransform rectTransform = transform as RectTransform;
Vector2 pos = Vector2.zero;
if (isOffset)
{
pos = eventData.position - offset;
}
else
{
pos = eventData.position;
}
// 屏幕范围
float parentMinX = 0;
float parentMinY = 0;
float parentMaxX = Screen.width;
float parentMaxY = Screen.height;
var pivotDistance = GetPivotDistance(rectTransform);
float pivotMinX = pivotDistance.pivotMinX;
float pivotMinY = pivotDistance.pivotMinY;
float pivotMaxX = pivotDistance.pivotMaxX;
float pivotMaxY = pivotDistance.pivotMaxY;
// 限制范围
float minX = parentMinX + pivotMinX;
float minY = parentMinY + pivotMinY;
float maxX = parentMaxX - pivotMaxX;
float maxY = parentMaxY - pivotMaxY;
// 限制拖动范围
pos.x = Mathf.Clamp(pos.x, minX, maxX);
pos.y = Mathf.Clamp(pos.y, minY, maxY);
transform.position = pos;
}
// 限制矩形内拖动
public void SetDragWithinParent(PointerEventData eventData)
{
RectTransform rectTransform = transform as RectTransform;
RectTransform parentRectTransform = transform.parent as RectTransform;
Vector2 pos = Vector2.zero;
if (isOffset)
{
pos = eventData.position - offset;
}
else
{
pos = eventData.position;
}
var parentMinMax = GetMinMax(parentRectTransform);
float parentMinX = parentMinMax.minX;
float parentMinY = parentMinMax.minY;
float parentMaxX = parentMinMax.maxX;
float parentMaxY = parentMinMax.maxY;
var pivotDistance = GetPivotDistance(rectTransform);
float pivotMinX = pivotDistance.pivotMinX;
float pivotMinY = pivotDistance.pivotMinY;
float pivotMaxX = pivotDistance.pivotMaxX;
float pivotMaxY = pivotDistance.pivotMaxY;
// 限制范围
float minX = parentMinX + pivotMinX;
float minY = parentMinY + pivotMinY;
float maxX = parentMaxX - pivotMaxX;
float maxY = parentMaxY - pivotMaxY;
// 限制拖动范围
pos.x = Mathf.Clamp(pos.x, minX, maxX);
pos.y = Mathf.Clamp(pos.y, minY, maxY);
transform.position = pos;
}
#endregion
#region 功能
// 计算物体中心点到四条边的距离
public (float pivotMinX, float pivotMinY, float pivotMaxX, float pivotMaxY) GetPivotDistance(RectTransform rectTransform)
{
float width = rectTransform.rect.width;
float height = rectTransform.rect.height;
// 注:这里一定要使用全局坐标,不然计算会受到父物体缩放的影响
float scaleX = rectTransform.lossyScale.x;
float scaleY = rectTransform.lossyScale.y;
Vector2 pivot = rectTransform.pivot;
float pivotMinX = width * pivot.x * scaleX;
float pivotMinY = height * pivot.y * scaleY;
float pivotMaxX = width * (1 - pivot.x) * scaleX;
float pivotMaxY = height * (1 - pivot.y) * scaleY;
return (pivotMinX, pivotMinY, pivotMaxX, pivotMaxY);
}
// 计算物体X,Y最大值最小值
public (float minX, float minY, float maxX, float maxY) GetMinMax(RectTransform rectTransform)
{
float parentX = rectTransform.position.x;
float parentY = rectTransform.position.y;
var pivotDistance = GetPivotDistance(rectTransform);
float pivotMinX = pivotDistance.pivotMinX;
float pivotMinY = pivotDistance.pivotMinY;
float pivotMaxX = pivotDistance.pivotMaxX;
float pivotMaxY = pivotDistance.pivotMaxY;
float minX = parentX - pivotMinX;
float minY = parentY - pivotMinY;
float maxX = parentX + pivotMaxX;
float maxY = parentY + pivotMaxY;
return (minX, minY, maxX, maxY);
}
public bool IsRectanglesOverlapping(RectTransform rect1, RectTransform rect2)
{
Vector2 rect1Min = rect1.anchoredPosition - (rect1.sizeDelta * rect1.pivot)*rect1.lossyScale;
Vector2 rect1Max = rect1.anchoredPosition + (rect1.sizeDelta * (Vector2.one-rect1.pivot)) * rect1.lossyScale;
Vector2 rect2Min = rect2.anchoredPosition - (rect2.sizeDelta * rect2.pivot) * rect2.lossyScale;
Vector2 rect2Max = rect2.anchoredPosition + (rect2.sizeDelta * (Vector2.one - rect2.pivot)) * rect2.lossyScale;
// 判断矩形 1 的最右边小于矩形 2 的最左边,或者矩形 2 的最右边小于矩形 1 的最左边,说明没有水平方向重叠
if (rect1Max.x < rect2Min.x || rect2Max.x < rect1Min.x || rect1Max.y < rect2Min.y || rect2Max.y < rect1Min.y)
{
return false;
}
return true;
}
#endregion
}
}
SlotHandle.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
namespace MinorFunction
{
public class SlotHandle : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IDropHandler
{
[HideInInspector]
public bool isEnter = false;
[Tooltip("multipleNumber == 0 不做任何限制可以附加无限多个,multipleNumber > 0 任何限制只能附加 multipleNumber个,multipleNumber = -1 不允许附加")]
public int multipleNumber = 0;
private DragHandle temporaryDragHandle;
public List<DragHandle> currentDragHandles = new List<DragHandle>();
public UnityEvent<DragHandle> onDragEntry; //进入
public UnityEvent<DragHandle> onDragExit; //离开
public UnityEvent<DragHandle> onDragNotExit; //未离开
public UnityEvent onPointerEnter; //鼠标进入
public UnityEvent onPointerExit; //鼠标离开
private void Awake()
{
SetMultipleNumber(multipleNumber);
}
public void OnPointerEnter(PointerEventData eventData)
{
isEnter = true;
onPointerEnter?.Invoke();
}
public void OnPointerExit(PointerEventData eventData)
{
isEnter = false;
onPointerExit?.Invoke();
}
public void OnDrop(PointerEventData eventData)
{
if (eventData.pointerDrag != null)
{
temporaryDragHandle = eventData.pointerDrag.GetComponent<DragHandle>();
if (temporaryDragHandle.isOverlap)
{
return;
}
ModifyDrag(temporaryDragHandle);
}
}
public void ModifyDrag(DragHandle dragHandle)
{
temporaryDragHandle = dragHandle;
if (!currentDragHandles.Contains(temporaryDragHandle))
{
if (multipleNumber == 0) //不做任何限制
{
RemoveDragHandle();
ShiftInDragHandle();
}
else if (multipleNumber > 0)
{
if (currentDragHandles.Count < multipleNumber)
{
RemoveDragHandle();
ShiftInDragHandle();
}
}
else if (multipleNumber < 0)
{
return;
}
}
else
{
onDragNotExit?.Invoke(temporaryDragHandle);
}
}
public void RemoveDragHandle()
{
if (temporaryDragHandle.currentSlotHandle)
{
temporaryDragHandle.RemoveSlotHandle();
}
}
public void ShiftInDragHandle()
{
temporaryDragHandle.currentSlotHandle = this;
currentDragHandles.Add(temporaryDragHandle);
onDragEntry?.Invoke(temporaryDragHandle);
}
/// <summary>
/// 设置多选数量
/// </summary>
/// <param name="number">number == 0 不做任何限制可以附加无限多个,number > 0 任何限制只能附加 number 个,number = -1 不允许附加</param>
public void SetMultipleNumber(int number)
{
if (number < 0)
{
multipleNumber = -1;
RemoveAllDragHandles();
}
else
{
multipleNumber = number;
if (number < currentDragHandles.Count)
{
for (int i = multipleNumber - 1; i < currentDragHandles.Count; i++)
{
temporaryDragHandle = currentDragHandles[i];
RemoveDragHandle();
}
}
}
}
/// <summary>
/// 移除全部DragHandle
/// </summary>
public void RemoveAllDragHandles()
{
for (int i = 0; i < currentDragHandles.Count; i++)
{
temporaryDragHandle = currentDragHandles[i];
RemoveDragHandle();
}
}
/// <summary>
/// 移除指定DragHandle
/// </summary>
/// <param name="index"></param>
public void RemoveIndexDragHandles(int index)
{
temporaryDragHandle = currentDragHandles[index];
RemoveDragHandle();
}
/// <summary>
/// 移除指定DragHandle
/// </summary>
/// <param name="dragHandle"></param>
public void RemoveIndexDragHandles(DragHandle dragHandle)
{
temporaryDragHandle = dragHandle;
RemoveDragHandle();
}
}
}
功能扩展案例
SlotHandleFunctionExpansion.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MinorFunction
{
public class SlotHandleFunctionExpansion : MonoBehaviour
{
public SlotHandle slotHandle;
void Start()
{
slotHandle = GetComponent<SlotHandle>();
AdsorptionFunction();
}
/// <summary>
/// 吸附功能
/// </summary>
public void AdsorptionFunction()
{
slotHandle.onDragEntry.AddListener(call => {
call.gameObject.transform.position = gameObject.transform.position;
Debug.Log($"{call.gameObject.name}进入了{gameObject.name}");
});
slotHandle.onDragExit.AddListener(call => {
Debug.Log($"{call.gameObject.name}离开了{gameObject.name}");
});
slotHandle.onDragNotExit.AddListener(call => {
call.gameObject.transform.position = gameObject.transform.position;
Debug.Log($"{call.gameObject.name}未离开{gameObject.name}");
});
}
}
}