美文网首页unityunity3D技术分享Unity乱七八糟技巧
【UGUI】UGUI的Drag拖拽与游戏物体的Drop进行交互

【UGUI】UGUI的Drag拖拽与游戏物体的Drop进行交互

作者: 技术宅_志远 | 来源:发表于2018-10-31 22:40 被阅读178次

    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

    通过反编译得知函数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。


    Button

    创建一个测试接收脚本DropTest挂载到一个Cube上

    public class DropTest : EventTrigger
    {
        public override void OnDrop(PointerEventData eventData)
        {
            Debug.Log("我收到一个拖入者:"+ eventData.lastPress);
            base.OnDrop(eventData);
        }
    }
    
    Cube

    补充:别忘了给摄像机挂载PhysicsRaycaster组件哦!!!!!

    0x004 实现效果

    效果演示

    0x005 后记

    写代码一定要想着偷懒,但是要精致的偷懒 = =

    相关文章

      网友评论

      本文标题:【UGUI】UGUI的Drag拖拽与游戏物体的Drop进行交互

      本文链接:https://www.haomeiwen.com/subject/xxpztqtx.html