• Unity UI点击事件


    UI点击事件

    UGUI的事件本质上就是发送射线,由于UI的操作有一些复杂的手势,所以UGUI帮我们又封装了一层。创建任意UI时都会自动创建EventSystem对象,并且绑定EventSystem.cs和StandaloneInputModule.cs如下代码所示,EventSystem会将该对象绑定的所有InputModule脚本收集起来保存在SystemInputModules对象中。

    然后在EventSystem的Update()方法中更新它们,通常情况下我们只需要一个StandaloneInputModule即可。

    当存在可执行的Module 会调用它的m_CurrentInputModule.Process();方法。那么UI是如何确定出点击到那个元素上的呢?如下代码所示,在EventSystem中遍历所有module.Raycast()方法。 

    EventSystem.cs(部分代码)

    public class EventSystem : UIBehaviour
    {
        //...略
    
        public void RaycastAll(PointerEventData eventData, List<RaycastResult> raycastResults)
        {
            raycastResults.Clear();
            //获得当前激活状态下每个Canvas上绑定的GraphicRaycaster的对象
            var modules = RaycasterManager.GetRaycasters();
            for (int i = 0; i < modules.Count; ++i)
            {
                var module = modules[i];
                if (module == null || !module.IsActive())
                    continue;
                //开始发送射线
                module.Raycast(eventData, raycastResults);
            }
            //对发送射线的结果进行排序,保证在前面的UI优先处理
            raycastResults.Sort(s_RaycastComparer);
        }
    
        private static readonly Comparison<RaycastResult> s_RaycastComparer = RaycastComparer;
    
        private static int RaycastComparer(RaycastResult lhs, RaycastResult rhs)
        {
            if (lhs.module != rhs.module)
            {
                var lhsEventCamera = lhs.module.eventCamera;
                var rhsEventCamera = rhs.module.eventCamera;
                if (lhsEventCamera != null && rhsEventCamera != null && lhsEventCamera.depth != rhsEventCamera.depth)
                {
                    // 比较camera的深度
                    if (lhsEventCamera.depth < rhsEventCamera.depth)
                        return 1;
                    if (lhsEventCamera.depth == rhsEventCamera.depth)
                        return 0;
    
                    return -1;
                }
                //比较射线结果的排序优先级
                if (lhs.module.sortOrderPriority != rhs.module.sortOrderPriority)
                    return rhs.module.sortOrderPriority.CompareTo(lhs.module.sortOrderPriority);
                //比较射线结果的渲染优先级
                if (lhs.module.renderOrderPriority != rhs.module.renderOrderPriority)
                    return rhs.module.renderOrderPriority.CompareTo(lhs.module.renderOrderPriority);
            }
    
            if (lhs.sortingLayer != rhs.sortingLayer)
            {
                // 比较SortingLayer
                var rid = SortingLayer.GetLayerValueFromID(rhs.sortingLayer);
                var lid = SortingLayer.GetLayerValueFromID(lhs.sortingLayer);
                return rid.CompareTo(lid);
            }
    
            //比较sortOrder
            if (lhs.sortingOrder != rhs.sortingOrder)
                return rhs.sortingOrder.CompareTo(lhs.sortingOrder);
    
            //比较深度
            if (lhs.depth != rhs.depth)
                return rhs.depth.CompareTo(lhs.depth);
            //比较距离
            if (lhs.distance != rhs.distance)
                return lhs.distance.CompareTo(rhs.distance);
            //最后比较index 
            return lhs.index.CompareTo(rhs.index);
        }
    }
    

      还记得每个Canvas要想监听点击事件必须绑定GraphicRaycaster脚本吗?上面代码中的RaycasterManager.GetRaycasters();方法就是获取当前到底有多少个绑定GraphicRaycaster脚本的对象,那么同时参与点击事件的Canvas越多效率也就越低了,游戏中有很多界面是叠在一起的,最上面的界面已经挡住了所有界面,但是由于下面的界面还有GraphicRaycaster对象,那么必然产生额外的计算开销,所以这种情况可以DeActive不需要参与点击事件的Canvas。 

    最后我们来看看到底如何判断点击的事件的,如下代码所示,首先遍历Canvas下每一个参与渲染的Graphic对象,如果勾选了raycastTarget并且点击射线与它们相交,此时先存起来。

    由于多个UI有相交的情况,但由于Mesh都合批了第一个与射线相交的对象是没有意义的,但是我们只需要响应在最上面的UI元素,这里只能根据depth来做个排序了,找到最上面的UI元素,最后再抛出正确的点击事件。 
    GraphicRaycaster.cs(部分代码)

    public class GraphicRaycaster : BaseRaycaster
    {
        //...略
        [NonSerialized] static readonly List<Graphic> s_SortedGraphics = new List<Graphic>();
        private static void Raycast(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, IList<Graphic> foundGraphics, List<Graphic> results)
        {
            //遍历,将每个参与渲染的UI添加到s_SortedGraphics对象中。
            int totalCount = foundGraphics.Count;
            for (int i = 0; i < totalCount; ++i)
            {
                Graphic graphic = foundGraphics[i];
    
                if (graphic.depth == -1 || !graphic.raycastTarget || graphic.canvasRenderer.cull)
                    continue;
    
                if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, eventCamera))
                    continue;
    
                if (eventCamera != null && eventCamera.WorldToScreenPoint(graphic.rectTransform.position).z > eventCamera.farClipPlane)
                    continue;
               
                if (graphic.Raycast(pointerPosition, eventCamera))
                {
                    s_SortedGraphics.Add(graphic);
                }
            }
            //根据depth开始对它进行排序
            s_SortedGraphics.Sort((g1, g2) => g2.depth.CompareTo(g1.depth));
            totalCount = s_SortedGraphics.Count;
            //最后将排序过的正确顺序保存起来
            for (int i = 0; i < totalCount; ++i)
                results.Add(s_SortedGraphics[i]);
    
            s_SortedGraphics.Clear();
        }
    }

    所以说GraphicRaycaster组件越多越卡,raycastTarget勾选的越多越卡,其实开发中很多UI是不需要响应点击事件的,但是却被无意地勾选上了。这里我提供一个我开发的经验,如下图11-1所示,我会在Scene界面中将所有勾选过raycastTarget的对象用蓝色矩形框标记出来,这样做UI的人可以很方便地看到,如果有不需要参与点击的UI元素,那么就及时取消勾选吧。

  • 相关阅读:
    关于SubSonic3.0查询或更新时出现System.NullReferenceException异常的处理
    SubSonic3.0使用例子
    Hive:ORC File Format存储格式详解
    HiveQL之Sort by、Distribute by、Cluster by、Order By详解
    Hive之import和export使用详解
    手动安装cloudera manager 5.x(tar包方式)详解
    初次安装hive-2.1.0启动报错问题解决方法
    LAMP下安装zabbix流水
    ESXI虚拟机磁盘管理(精简-厚置-精简)
    VMware esxi 5.5装机方案
  • 原文地址:https://www.cnblogs.com/chenggg/p/11185890.html
Copyright © 2020-2023  润新知