0x001 需求
Unity事件系统EventSystems在对于UGUI之间的OnDrag和OnDrop完全没问题,但是对于UGUI和游戏物体的交互就会出现一些情况,我们要实现一个让UGUI和游戏物体之间的OnDrag和OnDrop。
当然你非要用自己的射线进行检测,我也不说什么,但是为了高效的进行开发,做一些疯狂的事情是有必要的!
0x002 分析
引入一个国外路人的提问以及热心道友的回答
how do you use IDropHandler.OnDrop with 3D objects?
经过测试发现确实是UGUI的射线遮挡(Raycast Target)选项影响了我们的3D射线输入组件(PhysicsRaycaster)的正常运行,用道友的方法确实是可以实现效果,但是并不稳定也不够高效和严谨,更不用说代码复用了,对此我提出一个好想法!
再次引入momo大神的文章
Unity3D研究院之将UI的点击事件渗透下去
那么我们就让UGUI的OnDrop事件渗透到3D物体上去吧=-=
补充
通过momo大神的文章得知EventSystem.current.RaycastAll(, );可以得到所有射线检测的物体
其实我们是间接利用了系统EventSystem的射线。
通过反编译得知函数RaycastAll对返回值 List 做了排序(sort)操作 因此我们不需要进行前后排序。
0x003 实现步骤
1. 编写BaseDrag抽象基类
using UnityEngine;
using UnityEngine.EventSystems;
using System.Collections.Generic;
public abstract class BaseDrag :MonoBehaviour, IDragHandler,IEndDragHandler,IBeginDragHandler
{
public bool dropAll = false;
/// <summary>
/// 拖拽开始
/// </summary>
/// <param name="eventData"></param>
public abstract void OnBeginDrag(PointerEventData eventData);
/// <summary>
/// 拖拽中
/// </summary>
/// <param name="eventData"></param>
public abstract void OnDrag(PointerEventData eventData);
/// <summary>
/// 拖拽结束
/// </summary>
/// <param name="eventData"></param>
public virtual void OnEndDrag(PointerEventData eventData)
{
if (dropAll)
PassEvent(eventData, ExecuteEvents.dropHandler);
}
//把事件透下去
public void PassEvent<T>(PointerEventData data, ExecuteEvents.EventFunction<T> function)
where T : IEventSystemHandler
{
List<RaycastResult> results = new List<RaycastResult>();
EventSystem.current.RaycastAll(data, results);
///用真正的当前拖拽的物体做判断
GameObject current = data.pointerDrag;
for (int i = 0; i < results.Count; i++)
{
if (current != results[i].gameObject)
{
///如果是3D物体的射线那么就直接赋值到原数据,传递到下一个组件。
///这里是为了用户能拿到当前射线打到3D物体的世界坐标。
///方便用户自己处理 不需要再次发射射线。
data.pointerPressRaycast = results[i];
ExecuteEvents.Execute(results[i].gameObject, data, function);
}
}
}
}
此类规定了拖拽类的基本事件,以及实现了事件的渗透,和控制开关dropAll 并且继承MonoBehaviour
2. 编写UIDrag用户实际实现类
这里为了方便,直接找一个网上已经实现的UI拖拽类进行重构
UGUI拖拽类
重构后代码
using UnityEngine;
using UnityEngine.EventSystems;
public class UIDrag : BaseDrag
{
[Header("是否精准拖拽")]
public bool m_isPrecision = true;
//存储图片中心点与鼠标点击点的偏移量
private Vector3 m_offset;
//存储当前拖拽图片的RectTransform组件
private RectTransform m_rt;
void Start()
{
//初始化
m_rt = gameObject.GetComponent<RectTransform>();
}
//开始拖拽触发
public override void OnBeginDrag(PointerEventData eventData)
{
//如果精确拖拽则进行计算偏移量操作
if (m_isPrecision)
{
// 存储点击时的鼠标坐标
Vector3 tWorldPos;
//UI屏幕坐标转换为世界坐标
RectTransformUtility.ScreenPointToWorldPointInRectangle(m_rt, eventData.position, eventData.pressEventCamera, out tWorldPos);
//计算偏移量
m_offset = transform.position - tWorldPos;
}
//否则,默认偏移量为0
else
{
m_offset = Vector3.zero;
}
SetDraggedPosition(eventData);
}
//拖拽过程中触发
public override void OnDrag(PointerEventData eventData)
{
SetDraggedPosition(eventData);
}
//结束拖拽触发
public override void OnEndDrag(PointerEventData eventData)
{
SetDraggedPosition(eventData);
base.OnEndDrag(eventData);
}
/// <summary>
/// 设置图片位置方法
/// </summary>
/// <param name="eventData"></param>
private void SetDraggedPosition(PointerEventData eventData)
{
//存储当前鼠标所在位置
Vector3 globalMousePos;
//UI屏幕坐标转换为世界坐标
if (RectTransformUtility.ScreenPointToWorldPointInRectangle(m_rt, eventData.position, eventData.pressEventCamera, out globalMousePos))
{
//设置位置及偏移量
m_rt.position = globalMousePos + m_offset;
}
}
}
继承对象改为BaseDrag,并把原本的OnBeginDrag、OnDrag、OnEndDrag 方法前加上override (重写)
注意OnEndDrag方法最后加上base.OnEndDrag(eventData);调回父类实现穿透。
创建一个Button挂载UIDrag ,并点上DropAll。
创建一个测试接收脚本DropTest挂载到一个Cube上
public class DropTest : EventTrigger
{
public override void OnDrop(PointerEventData eventData)
{
Debug.Log("我收到一个拖入者:"+ eventData.lastPress);
base.OnDrop(eventData);
}
}
补充:别忘了给摄像机挂载PhysicsRaycaster组件哦!!!!!
0x004 实现效果
0x005 后记
写代码一定要想着偷懒,但是要精致的偷懒 = =