Unity3D 实现 UI 元素拖拽功能。
UI 拖拽
通常画布上的 UI 元素都是固定位置的,我们可以通过实现拖拽接口,让 UI 元素可以被拖拽到其他位置。
拖拽接口
创建一个脚本 UIDrag.cs
,在默认继承的 MonoBehaviour
后面,再继承三个接口。
IBeginDragHandler
(开始拖拽)IDragHandler
(拖拽中)IEndDragHandler
(结束拖拽)
继承接口之后,要在脚本中实现接口中定义的方法,即 OnBeginDrag
、OnDrag
、OnEndDrag
。
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 模式。
如果把画布渲染模式改成 Camera 模式,上述的代码实现就会出现问题。
因为 Camera 模式的画布会被缩小到相机的视野范围内,坐标的数值会变得很小。
此时的运行效果,拖拽后图片飞到了离画布很远的位置,坐标错误。
所以我们需要对拖拽时获得的坐标位置进行转换。
坐标转换
首先,定义一个 RectTransform
变量,在 Awake
时进行赋值。
然后利用 RectTransformUtility.ScreenPointToWorldPointInRectangle
方法,把自身的 rect
、eventData.position
、eventData.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
方法,而 OnBeginDrag
和 OnEndDrag
可以为拖拽操作添加更多的逻辑处理。
例如,在场景中添加两个 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;}}
}
运行效果: