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

news/2024/10/31 3:20:10/

前言

在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" 的位置elsetransform.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的anchoredPositionpanelRectTrans.anchoredPosition += eventData.delta;}//当用户按下物体时,将会触发public void OnPointerDown(PointerEventData eventData){// 将panelRectTrans设置为最后一个兄弟对象panelRectTrans.SetAsLastSibling();}
}

效果
在这里插入图片描述


http://www.ppmy.cn/news/280390.html

相关文章

Linux防火墙学习笔记5

iptables规则匹配及动作&#xff1a; 规则&#xff1a;根据规定的匹配条件来尝试匹配每个流经此处的数据包&#xff0c;匹配成功&#xff0c;则由规则指定的处理动作进行处理。规则是由匹配条件和动作组成的。 iptables的规则匹配条件分类&#xff1a; 基本匹配条件&#xff…

Docker Registry部署

之前执行 docker pull的命令都是从 docker hub上拉取的&#xff0c;是docker 公共仓库&#xff0c;如果在公司中使用docker&#xff0c;我们不可能把自己的镜像上传到公共仓库&#xff0c;这个时候就需要一个自己的仓库&#xff08;私有仓库&#xff09;&#xff0c;在局域网之…

[DesktopPicture]桌面图片

转载于:https://www.cnblogs.com/joekk01/p/9996959.html

Redis事务和管道

一、Redis事务 1、定义 可以一次执行多个命令&#xff0c;本质上是一组命令的集合。一个事务中的所有命令都会序列化&#xff0c;按顺序的串行化执行而不会被其他命令插入&#xff0c;不能加塞。 2、作用 一个队列中&#xff0c;一次性、顺序性、排他性的执行一系列命令。 …

公司新来了个00后测开,上来一顿操作给我秀麻了.....

开年公司新来了个同事&#xff0c;听说大学是学的广告专业&#xff0c;因为喜欢IT行业就找了个培训班&#xff0c;后来在一家小公司实习半年&#xff0c;现在跳槽来我们公司。来了之后把现有项目的性能优化了一遍&#xff0c;服务器缩减一半&#xff0c;性能反而提升4倍!给公司…

适应新时代的FTP已经出现?这种产品有何过人之处?

大家都知道&#xff0c;FTP是用于在网络上进行文件传输的一套标准协议&#xff0c;它作为互联网最经典的协议之一&#xff0c;至今已经存在了50年。而随着时代发展&#xff0c;越来越多的用户与企业开始觉得FTP不够满足大家的需求&#xff0c;出现的问题与漏洞越来越多&#xf…

freeswitch在centos 7下编译和安装

前言 freswitch在centos7下编译总体上还是步骤比较复杂的。 忠告 千万别使用 CentOS 部署 FreeSWITCH &#xff01;&#xff01;&#xff01; 以下大部分都是笔者用 CentOS7 自编译踩得坑。 建议直接 Debian 安装官方编译好的包&#xff01;&#xff01;&#xff01;可以少踩很…

springboot+java校园二手物品交易系统vxkyj

本项目在开发和设计过程中涉及到原理和技术有: B/S、Java、Jsp、MySQL数据库等等。 系统有以下几点意义&#xff1a; &#xff08;1&#xff09;提供用户和用户之间互利互惠的交易平台。 &#xff08;2&#xff09;操作简单&#xff0c;用户可以在家里就能淘到自己想要的东西&a…