Unity UI点击事件

摘要:
UI单击事件UGUI事件本质上是发送光线。由于UI操作有一些复杂的手势,UGUI帮助我们封装另一层=rhs.distance)returnlhs.distance。CompareTo;//最后,比较indexreturnlhs。指数比较到;}}记住,每个画布都必须绑定GraphicRaycaster脚本以侦听单击事件吗?因为多个UI相交,所以网格批准与光线相交的第一个对象是没有意义的。然而,我们只需要响应顶部UI元素。在这里,我们只能按深度排序,找到顶部UI元素,然后抛出正确的单击事件。

UI点击事件

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

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

Unity UI点击事件第2张

当存在可执行的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元素,那么就及时取消勾选吧。

免责声明:文章转载自《Unity UI点击事件》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇XAudio2播放PCMGrid++Report应用(引入项目中)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

linux性能评估-磁盘io概念实战篇

看起来python是个可疑进程。avgqu-sz:平均I/O队列长度。%util:一秒中有百分之多少的时间用于I/O操作,即被io消耗的cpu百分比备注:如果%util接近100%,说明产生的I/O请求太多,I/O系统已经满负荷,该磁盘可能存在瓶颈。如果avgqu-sz比较大,也表示有当量io在等待。观察iostat的最后一列,你会看到,磁盘vda的I/O使...

Latex 双栏模式下表格太长怎么办?

有时一张桌子放不下任何一页。如果使用原始表包,它可能会溢出。因此,自动更改表格是很自然的。对于许多在线材料,建议使用Longtable。但是因为我的文章是双栏文章,所以这个包会有问题。例如,表格将只浮动在文本上,标题的显示也有问题。经过长时间的尝试,我终于找到了解决方案,而且非常简单。只需缩放表格。方法如下:egin{table*}[!...

华为交换机堆叠配置

请参考华为交换机的配置堆栈。[Leaf1-stack-port0/1]portinterfaceg0/0/12启用物理接口12加入堆栈组[Leaf1]stackslot0priority255修改优先级255,默认值为100警告:不要频繁修改优先级,因为它会使堆栈分裂。持续...

C# AES的128位、192位、256位加密

这里将不解释C#AES的128位、192位和256位加密原理。这里我们主要讨论AES的CBC加密模式中128位、192位和256位加密之间的差异,并参考对称加密和块加密的四种模式。16位密钥对应128位加密,24位密钥对应192位加密,32位密钥对应256位加密,矢量必须为16位。“);ifthrownewException(”指定的密钥长度不能小于16位。...

浅析前端常见文件下载的9种场景:Blob基础知识/组成/Blob URL、a标签下载、showSaveFilePicker API下载(兼容性差)、FileSaver.js库下载、Zip下载(JSZip库)、附件形式下载(设置Content-Disposition)、base64格式下载(需转为blob)、分块传输下载、HTTP范围请求下载、大文件分块并行下载

它主要涉及九种文件下载场景。在浏览器端文件下载场景中,JavaScript中的blob类型对象表示一个不可变的原始数据类文件对象。在JavaScript中,您可以通过blob构造函数创建blob对象,blob构造函数表示要放入blob的数组内容的MIME类型。行终止符将更改为适合主机操作系统文件系统的新行字符,允许Blob和file对象用作图像的URL源、下...

CSS躬行记(8)——裁剪和遮罩

裁剪最早是在CSS2.1时代由clip属性引入,但该属性只能应用于绝对定位的元素,并且只能裁剪成矩形。CSS3提供了强大的clip-path属性,突破了clip属性的众多限制,接下来将围绕clip-path属性展开讲解。3)裁剪路径对于复杂的形状,可以采用SVG来创建裁剪路径,实现自定义。2)替换元素的填充和定位CSS3引入了两个新属性,用于遮罩替换元素。...