【unity】几种常用的拖拽方法(内置方法 + 接口 + Event Trigger组件)

前言

在Unity中实现拖拽的方法有多种,以下是几种常见的方法和它们的优缺点

1. 鼠标按键的点击事件

Input.GetMouseButtonDown和Input.GetMouseButtonUp 方法可以监测用户鼠标按键的点击事件,通过检测鼠标按钮的状态来实现拖拽效果。用户通过鼠标进行拖拽操作。

1.1 优点:

  • 简单易懂,是最通用的实现拖拽方法之一。
  • 适用于所有平台,包括PC和移动设备。

1.2 缺点:

  • 拖拽细节(如拖拽的加速度、加速度的方向等)难以控制。
  • 如果需要控制多个物体的拖拽行为,则需要编写大量的代码逻辑。

2. OnMouseDrag

OnMouseDrag 方法是 Unity 内建的一个组件事件,用于处理鼠标拖拽事件,可以通过在物体上加上事件脚本来实现拖拽。

2.1 优点:

  • 简单明了,易于使用。
  • 对于简单的拖拽需求,非常适用。

2.2 缺点:

  • 只能用于PC平台或者Web平台。
  • 不支持多点触摸和移动设备上的触摸操作。

3. Event Trigger 中的 BeginDrag、OnDrag 和 EndDrag

Event Trigger 是 Unity 中常用的 GUI 事件框架,通过监听不同的事件类型实现拖拽功能,包括 BeginDrag、OnDrag 和 EndDrag 事件。

BeginDrag 事件:用户开始拖拽一个物体时触发该事件;

OnDrag 事件:在拖拽物体时持续调用该事件,可以实现拖拽过程中的反馈等功能;

EndDrag 事件:在用户释放物体时触发该事件,可以在此处理放置、执行等操作。

3.1 优点:

  • 支持多点触摸和移动设备上的触摸操作。
  • 比较容易控制拖拽的操作流程,如速度、拖拽范围等。
  • 可以实现更多基于 GUI 的拖拽效果。

3.2 缺点:

  • 对于非 GUI 元素的拖拽,需要额外的逻辑实现。
  • 开销比较大。

4. 接口实现的 OnBeginDrag、OnDrag 和 OnEndDrag

该方法需要继承 UnityEngine.EventSystems.IDragHandler 接口并实现接口中的方法,从而接收该界面上的物体的拖拽操作。

4.1 优点:

  • 支持多点触摸和移动设备上的触摸操作。
  • 对于非 GUI 元素的拖拽,也很容易实现。

4.2 缺点:

  • 开销较大。
  • 需要手动实现接口中的方法。

综上所述,以上几种实现拖拽效果的方法各有优缺点,需要针对实际需求来选择使用。如果需要快速实现拖拽效果,可以使用
Input.GetMouseButtonDown 和 OnMouseDrag。如果要实现更多的拖拽事件,可以使用 Event Trigger
或者接口实现。

实例

还不懂的也没关系,下面我会举一下实例,更深入的了解他们的用法

1. 鼠标按键的点击事件

新建场景
在这里插入图片描述
我们要做的就是:当游戏运行后,通过鼠标的点击、拖拽、松开等操作,能够自由地将右边的这些人物的零部件,自定义(拖拽)到我们左边的这个人物的外貌上

扩展问题:我们如何让我们处于世界坐标系的2D图片,跟随着处于屏幕坐标系的鼠标一起移动呢?

我们只要将鼠标的屏幕坐标系信息转换为世界坐标系就可以了,可以通过Camera.Main.ScreenToWorldPoint 方法将Screen屏幕坐标系To转换为WorldPoint世界坐标系上的每一个像素点(坐标)

using UnityEngine;

public class Drag2DSprite : MonoBehaviour
{
    
    
    [SerializeField] private bool isSelected; // 是否被选中
    private void Update()
    {
    
    
        if (isSelected)
        {
    
    
            Vector2 cursorPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            transform.position = new Vector2(cursorPos.x, cursorPos.y);
        }
    }
    private void OnMouseOver()
    {
    
    
        if (Input.GetMouseButtonDown(0))
            isSelected = true; // 被选中
        if (Input.GetMouseButtonUp(0))
            isSelected = false; // 取消选中
    }
}

2. 拖拽内置方法实现

其中就有一个叫做【OnMouseDrag】方法,它可以更方便的可以实现这个案例当中的2D贴图的拖拽

注意:如果我们想使用OnMouseDrag、OnMouseOver、OnMouseEnter、On MouseExit、OnMouseUp等方法,这个对象必须含有Collider组件,之后才能被这些方法所调用

2.1 OnMouseEnter、OnMouseExit例子

还是上面的案例,我们可以通过【OnMouseDrag】来实现,当我们的鼠标进入、或者离开2D贴图时,可以增加相应的放大、缩小功能,来增加一些交互的体验感

private void OnMouseDrag() // 当鼠标拖动时
{
    
    
    Vector2 cursorPos = Camera.main.ScreenToWorldPoint(Input.mousePosition); // 将鼠标位置转换为世界坐标
    transform.position = new Vector2(cursorPos.x,cursorPos.y); // 将物体位置设置为鼠标位置
}

private void OnMouseEnter() // 当鼠标进入时
{
    
    
    transform.localScale += Vector3.one * 0.07f; // 增加物体的缩放大小
}

private void OnMouseExit() // 当鼠标离开时
{
    
    
    transform.LocalScale -= Vector3.one * 0.07f; // 减小物体的缩放大小
}

2.2 OnMouseUp例子

在这里插入图片描述

我们要做的就是,将下方的三张图通过拖拽来进行正确的匹配,我们首先选中可拖拽的这三张图片,因为由于我们之后会用到OnMouseDrag等方法,我们首先去添加BoxCollider2D组件

代码实现

private Vector2 startPos; // 起始位置
[SerializeField] private Transform correctTrans; // 黑色图像
[SerializeField] private bool isFinished; // 是否完成
private void Start()
{
    
    
    startPos = transform.position; // 记录起始位置
}
private void OnMouseDrag()
{
    
    
    if (!isFinished) // 如果还未完成
    {
    
    
        transform.position = new Vector2(Camera.main.ScreenToWorldPoint(Input.mousePosition).x,
            Camera.main.ScreenToWorldPoint(Input.mousePosition).y); // 鼠标拖拽时移动物体
    }
}
private void OnMouseUp()
{
    
    
    if (Mathf.Abs(transform.position.x - correctTrans.position.x) <= 0.5f &&
        Mathf.Abs(transform.position.y - correctTrans.position.y) <= 0.5f) // 如果移动到了正确位置
    {
    
    
        transform.position = new Vector2(correctTrans.position.x, correctTrans.position.y); // 将物体移动到正确位置
        isFinished = true; // 标记为已完成
    }
    else // 如果没有移动到正确位置
    {
    
    
        transform.position = new Vector2(startPos.x, startPos.y); // 将物体移回起始位置
    }
}

结果
在这里插入图片描述

UI拖拽方法

我们刚才说了OnMouseDrag(等拖拽内置方法),一般适用于2D图片贴图和3D场景当中,如果遇到UI图片呢,是不会去使用OnMouseDrag等方法的

Event Trigger组件

新建ui场景
在这里插入图片描述
通过添加Event Trigger组件来实现

在这里插入图片描述
按下【Add New Event Type】添加新的事件类型,根据游戏中不同的事件类型,来实现不同的交互效果,不同的事件类型,包括了鼠标指针的进入、离开、按下、松开、点击,还有我们将会去使用到的Drag拖拽
还有我们拖拽结束后的EndDrag事件
在这里插入图片描述
书写代码方法

private Vector3 startPos; // 初始位置

private void Start()
{
    
    
    startPos = transform.position; // 记录初始位置
}
public void DragMethod()
{
    
    
    transform.position = Input.mousePosition; // 将物体位置设置为鼠标位置
}
public void EndDragMethod()
{
    
    
    Gameobject slotGO = Gameobject.Find("SLot"); // 查找名为 "SLot" 的物体
    float dist = Vector3.Distance(transform.position, slotGO.transform.position); // 计算物体与 "SLot" 之间的距离
    if (dist <= 100)
        transform.position = slotGO.transform.position; // 如果距离小于等于 100,则将物体位置设置为 "SLot" 的位置
    else
        transform.position = startPos; // 否则将物体位置设置为初始位置
}

在刚才的Event Trigger上挂载对应的方法
在这里插入图片描述
运行效果
在这里插入图片描述

2. 疑问

这时候有人可能会有疑问了,前面不是说鼠标的位置信息是屏幕坐标系,而我们现在图片是UI图片并不是在同一坐标系,为什么我们可以用等号直接来写呢?
如果我们的UI模式是Screenspace Overlay模式下,我们可以直接的将鼠标位置信息直接赋值给我们的transition.position(如果想使用RectTransformUtility.ScreenPointToLocalPointInRectangle也是可以的)

3. 问题

  • 首先:一般情况下我们会很少去使用GameObject.Find方法(如果需要找很多类似的游戏对象)
    原因之一就是因为这个方法是在所有游戏对象中,通过名字挨个去查找满足这个名字要求的游戏对象,我们有可能会有很多个【槽】也有很多个物品,那么这将会是一件非常消耗性能的地方
  • 第二就是如果你的同事修改了你这个对象的名字,那这个方法对这个项目可能就会造成不必要的隐患
  • 还有就是Vector3.Distancel的计算方法也非常消耗性能,这就引出了本文章的最后一种方法,通过接口来实现UI对象的拖拽、点击等操作

接口

首先我们需要去引用UnityEngine.EventSystems命名空间

接着我们开始使用EventSystem下提供的拖拽接口,有关Drag的接口,有IBeginDrag、IEndDrag、IDrag等接口

注意:OnBeginDrag和OnEndDrag的使用必须依赖OnDragHandler,而反过来,OnDragHandler却可以单独使用

1. IBeginDrag、IEndDrag、IDrag接口实现拖拽

1.1 扩展

我们可以通过rectTrans.AnchoredPosition的方式,获取这个UI图片「相对于」Anchor锚点的(参考)位置坐标信息,等于形参变量「eventData.delta」,这里的eventData.deltal的类型是Vector2结构体类型,表示的是自从上一次更新、上一次Update(Since Last Update可以理解为每一帧)用户拖着这个对象所移动的2D位置坐标信息通过+=的方式的累加(赋值),在拖拽的过程当中,赋值给RectTrans组件当中的anchoredPosition,如果你对这个方法有所迟疑呢,我们还可以去使用之前的transform.position=input.mousePosition来实现

1.2 槽的实现

我们拖拽的这个物品是否在【槽内】还是在【槽外】,所以放下物品这一个操作接口IDropHandler应该在写【槽Slot】这个游戏对象中,而不是写在我们的这个物品脚本上,因为我们的物品随时是可以Drop的,但是只有当我们的物品在【槽内】Drop的时候呢,那才有意义

1.2.1 槽的代码实现

using UnityEngine;
using UnityEngine.EventSystems;

public class Slot:MonoBehaviour,IDropHandler
{
    
    
	// 在拖拽结束时调用,将拖拽的物体的位置设置为当前物体的位置
	public void OnDrop(PointerEventData eventData)	
	{
    
    
		eventData.pointerDrag.GetComponent<RectTransform>().anchoredPosition = GetComponent<RectTransform>().anchoredPosition;
	}
}

1.2.2 可能出现的问题

当我们想要点击、想要触发的这个对象时,可能被上一层的U对象呢所遮挡、所覆盖了,会导致我们的鼠标无法被检测到,无法实现我们想要实现的功能。
比如说这里,我们想要实现的是槽这个游戏对象中的OnDrop方法,但是槽本身呢被上方鼠标拖拽的这个UI物品所覆盖、所遮挡了,他无法获取到我们鼠标何时松开Drop的操作,因为他被我们的物品所遮挡

这里介绍一个更为方便的组件,适合管理这一物体,包括他的子物体的所有的UI对象(透明度、可交互、是否遮挡等属性),添加Canvas Group组件

在脚本当中呢,我们首先去获取Canvas Group组件
在这里插入图片描述
当我们开始拖拽时,在OnBeginDrag方法的内部,将这个组件的blocksRaycasts属性设置为false,表示在我们刚开始拖拽的整个过程当中,鼠标不会再去把这个UI物品当作一个阻挡物来看待

1.3 物品代码

using UnityEngine;
using UnityEngine.EventSystems;

public class DragByInterface : MonoBehaviour,IDragHandler,IBeginDragHandler,IEndDragHandler
{
    
    
    private RectTransform rectTrans;
    private CanvasGroup canvasGroup;
    private void Start()
    {
    
    
        rectTrans = GetComponent<RectTransform>();
        canvasGroup = GetComponent<CanvasGroup>();
    }
    public void OnBeginDrag(PointerEventData eventData)
    {
    
    
        // 开始拖拽时,禁用射线检测和设置透明度
        canvasGroup.blocksRaycasts = false;
        canvasGroup.alpha = 0.35f;
    }
    public void OnDrag(PointerEventData eventData)
    {
    
    
        // 拖拽时,更新位置
        rectTrans.anchoredPosition += eventData.delta;
        //transform.position Input.mousePosition;//0PTIONAL
    }
    public void OnEndDrag(PointerEventData eventData)
    {
    
    
        // 结束拖拽时,启用射线检测和恢复透明度
        canvasGroup.blocksRaycasts = true;
        canvasGroup.alpha = 1f;
    }
}

1.4 效果

在这里插入图片描述

1.5 问题

这里最后提一句,如果鼠标在拖拽的过程当中,并不是和你要拖着这个物品同步,比如说鼠标和你拖拽的这个点的位置偏离过大
在这里插入图片描述

我们需要去检查Cnavasi画布当中Scale的数值呢是否为1,如果这个scalel不是1,那么就会出现鼠标拖拽过程当中不同步的问题,我们需要在EventData.deltal的后面,去除以相应的U画布尺寸大小的系数,这样就可以去解决鼠标拖拽U物品跑偏的这个问题了

public void OnDrag(PointerEventData eventData)
{
    
    
	rectTrans.anchoredPosition += eventData.delta / canvas.scaleFactor;
	//transform.position Input.mousePosition;//0PTIONAL
}

2. 单独使用IDragHandler实现拖拽

我们简单的通过接口来实现针对不同的UI面板窗口,进行拖拽的功能实现
在这里插入图片描述
我们希望拖拽的每个面板上方的导航栏会显示在UI的最高层,我们可以去使用panelRectPanel.SetAsLastSibling表示的是:set设置,as为,last最后一个、最下方的Sibling同级最下方的这个位置将它在Hierarchy窗▣中,这个父物体下的顺序设置为最后一个,这样,我们就可以确保它能渲染在最前方

using UnityEngine;
using UnityEngine.EventSystems;

public class Dragwindow : MonoBehaviour, IDragHandler, IPointerDownHandler
{
    
    
    private RectTransform panelRectTrans;

    private void Awake()
    {
    
    
        // 如果panelRectTrans为空,则设置为父对象的RectTransform组件
        if (panelRectTrans == null)
            panelRectTrans = transform.parent.GetComponent<RectTransform>();
    }
	
	//当用户拖拽物体时,将会触发
    public void OnDrag(PointerEventData eventData)
    {
    
    
        // 更新panelRectTrans的anchoredPosition
        panelRectTrans.anchoredPosition += eventData.delta;
    }
	
	//当用户按下物体时,将会触发
    public void OnPointerDown(PointerEventData eventData)
    {
    
    
        // 将panelRectTrans设置为最后一个兄弟对象
        panelRectTrans.SetAsLastSibling();
    }
}

效果
在这里插入图片描述

参考

完毕

好了,我是向宇,https://xiangyu.blog.csdn.net/

一位在小公司默默奋斗的开发者,出于兴趣爱好,于是开始自习unity。最近创建了一个新栏目【你问我答】,主要是想收集一下大家的问题,有时候一个问题可能几句话说不清楚,我就会以发布文章的形式来回答。 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~

我知道阁下是一位白嫖高手,但假如我说下面有个打赏按钮,那阁下又该如何应对呢?
(量力而行,力虽微,心暖人,你的支持是我创作的最大动力)

猜你喜欢

转载自blog.csdn.net/qq_36303853/article/details/131096925