前言
在Unity中实现拖拽的方法有多种,以下是几种常见的方法和它们的优缺点:
- Input.GetMouseButtonDown
Input.GetMouseButtonDown 方法可以监测用户鼠标按键的点击事件,通过检测鼠标按钮的状态来实现拖拽效果。用户通过鼠标进行拖拽操作。
优点:
- 简单易懂,是最通用的实现拖拽方法之一。
- 适用于所有平台,包括PC和移动设备。
缺点:
- 拖拽细节(如拖拽的加速度、加速度的方向等)难以控制。
- 如果需要控制多个物体的拖拽行为,则需要编写大量的代码逻辑。
- OnMouseDrag
OnMouseDrag 方法是 Unity 内建的一个组件事件,用于处理鼠标拖拽事件,可以通过在物体上加上事件脚本来实现拖拽。
优点:
- 简单明了,易于使用。
- 对于简单的拖拽需求,非常适用。
缺点:
- 只能用于PC平台或者Web平台。
- 不支持多点触摸和移动设备上的触摸操作。
- Event Trigger 中的 BeginDrag、OnDrag 和 EndDrag
Event Trigger 是 Unity 中常用的 GUI 事件框架,通过监听不同的事件类型实现拖拽功能,包括 BeginDrag、OnDrag 和 EndDrag 事件。
BeginDrag 事件:用户开始拖拽一个物体时触发该事件;
OnDrag 事件:在拖拽物体时持续调用该事件,可以实现拖拽过程中的反馈等功能;
EndDrag 事件:在用户释放物体时触发该事件,可以在此处理放置、执行等操作。
优点:
- 支持多点触摸和移动设备上的触摸操作。
- 比较容易控制拖拽的操作流程,如速度、拖拽范围等。
- 可以实现更多基于 GUI 的拖拽效果。
缺点:
- 对于非 GUI 元素的拖拽,需要额外的逻辑实现。
- 开销比较大。
- 接口实现的 OnBeginDrag、OnDrag 和 OnEndDrag
该方法需要继承 UnityEngine.EventSystems.IDragHandler 接口并实现接口中的方法,从而接收该界面上的物体的拖拽操作。
优点:
- 支持多点触摸和移动设备上的触摸操作。
- 对于非 GUI 元素的拖拽,也很容易实现。
缺点:
- 开销较大。
- 需要手动实现接口中的方法。
综上所述,以上几种实现拖拽效果的方法各有优缺点,需要针对实际需求来选择使用。如果需要快速实现拖拽效果,可以使用 Input.GetMouseButtonDown 和 OnMouseDrag。如果要实现更多的拖拽事件,可以使用 Event Trigger 或者接口实现。
新建场景
我们要做的就是:当游戏运行后
通过鼠标的点击、拖拽、松开等操作
能够自由地将右边的这些人物的零部件
自定义(拖拽)到我们左边的这个人物的外貌上
因为如果我们想使用OnMouseOver、OnMouseEnter、.,On MouseExit等方法的实现的条件呢
这个对象必须With the Collider”含有Collider组件,之后才能被这些方法所调用
我们只要将鼠标的屏幕坐标系信息转换为世界坐标系就可以了
我们可以通过Camera.Main.ScreenToWorldPoint
方法的名字呢也很直接,说的就是将
Screen屏幕坐标系To转换为WorldPointt世界坐标系上的每一个像素点(坐标)
代码实现
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;
}
}
拖拽内置方法实现
其中就有一个叫做【OnMouseDrag】方法呢
更方便的可以实现这个案例当中的2D贴图的拖拽
当我们的鼠标进入、或者离开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;
}
OnMouseDrag等方法呢
我会在2D游戏当中,那些非U类型的2D贴图
其他游戏
我们要做的就是
将下方的三张图通过拖拽来进行正确的匹配
我们首先选中可拖拽的这三张图片
因为由于我们之后会用到OnMouseDrag等方法
我们首先去添加BoxCollider:2D组件
代码实现
private Vector2 startPos;
[SerializeField]private Transform correctTrans;//Black Image
[SerializeField]private bool isFinished;
private void Start()
{
startPos = transform.position;
}
private void OnMouseDrag()
{
if(!isFinished)//isFinished =false
{
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场景当中
如果遇到U图片呢
是不会去使用OnMouseDrag等方法的
新建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");
float dist Vector3.Distance(transform.position,slotGO.transform.position);
if(dist<=100)
transform.position slotGO.transform.position;
else
transform.position startPos;
}
刚才不是说鼠标的位置信息是屏幕坐标系
而我们现在图片呢由于是U图片并不是在同一坐标系
为什么我们可以用等号直接来写呢
如果U模式是Screenspace Overlay模式下呢
我们可以直接的将鼠标位置信息直接赋值给我们的transition.position
如果想使用RectTransformUtility.ScreenPointToLocalPointInRectangle也是可以的)
在刚才的Event Trigger上挂载对应的方法
运行效果
这样就实现了我们的基本功能,但是呢,它存在很多问题
首先:一般情况下我们会很少去使用GameObject.Find方法
(如果需要找很多类似的游戏对象)
原因之一呢是因为
这个方法呢是在所有游戏对象中
通过名字挨个去查找
满足这个名字要求的游戏对象
我们有很多个【槽】也有很多个物品
这将会是一件非常消耗性能的地方
第二呢
如果你可爱的同事修改了你这个对象的名字
那这个方法呢
对这个项目呢
就会造成不必要的隐患
还要呢就是
Vector3.Distancel的计算方法呢也非常消耗性能
这就引出了本视频当中最后一种方法
通过接口来实现UI对象的拖拽、点击等操作
接口
我们可以让脚本实现更多有趣的事件接口
这些接口呢会强制要求实现它的方法
接口的方法
会在进行U交互的特定行为出现后呢,被调用
使用接口需要在冒号的后面:输入我们需要使用的接口
接口一般惯例会是以【I】开头的
【I】呢就表示我们的Interface接口
如果我们想使用IPointerDownHandler接口
直接输入呢还是不行的
因为使用这些接口
首先我们需要去引用UnityEngine.EventSystems命名空间
单单这样呢依旧是会报错的
因为使用接口的【前提】
接口本身它只关注方法的定义
他并不会去关注方法的实现
我们要使用这个接口
那就必须将接口中未实现的这些方法呢
加以实现
接着我们开始使用EventSystem下提供的拖拽接口
我们可以看到有关Drag的接口呢
有IBeginDrag,IEndDrag和IDrag等四个接口
如果BeginDrag和EndDrag在没有IDrag的基础上呢,是不成大器的
如果这里我们再补上OnDragHandler接口呢
那么呢,Console窗口呢就会输出我们想要的效果了
也就是BeginDrag和EndDrag的使用必须依赖OnDragHandler,而反过来却又是可行的,OnDragHandler可以单独使用
首先我们需要去获取当前拖拽的这个游戏对象,就是我们这个UI物品的RectTrans组件
我们通过rectTrans.AnchoredPosition的方式
获取这个UI图片「相对于」Anchor锚点的(参考)位置坐标信息
等于形参变量「eventData.delta」
这里的eventData.deltal呢
首先它的类型呢,是Vector2结构体类型
表示的是
自从上一次更新、上一次Update
(Since Last Update可以理解为每一帧)
用户拖着这个对象所移动的2D位置坐标信息
通过+=的方式的累加(赋值)
(这里的「+=」表示A=A+1,也可以写作A+=1;与委托的「+=」不同
在拖拽的过程当中
赋值给RectTrans组件当中的anchoredPosition
如果你对这个方法有所迟疑呢
我们还可以去使用之前的transform.position=input.mousePosition:来实现
我们拖拽的这个物品是否在【槽内】还是在【槽外】
所以呢放下物品这一个操作
接口IDropHandler应该在写【槽Slot】这个游戏对象中
而不是写在我们刚才写的这个物品脚本上
因为我们的物品呢随时是可以Drop的
只有当我们的物品在【槽内】Drop的时候呢
那才有意义
代码
using UnityEngine;
using UnityEngine.EventSystems;
public class Slot:MonoBehaviour,IDropHandler
{
public void OnDrop(PointerEventData eventData)
{
Debug.Log("Droooo0000oooop");
eventData.pointerDrag.GetComponent<RectTransform>().anchoredPosition = GetComponent<RectTransform>().anchoredPosition;
}
}
这里呢就要提到我们在制作U过程当中,经常会犯
那就是我们想要点击、想要触发的这个对象可能呢
被上一层的U对象呢所遮挡、所覆盖了
所以呢会导致我们的鼠标
无法被检测到,无法实现我们想要实现的功能
比如说这里
我们想要实现的是槽这个游戏对象中的OnDrop方法
比如说这里
我们想要实现的是槽这个游戏对象中的OnDrop方法
但是槽本身呢
被上方鼠标拖拽的这个UI物品所覆盖、所遮挡了
他无法获取到我们鼠标何时松开Drop的操作
因为他被我们的物品所遮挡
这里呢介绍一个更为方便的组件
适合管理这一物体
包括他的子物体的所有的UI对象(透明度、可交互、是否遮挡等属性)
添加Canvas Group组件
Group:表示这一个组合,这一个整体
管理这一个整体的一个组件
在脚本当中呢,我们首先去获取Canvas Group组件
当我们开始拖拽时
在OnBeginDrag方法的内部
将这个组件的blocksRaycasts属性设置为false
表示在我们刚开始拖拽的整个过程当中
鼠标不会再去把这个U物品当作一个碰撞物
当作一个阻挡物来看待
当我们拖拽这个物品到槽的上方时呢
鼠标能够顺利的去忽略这个UI物品
如果松开这个物品能够成功的去调用OnDrop方法
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;
}
}
效果
问题
这里最后提一句
如果在操作过程当中
鼠标在拖拽的过程当中呢
并不是和你要拖着这个物品同步的
比如说鼠标和你拖拽的这个点的位置偏离过大呢
我们还需要去检查
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
}
其他
我们简单的通过接口来实现针对不同的UI面板窗口
进行拖拽的功能实现
我们希望的是通过拖拽每个面板上方的导航栏
对不同的窗口面板呢进行拖拽
就像我们浏览器的网页一样
并且呢
会显示在UI的最高层
我们还是通过接口IDragHandler来实现
我们可以去使用【panelRectPanel,SetAsLastSibling】
表示的是:set设置,as为,last最后一个、最下方的
Sibling同级最下方的这个位置
将它在Hierarchy窗▣中,这个父物体下的顺序呢设置为最后一个
这样呢,我们就可以确保它能渲染在最前方
using UnityEngine;
using UnityEngine.EventSystems;
public class Dragwindow MonoBehaviour,IDragHandler,IPointerDownHandler
{
//[SerializeField]private RectTransform panelRectTrans;//0PTIONAL
private RectTransform panelRectTrans;
private void Awake()
{
if (panelRectTrans =null)
panelRectTrans transform.parent.GetComponent<RectTransfgrm>();
}
public void OnDrag(PointerEventData eventData)
{
panelRectTrans.anchoredPosition += eventData.delta;
}
public void OnPointerDown(PointerEventData eventData)
{
panelRectTrans.SetAsLastsibling();
}
}
效果