Unity3D UI 拖拽

Unity3D 实现 UI 元素拖拽功能。

UI 拖拽

通常画布上的 UI 元素都是固定位置的,我们可以通过实现拖拽接口,让 UI 元素可以被拖拽到其他位置。

拖拽接口

创建一个脚本 UIDrag.cs,在默认继承的 MonoBehaviour 后面,再继承三个接口。

  • IBeginDragHandler(开始拖拽)
  • IDragHandler(拖拽中)
  • IEndDragHandler(结束拖拽)

继承接口之后,要在脚本中实现接口中定义的方法,即 OnBeginDragOnDragOnEndDrag

using UnityEngine;
using UnityEngine.EventSystems;

public class UIDrag : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    public void OnBeginDrag(PointerEventData eventData)
    {
        
    }

    public void OnDrag(PointerEventData eventData)
    {
        transform.position = eventData.position;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        
    }
}

这里先在 OnDrag 方法中,把 eventData.position 赋值给 transform.position

然后创建一个 Image,把 UIDrag 脚本拖拽到 Image 上。

挂载组件

运行游戏,点击拖拽图片。

拖拽效果

画布渲染模式

上述的拖拽实现,是基于画布的 Overlay 模式。

Overlay模式

如果把画布渲染模式改成 Camera 模式,上述的代码实现就会出现问题。

因为 Camera 模式的画布会被缩小到相机的视野范围内,坐标的数值会变得很小。

Camera模式

此时的运行效果,拖拽后图片飞到了离画布很远的位置,坐标错误。

坐标错误

所以我们需要对拖拽时获得的坐标位置进行转换。

坐标转换

首先,定义一个 RectTransform 变量,在 Awake 时进行赋值。

然后利用 RectTransformUtility.ScreenPointToWorldPointInRectangle 方法,把自身的 recteventData.positioneventData.pressEventCamera 传入,如果坐标转换正常,则会返回转换后的 worldPoint,把这个坐标赋值给 rect.position 就可以了。

这个方法可以把屏幕空间坐标转换成 UI 元素所在的世界坐标。

using UnityEngine;
using UnityEngine.EventSystems;

public class UIDrag : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    RectTransform rect;

    void Awake()
    {
        rect = GetComponent<RectTransform>();
    }

    public void OnBeginDrag(PointerEventData eventData)
    {

    }

    public void OnDrag(PointerEventData eventData)
    {
        if (RectTransformUtility.ScreenPointToWorldPointInRectangle(rect, eventData.position,
        eventData.pressEventCamera, out Vector3 worldPoint))
        {
            rect.position = worldPoint;
        }
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        
    }
}

修改代码后,无论画布是 Overlay 还是 Camera 模式,都可以正常拖拽。

切换模式

开始与结束拖拽

单纯的拖拽,只需要 OnDrag 方法,而 OnBeginDragOnEndDrag 可以为拖拽操作添加更多的逻辑处理。

例如,在场景中添加两个 Image,修改名字,改变颜色和不透明度,放置到左右两边。

添加容器

然后在代码中添加更多的逻辑,添加一个 raycastResults 用于保存射线检测到的 UI 元素列表,通过 EventSystem.current.RaycastAll 方法,把鼠标经过的 UI 元素都添加到列表中(包含 Image 自己)。

如果鼠标经过的 UI 元素的名字包含 Container,就把目标元素赋值给 target;如果列表中只有 Image 自己,则 target 为空。

我们可以在 OnBeginDrag 中,把 Image 原来的位置保存起来。在 OnEndDrag 中,检查 target 为空时,把原来的位置赋值给 Image,回到原位。只有在 target 不为空时,Image 才会移动到目标容器位置。

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class UIDrag : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    RectTransform rect;

    List<RaycastResult> raycastResults;
    GameObject target;
    Vector3 originPos;

    void Awake()
    {
        rect = GetComponent<RectTransform>();

        raycastResults = new List<RaycastResult>();
    }

    public void OnBeginDrag(PointerEventData eventData)
    {
        originPos = transform.position;
    }

    public void OnDrag(PointerEventData eventData)
    {
        if (RectTransformUtility.ScreenPointToWorldPointInRectangle(rect, eventData.position,
        eventData.pressEventCamera, out Vector3 worldPoint))
        {
            rect.position = worldPoint;
        }

        // 清空上一次的射线检测结果
        raycastResults.Clear();

        // 进行射线检测
        EventSystem.current.RaycastAll(eventData, raycastResults);
        
        // 包含两个 UI 元素以上
        if (raycastResults.Count > 1)
        {
            // 遍历检测结果
            foreach (RaycastResult result in raycastResults)
            {
                // 跳过自身对象的检测
                if (result.gameObject == gameObject) continue;

                // 检测到目标容器
                if (result.gameObject.name.Contains("Container"))
                {
                    target = result.gameObject;
                }
            }
        }
        // 只包含自己,没有目标
        else
        {
            target = null;
        }
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        if (target == null)
        {
            transform.position = originPos;
        }
        else
        {
            transform.position = target.transform.position;
        }
    }
}

运行效果:

拖到容器

### 如何在 Unity 中使用拖拽接口实现 Drag and Drop 功能 为了实现在 Unity 中的拖拽功能,可以采用 UGUI 系统中的事件处理接口来完成这一操作。具体来说,在物品类中应当继承 `UnityEngine.EventSystems` 命名空间下的五个主要接口:`IBeginDragHandler`, `IDragHandler`, `IEndDragHandler`, `IPointerDownHandler`, 和 `IPointerUpHandler`[^2]。 #### 创建可拖动对象脚本 创建一个新的 C# 脚本来定义这些行为: ```csharp using UnityEngine; using UnityEngine.EventSystems; public class DraggableItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IPointerDownHandler, IPointerUpHandler { private RectTransform rectTransform; void Start() { rectTransform = GetComponent<RectTransform>(); } public void OnBeginDrag(PointerEventData eventData) { // 开始拖拽时的操作 Debug.Log("Start dragging"); } public void OnDrag(PointerEventData eventData) { // 正在拖拽期间执行的动作 rectTransform.anchoredPosition += eventData.delta; } public void OnEndDrag(PointerEventData eventData) { // 结束拖拽后的动作 Debug.Log("Stop dragging"); } public void OnPointerDown(PointerEventData eventData) { // 鼠标按下时触发 Debug.Log("Mouse down on item"); } public void OnPointerUp(PointerEventData eventData) { // 鼠标松开时触发 Debug.Log("Mouse up from item"); } } ``` 此代码片段展示了如何通过监听不同阶段的鼠标交互事件来控制 UI 元素的位置变化,从而模拟出真实的拖放体验。 对于更复杂的逻辑需求,比如当被拖动物品放置到特定区域时发生某些反应,则可以在 `OnEndDrag()` 方法内部加入额外判断条件并调用相应的方法来进行处理。 另外值得注意的是,如果是在自定义编辑器内开发工具插件的话,还可以考虑利用 `UnityEditor.DragAndDrop` 类提供的 API 来增强 Hierarchy 或 Inspector 窗口的功能性[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值