前言:
最近项目一直在做优化,然后发现Unity的其中一个大坑,关于EventSystem的。当玩家在连续操作屏幕的时候,就会触发EventSystem.Update() -->....-->EventSystem.RaycastAll();这个RaycastAll非常耗时,每帧七八毫秒、甚至十几毫秒的情况都有。

虽然是Unity 的底层,但是还是要想办法来优化一下。

正文:
1、现状分析:

由上图我们可以看到,耗时的大头分别是:

GraphicRaycaster.get_eventCamera();           --------1.84ms

Graphic.get_canvasRenderer();                      --------1.14ms+1.17ms=2.31ms

其中Graphic.get_canvasRenderer()总计调用了2次。这两个方法总计造成耗时4.15ms,占总共8.32ms的49.88%。如果能解决这两个的耗时问题,就能将这个函数的耗时减少一半,还是非常可观的。

2、优化GraphicRaycaster.get_eventCamera()

这个函数使用来获取当前UI的摄像机的,但是实际上相机都是与各个Canvas绑定的,按理来说一旦初始化之后,只要游戏内没有更改过相机,那么就应该一直是原来的相机。其实他的动态获取在兼容性上会好些,但是在性能上面是没有必要的。

优化这一个需要重写GraphicRaycaster,一般这个类会挂载在Canvas上面:

public class YHGraphicRaycaster : GraphicRaycaster
    {
        public Camera TargetCamera;
 
        public override Camera eventCamera
        {
            get
            {
                if (TargetCamera == null)
                {
                    TargetCamera = base.eventCamera;
                }
                return TargetCamera;
            }
        }
 
    }
代码其实非常简单,然后只需要将这个YHGraphicRaycaster 挂载在Canvas下面,替换掉原来的Canvas可以了。

经过这么一堆操作,目前性能状况如下:

优化之后(YHGraphicRaycaster.get_eventCamera())剩余0.16ms,基本减少了2ms。其实可以看到其实还是有调用GraphicRaycaster.get_eventCamera(),说明还有哪里的GraphicRaycaster没有替换干净,只有再去项目里找找看在哪里。不过比较而言,原来的方法10次调用0.2ms,新方法76次调用0.16ms,区别还是很大的。

经过优化之后点击事件没有任何问题,亲测无Bug。

另:翻阅其源码:

namespace UnityEngine.UI
{
    [AddComponentMenu("Event/Graphic Raycaster")]
    [RequireComponent(typeof(Canvas))]
    public class GraphicRaycaster : BaseRaycaster
    {
//……
        public override Camera eventCamera
        {
            get
            {
                if (canvas.renderMode == RenderMode.ScreenSpaceOverlay || (canvas.renderMode == RenderMode.ScreenSpaceCamera && canvas.worldCamera == null))
                    return null;
 
                return canvas.worldCamera != null ? canvas.worldCamera : Camera.main;
            }
        }
 
//……
    }
}
可见其原本并没有缓存,而是每次都是去获取相机,所以是有优化空间的。

3、 优化Graphic.get_canvasRenderer()

与EventCamera不同的是,canvasRenderer在Unity的内部是有缓存的:

namespace UnityEngine.UI
{
    /// <summary>
    /// Base class for all UI components that should be derived from when creating new Graphic types.
    /// </summary>
    [DisallowMultipleComponent]
    [RequireComponent(typeof(CanvasRenderer))]
    [RequireComponent(typeof(RectTransform))]
    [ExecuteInEditMode]
    public abstract class Graphic
        : UIBehaviour,
          ICanvasElement
    {
        //……        
        [NonSerialized] private CanvasRenderer m_CanvasRender;
        //……    
        /// <summary>
        /// UI Renderer component.
        /// </summary>
        public CanvasRenderer canvasRenderer
        {
            get
            {
                if (m_CanvasRender == null)
                    m_CanvasRender = GetComponent<CanvasRenderer>();
                return m_CanvasRender;
            }
        }
        //……
    }
}
所以其耗时多就是因为调用此时太多了,其实可以看到其477+477次的调用,单次0.002ms,其实和优化后的eventCamera单次调用是在一个水平上的。

所以从canvasRenderer的获取上来看,没有什么值得优化的点,值得优化的部分在于减少对此函数的调用。从源码分析来看,其实UGUI在获取到很多点击控件的时候会对其进行排序。但是我们项目的实际情况是根本不需要排序,因为只响应最顶层的UI就可以了。那些被遮住的UI干嘛还要排序呢?就算排序完了,发现不在顶层也不会响应啊。

那么如果需要UI穿透,那排序有用吗?当然也是没用的。因为这里只是每个Canvas下面的UI元素排序,在收集完各个Canvas的点击元素之后之后Unity会再一次进行排序,最后选取最上层的。所以这里的排序我认为是没有什么实际意义的,所以就取消了这里的排序,而只返回最顶层的元素。

后来想,这个会不会和手机上的多点触控有关系,不排序导致其不能多点触控了。不过后来打了安卓包试了试发现没这个问题,那这个就基本告别排序了。

将YHGraphicRaycaster增加如下部分:

#region 事件点击部分;
        
        private Canvas m_Canvas;
 
        private Canvas canvas
        {
            get
            {
                if (m_Canvas == null)
                    m_Canvas = GetComponent<Canvas>();
                return m_Canvas;
            }
        }
 
        [NonSerialized] private List<Graphic> m_RaycastResults = new List<Graphic>();
        public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
        {
            if (canvas == null)
                return;
 
            var canvasGraphics = GraphicRegistry.GetGraphicsForCanvas(canvas);
            if (canvasGraphics == null || canvasGraphics.Count == 0)
                return;
 
            int displayIndex;
            var currentEventCamera = eventCamera; // Propery can call Camera.main, so cache the reference
 
            if (canvas.renderMode == RenderMode.ScreenSpaceOverlay || currentEventCamera == null)
                displayIndex = canvas.targetDisplay;
            else
                displayIndex = currentEventCamera.targetDisplay;
 
            var eventPosition = Display.RelativeMouseAt(eventData.position);
            if (eventPosition != Vector3.zero)
            {
                int eventDisplayIndex = (int)eventPosition.z;
                if (eventDisplayIndex != displayIndex)
                    return;
            }
            else
            {
                eventPosition = eventData.position;
            }
 
            // Convert to view space
            Vector2 pos;
            if (currentEventCamera == null)
            {
                float w = Screen.width;
                float h = Screen.height;
                if (displayIndex > 0 && displayIndex < Display.displays.Length)
                {
                    w = Display.displays[displayIndex].systemWidth;
                    h = Display.displays[displayIndex].systemHeight;
                }
                pos = new Vector2(eventPosition.x / w, eventPosition.y / h);
            }
            else
                pos = currentEventCamera.ScreenToViewportPoint(eventPosition);
            if (pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y > 1f)
                return;
 
            float hitDistance = float.MaxValue;
 
            Ray ray = new Ray();
 
            if (currentEventCamera != null)
                ray = currentEventCamera.ScreenPointToRay(eventPosition);
 
            if (canvas.renderMode != RenderMode.ScreenSpaceOverlay && blockingObjects != BlockingObjects.None)
            {
                float distanceToClipPlane = 100.0f;
 
                if (currentEventCamera != null)
                {
                    float projectionDirection = ray.direction.z;
                    distanceToClipPlane = Mathf.Approximately(0.0f, projectionDirection)
                        ? Mathf.Infinity
                        : Mathf.Abs((currentEventCamera.farClipPlane - currentEventCamera.nearClipPlane) / projectionDirection);
                }
            }
            m_RaycastResults.Clear();
            Raycast(canvas, currentEventCamera, eventPosition, canvasGraphics, m_RaycastResults);
            int totalCount = m_RaycastResults.Count;
            for (var index = 0; index < totalCount; index++)
            {
                var go = m_RaycastResults[index].gameObject;
                bool appendGraphic = true;
 
                if (ignoreReversedGraphics)
                {
                    if (currentEventCamera == null)
                    {
                        var dir = go.transform.rotation * Vector3.forward;
                        appendGraphic = Vector3.Dot(Vector3.forward, dir) > 0;
                    }
                    else
                    {
                        var cameraFoward = currentEventCamera.transform.rotation * Vector3.forward;
                        var dir = go.transform.rotation * Vector3.forward;
                        appendGraphic = Vector3.Dot(cameraFoward, dir) > 0;
                    }
                }
 
                if (appendGraphic)
                {
                    float distance = 0;
 
                    if (currentEventCamera == null || canvas.renderMode == RenderMode.ScreenSpaceOverlay)
                        distance = 0;
                    else
                    {
                        Transform trans = go.transform;
                        Vector3 transForward = trans.forward;
                        distance = (Vector3.Dot(transForward, trans.position - currentEventCamera.transform.position) / Vector3.Dot(transForward, ray.direction));
                        if (distance < 0)
                            continue;
                    }
                    if (distance >= hitDistance)
                        continue;
                    var castResult = new RaycastResult
                    {
                        gameObject = go,
                        module = this,
                        distance = distance,
                        screenPosition = eventPosition,
                        index = resultAppendList.Count,
                        depth = m_RaycastResults[index].depth,
                        sortingLayer = canvas.sortingLayerID,
                        sortingOrder = canvas.sortingOrder
                    };
                    resultAppendList.Add(castResult);
                }
            }
        }
 
 
        /// <summary>
        /// Perform a raycast into the screen and collect all graphics underneath it.
        /// </summary>
        [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)
        {
            int totalCount = foundGraphics.Count;
            Graphic upGraphic = null;
            int upIndex = -1;
            for (int i = 0; i < totalCount; ++i)
            {
                Graphic graphic = foundGraphics[i];
                int depth = graphic.depth;
                if (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);
                    if (depth > upIndex)
                    {
                        upIndex = depth;
                        upGraphic = graphic;
                    }
                }
            }
            if (upGraphic != null)
                results.Add(upGraphic);
        }
 
        #endregion
经过此优化之后,应该能减少depth和canvasRenderer的调用。效果如下:

可见将其调用次数有较多的下降(477+477)--->(419+151),从而使耗时降低了不少。

3、get_canvas的优化

后面发现,在获取Canvas居然也这么耗时……真是郁闷。

仔细看了下性能,是因为在做缓存的Canvas==null的时候耗时1.28ms。我寻思着,这玩意在我们项目中从来没改过,何必一直判空呢?

所以干脆就在Awake里面给他赋值,干脆就不判空了算了。

代码修改如下:

protected override void Awake()
        {
            canvas= GetComponent<Canvas>();
        }
 
        private Canvas canvas;
优化效果:

可见其中给Canvas判空的消耗已经没有了。

后面的工作还是主要针对YHGraphicRaycaster.Raycast进行优化。毕竟他真用了最多的时间,将近一半了。

待续:
还有一部分对EventSystem的优化,在 这里 。

因为写在一篇里太长了,所以新开了一篇文章。经过第二次优化之后cpu耗时能下降到2ms左右,可喜可贺~~

后来又新增了优化的第三部分:https://blog.csdn.net/cyf649669121/article/details/86484168

点赞 7
————————————————
版权声明:本文为CSDN博主「魔术师Dix」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/cyf649669121/article/details/83661023

Unity UGUI优化:解决EventSystem耗时过长的问题 第一部分相关推荐

  1. Unity UGUI优化与原理【unity官方】

    来源( 来源:unity官方 Optimizing Unity UI ) 官方链接: [1]  https://unity3d.com/cn/learn/tutorials/temas/best-pr ...

  2. unity UGUI 优化

    一.图片打成图集 drawCall  打断问题 当渲染一组UGUI时,中间有使用其他图集(或者UGUI自带组件)时,会打断drawcall 的批处理 但,当unity检测到中间图集延后渲染不影响原有效 ...

  3. Unity Ugui 优化 DrawCall

    1.使用Frame Debug 2.不要出现 如果有 给他替换成一像素透明 常见的是 image里的source没有赋值 3.mask 有两次dc 并且里外不能合并 可以用rect mask 2d 代 ...

  4. Unity基础知识学习五,UGUI优化相关

    1.什么是UGUI优化,UGUI优化的理论基础 1.1理论基础 Canvas, 是Unity渲染系统给层状几何体( layered geometry  )提供的可以被画入.被放在上面或者放在世界空间的 ...

  5. Unity UGUI

    超详细的基础教程传送门:(持续更新中) Unity UGUI之Canvas&EventSystem:http://blog.csdn.net/qq992817263/article/detai ...

  6. Unity UGUI 性能优化

    简介主要性能消耗点 rebatch Rebatch发生在C++层面,是指Canvas分析UI节点生成最优批次的过程,节点数量过多会导致算法(贪心策略)耗时较长.对应SetVerticesDirty,当 ...

  7. Unity UGUI 之 实现 Text 文本文字过长,省略号显示(TextMeshPro 和常规 Text 二种方法)

    Unity UGUI 之 实现 Text 文本文字过长,省略号显示(TextMeshPro 和常规 Text 二种方法) 目录 Unity UGUI 之 实现 Text 文本文字过长,省略号显示(Te ...

  8. Unity UGUI 之 实现按钮 Button 长按和双击的功能效果

    Unity UGUI 之 实现按钮 Button 长按和双击的功能效果 目录 Unity UGUI 之 实现按钮 Button 长按和双击的功能效果 一.简单介绍 二.实现原理 三.注意事项 四.效果 ...

  9. 开发那些事儿:如何解决RK芯片视频处理编解码耗时很长的问题?

    流媒体视频直播包括以下几个步骤:采集->处理->编码和封装->推流到服务器->服务器流分发->播放器流播放. 在流媒体处理编码的过程中,会有硬解码和软解码两种播放方式.两 ...

  10. 如何快速优化手游性能问题?从UGUI优化说起

    WeTest 导读 本文作者从自身多年的Unity项目UI开发及优化的经验出发,从UGUI,CPU,GPU以及unity特有资源等几个维度,介绍了unity手游性能优化的一些方法. 在之前的文章< ...

最新文章

  1. 简述事件接口与事件适配器的联系与区别_设计模式——适配器模式
  2. Word自定义多级符号方法
  3. Mac上因磁盘格式导致gulp无限刷新问题
  4. CSP认证201509-1 数列分段[C++题解]:遍历
  5. SAP Spartacus全局配置里和路由Route相关的配置
  6. linux和python的关系_Python、Linux与我的缘分
  7. RTX5 | 线程管理01 - 创建线程(静态堆栈方式)
  8. 循环序列模型 —— 1.6 语言模型和序列生成
  9. 更灵活的定位内存地址的方法05 - 零基础入门学习汇编语言36
  10. linux串口进单用户模式,进入SUSE Linux Enterprise Server 12系统单用户模式的方法
  11. 第一次个人项目【词频统计】——PSP表格
  12. wireshark之不显示ip问题(五)
  13. 【Tenda腾达路由器限速图解教程】
  14. 《具体数学》部分习题解答2
  15. 智能会议系统集成解决方案
  16. 2019腾讯校园招聘面经
  17. Photoshop抠图(色彩范围命令扣人物/动物毛发图)
  18. vue全局引入openlayers_vue+openlayers绘制省市边界线
  19. webscraper多页爬取_Web Scraper 高级用法——Web Scraper 抓取多条内容 | 简易数据分析 07...
  20. Linux怎么完全删除一个用户

热门文章

  1. Julia: readdlm
  2. 阿里巴巴集团 CTO 约你聊聊这些事
  3. 新型肺炎数据,可以用Excel绘制成3维地图
  4. 【疫情模型】基于matlab改进SEIR模型【含Matlab源码 670期】
  5. 【图像隐写】基于matlab GUI DCT数字水印嵌入与提取【含Matlab源码 943期】
  6. 学习遗忘曲线_级联相关,被遗忘的学习架构
  7. openai-gpt_GPT-3 101:简介
  8. 意图识别 聊天机器人_如何解决聊天机器人中的意图冲突
  9. Java单链表中的元素互换位置_Java如何在链表的第一个和最后一个位置添加一个元素?...
  10. fastble找不到手机_Android蓝牙库FastBle的基础入门使用