添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
用SetActive或者设置enabled隐藏UI界面的缺陷很多人都讲过了,无非是"无谓的操作"和GC Alloc的问题。我这里多说一句,设置enabled最要命的地方其实是,UI基类Graphic会在OnEnabled,OnDisable,还有OnTransformParentChanged时执行SetAllDirty(),这个操作几乎等同于全部重建。我并不理解这样做的理由,虽然在禁用状态下无法接受Mono事件,无法根据属性变化更新界面,重新激活的时候确实需要一次ReBuild,但并不需要直接设置脏标记啊,这东西不是在具体属性变化的时候才设置的吗?而在没有脏标记的时候ReBuild只是一次空循环,虽然Canvas.BuildBatch的消耗跑不掉,Canvas.sendWillrenderCanvas起码不会多,当大量界面同时激活时,后者的耗时才是大头。
(Canvas.sendWillrenderCanvas是一个事件,它上面挂载了CanvasUpdateRegistry.PerformUpdate,会根据属性变化重新处理Image的网格并更新CanvasRenderer,而Canvas.BuildBatch指的是Canvas根据全部CanvasRenderer进行网格合并的C++部分。前者只更新变化,但效率低,后者必须处理全部,但效率高。之后我说的“重建”都指的前者。)
这里要专门提一句,不仅仅是OnEnabled,OnTransformParentChanged也会执行几乎相同的内容,主要的优点也就是少了一次GetCompent<Mask>()的GC Alloc,界面还是会重建,将界面移出RootCanvas这种方案优化幅度并不大。有差距大的说法,有可能是在编辑器里拖动层次的时Profiler显示的假象。不要拖拽,要用代码来修改transform的parent,至少我这没看出两者明显的区别,而且从源码分析两者有差异是不可思议的。
对于用Canvas包裹的界面而言,直接禁用Canvas组件反而是一个办法,它虽然会ReBuild,但不会让子级设置脏标记,就还OK,虽然是有一点消耗,但比前面的方法强。最彻底的做法是设置UI摄像机,并设置要隐藏Canvas的Layer让摄像机Cull掉(注意UGUI是以Canvas为渲染单位的,设置单独UI组件的Layer无效),这才是真正的零消耗。再把GraphicsRaycaster一并禁用就好了。
注意这种做法对Canvas内的Canvas是同样有效的。

但这都是针对“一个窗体”这样的界面而言的,如果我只是想暂时隐藏某个UI上的图片呢?

其实只要设置image.canvasRenderer.cull = true就可以了

当然仅仅是这样我也不会单独拿一篇文章来讲这个事。

CanvasRenderer.cull其实是用来配合ugui自己的RectMask2D(注意不是Mask)的功能,本身是没打算让我们来操作的,只是碰巧写成了public而已。但只要你这个Image不在RectMask2D内,完全可以把这个属性当作visible随便使用。

然而如果你就是想用RectMask2D,想利用它的区域裁剪功能(Mask不会裁剪区域外的UI对象,有多少就会渲染多少,而RectMask2D则只会渲染内部的元素),不愿意用Mask,那就……

不好办了,因为RectMask2D也会修改这个cull的值,会覆盖你Image的设置。

怎么办呢,只能扩展了。

写这个东西遇到了很多private和非虚方法的问题,只能算勉强完成。还复制了一部分原始代码,搞不好UGUI更新后就会完蛋。

然而也仅仅实现了在RectMask2D下正常设置cull这一件事……个人觉得挺不值的。RectMask2D完全可以不用啊。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace UnityEngine.UI
    [AddComponentMenu("UI/AdvancedImage", 11)]
    public class AdvancedImage : Image
        [SerializeField] private bool m_Visible = true;
        public bool Visible
                return m_Visible;
                m_Visible = value;
                UpdateVisible();
        protected override void OnEnable()
            base.OnEnable();
            UpdateVisible();
        #region 修正占用canvasRenderer.cull属性和Mask产生的冲突
        readonly Vector3[] m_Corners = new Vector3[4];
        private Rect rootCanvasRect
                rectTransform.GetWorldCorners(m_Corners);
                if (canvas)
                    Canvas rootCanvas = canvas.rootCanvas;
                    for (int i = 0; i < 4; ++i)
                        m_Corners[i] = rootCanvas.transform.InverseTransformPoint(m_Corners[i]);
                return new Rect(m_Corners[0].x, m_Corners[0].y, m_Corners[2].x - m_Corners[0].x, m_Corners[2].y - m_Corners[0].y);
        private bool m_IsCull;
        public override void Cull(Rect clipRect, bool validRect)
            if (!canvasRenderer.hasMoved)
                return;
            var cull = !validRect || !clipRect.Overlaps(rootCanvasRect, true);
            var cullingChanged = m_IsCull != cull;
            m_IsCull = cull;
            UpdateVisible();
            if (cullingChanged)
                onCullStateChanged.Invoke(cull);
                //SetVerticesDirty();
                //重新移回屏幕执行SetVerticesDirty会导致重建。重建确实是必要的,因为在Mask外任何属性修改都不会导致ReBuild,移入后需要执行一次让界面更新。
                //但是,仅仅是移出Mask是不应该标记Vertices变动的,根本没变啊,所以换成下面这句。这样仅仅是进出Mask就不会重建Mesh了。没看出有啥区别。
                if (IsActive()) CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);
        public override void SetClipRect(Rect clipRect, bool validRect)
            base.SetClipRect(clipRect, validRect);
            if (!validRect)
                m_IsCull = false;//移出Mask需要手动清除Cull标记
        public override void RecalculateClipping()
            base.RecalculateClipping();
            UpdateVisible();
        protected override void OnTransformParentChanged()
            base.OnTransformParentChanged();
            if (!isActiveAndEnabled)
                return;
            UpdateVisible();
        #endregion
        public void UpdateVisible()
            canvasRenderer.cull = m_IsCull || !m_Visible;
#if UNITY_EDITOR
        protected override void OnValidate()
            base.OnValidate();
            UpdateVisible();
#endif

嗯……对应编辑器代码。不过编辑器内直接点属性界面会强制SetAllDirty(),所以用属性界面改visible是看不到性能和以前的区别的……你得自己写代码改visible。

using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
using System.Collections;
namespace UnityEditor.UI
    [CustomEditor(typeof(AdvancedImage), false)]
    [CanEditMultipleObjects]
    public class AdvancedImageEditor : ImageEditor
        SerializedProperty m_Visible;
        protected override void OnEnable()
            base.OnEnable();
            m_Visible = serializedObject.FindProperty("m_Visible");
        public override void OnInspectorGUI()
            base.OnInspectorGUI();
            EditorGUILayout.PropertyField(m_Visible);
            serializedObject.ApplyModifiedProperties();

待会写一个完整的扩展Image好了,有一些更有意义的东西可以做。

……然而这个没卵用的东西我都不想留下了,不用RectMask2D不就好了么?现在都是循环列表,哪有什么Mask外的内容。而且这个RectMask2D,即使我已经改了注释那里的代码,避免了Canvas.sendWillrenderCanvas的执行,滚动时还是会导致不必要的Canvas.BuildBatch执行啊。

至于Text和RawImage的版本???

——————————————————

今天再翻了下代码,个人认为UGUI设计者非要在OnEnabled的时候每次重建界面,也就两个理由:

1.最开始的时候CanvasRenderer没有cull属性,必须清空CanvasRenderer才能做到隐藏界面的效果。清空后恢复就只能重建。

2.认为被禁用的CanvasRenderer一直留着不显示的Mesh和Material是在浪费内存。

这种做法是否合理,相信大家心里都有数。

然而,Cull方法那里的SetDirty则直接是一个BUG了,因为这次它隐藏的时候并没有清空CanvasRenderer,重建是一丁点意义都没有的行为,用CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this)代替才是正确的写法。

用SetActive或者设置enabled隐藏UI界面的缺陷很多人都讲过了,无非是"无谓的操作"和GC Alloc的问题。我这里多说一句,设置enabled最要命的地方其实是,UI基类Graphic会在OnEnabled,OnDisable,还有OnTransformParentChanged时执行SetAllDirty(),这个操作几乎等同于全部重建。我并不理解这样做的理由,虽然在禁用状态下无法接受Mono事件,无法根据属性变化更新界面,重新激活的时候确实需要一次ReBuild,但并不需要直接设置脏标记啊
在使用UGUI的项目中经常遇到诸如0x0000000140044A66 (Unity) Unity::GameObject::IsActive 0x00000001411B2E73 (Unity) UI::CanvasRenderer::SyncDirtyElements 0x00000001411BFAC9 (Unity) UI::CanvasManager::UpdateDirtyRende
using UnityEngine.EventSystems; using UnityEngine.UI; using static UnityEngine.ParticleSystem; public class UIRenderer3D : UIBehaviour
一、Canvas简介 Canvas画布是承载所有UI元素的区域。Canvas实际上是一个游戏对象上绑定了Canvas组件。所有的UI元素都必须是Canvas的自对象。如果场景中没有画布,那么我们创建任何一个UI元素,都会自动创建画布,并且将新元素置于其下。 二、Canvas画布参数与应用 1.创建画布   **当你创建任何一个UI元素的时候,都会自动创建画布。**也可以主动创建一张画布:点击Ga...
Unity开发——CPU优化之UI模块CPU优化之UI模块1.1UGUI1.1.1 网格重建流程图1.1.2 canvasRenderer.cull1.1.3 cull的变化原因(源码讲解)1.1.4 如何减少脏数据生成1.2NGUI CPU优化之UI模块 1.1UGUI UGUI在CPU的损耗主要是在网格重建,例如Image,Text等UI元素的Enable及UI元素的长、宽或Color属性的变...
MaskableGraphic MaskableGraphic是一个抽象类,继承了Graphic, IClippable, IMaskable, IMaterialModifier接口,派生了RawImage,Image和Text。 OnEnable方法 设置重新计算模板m_ShouldRecalculateStencil为true,更新裁剪的父对象UpdateClipParent,设置Ma...