ZMUIL6——高性能系统
高性能系统要做的工作
高性能系统是为了解决UI性能问题,增加游戏的流畅度而设计的一系列性能解决方案。
主要针对渲染、重绘、顶点、UI组件、等多个方面进行性能的优化处理。
他的功能有以下几点:
一键优化合批。 自动根据图集图片和相邻组件的特征进行重新排序。
避免使用SetActive引起的UI重绘和GC垃圾。 用CanvasGroup和Scale进行代替。
使用UI对象池。 避免频繁的克隆物体导致的卡顿和GC。(在之前的UIModule
里已经实现了,隐藏窗口不会直接销毁窗口对象,可复用)
智能化禁用不必要的组件属性。 从而来避免一些不必要的性能开销。
界面预加载。 针对复杂一些的界面我们可以使用预加载进行提前加载物体,来确保在真正使用界面时,能够流畅度加载出界面。
**高性能文字描边。 **Unity描边组件是拷贝4份相同的文本顶点数占用量巨大。
一个字母的Text加上Untiy的描边一共占用30个顶点。
而我们的Text同样是一个字母加上描边能做到只占用6个顶点。性能是Unity组件的5倍。 (这课没讲,不用记辣)
组件自动序列化。 避免掉使用Find接口查找组件带来的性能消耗,而使用自动化序列化的方式拿到组件,将性能消耗尽可能的降至最低。
(编写自动化系统时就已经实现了,使用组件数据脚本即可)
特点:最大的程度去降低UI在游戏中所消耗的性能问题,让我们做出来的游戏质量更好,游戏流畅度更高。
窗口预加载
就是将窗口的加载和窗口的弹出分离,在特定时间提前加载窗口而不显示它,达到预加载的结果,之后再显示窗口,使显示窗口更加顺畅
预加载在UIModule内实现,会调用窗口的OnAwake
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #region 预加载相关 public void PreLoadWindow <T >() where T : WindowBase, new (){ System.Type type = typeof (T); string windowName = type.Name; T windowBase = new T(); GameObject newWindowObj = TempLoadWindow(windowName); if (newWindowObj != null ) { windowBase.gameObject = newWindowObj; windowBase.transform = newWindowObj.transform; windowBase.Canvas = newWindowObj.GetComponent<Canvas>(); windowBase.Canvas.worldCamera = mUICamera; windowBase.Name = newWindowObj.name; windowBase.OnAwake(); windowBase.SetVisible(false ); RectTransform rectTrans = newWindowObj.GetComponent<RectTransform>(); rectTrans.anchorMax = Vector2.one; rectTrans.offsetMax = Vector2.zero; rectTrans.offsetMin = Vector2.zero; mAllWindowDic.Add(windowName, windowBase); mAllWindowList.Add(windowBase); } Debug.Log($"预加载窗口:{windowName} " ); } #endregion
优化显隐逻辑
检测网格重建行为
可以通过下面的脚本直接检查网格重建行为,随意挂载到一个对象上即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 using System;using System.Collections.Generic;using System.Reflection;using UnityEngine;using UnityEngine.UI;public class CanvasRebuildTest : MonoBehaviour { IList<ICanvasElement> mLayoutRebuildQueue; IList<ICanvasElement> mGraphicRebuildQueue; void Start () { Type type = typeof (CanvasUpdateRegistry); FieldInfo field = type.GetField("m_LayoutRebuildQueue" , BindingFlags.NonPublic | BindingFlags.Instance); mLayoutRebuildQueue = (IList<ICanvasElement>)field.GetValue(CanvasUpdateRegistry.instance); field = type.GetField("m_GraphicRebuildQueue" , BindingFlags.NonPublic | BindingFlags.Instance); mGraphicRebuildQueue = (IList<ICanvasElement>)field.GetValue(CanvasUpdateRegistry.instance); } void Update () { for (int i = 0 ; i < mLayoutRebuildQueue.Count; i++) { var rebuild = mLayoutRebuildQueue[i]; if (ObjectValidForUpdate(rebuild)) { Debug.LogFormat("{0}引起{1}网格重建" , rebuild.transform.name, rebuild.transform.GetComponent<Graphic>().canvas.name); } } for (int i = 0 ; i < mGraphicRebuildQueue.Count; i++) { var rebuild = mGraphicRebuildQueue[i]; if (ObjectValidForUpdate(rebuild)) { Debug.LogFormat("{0}引起{1}网格重建" , rebuild.transform.name, rebuild.transform.GetComponent<Graphic>().canvas.name); } } } private bool ObjectValidForUpdate (ICanvasElement element ) { var valid = element != null ; var isUnityObject = element is UnityEngine.Object; if (isUnityObject) { valid = (element as object ) != null ; } return valid; } }
检查发现,直接使用SetActive
方法控制窗口的显隐会触发网格重建,导致不必要的性能消耗,
而CanvasGroup
改变alpha值和改变Transform
的缩放不会触发网格重建
因此,我们可以调整CanvasGroup
改变alpha值和改变Transform
的缩放来控制UI对象的显示隐藏,性能更好
窗口的显隐
我们为每个窗口都添加一个CanvasGroup
,通过改变其alpha值为0或1,来控制窗口的隐藏或显示
将WindowBase
的设置可见性方法改成调整CanvasGroup
的逻辑
1 2 3 4 5 6 7 8 9 10 private CanvasGroup mCanvasGroup; public override void SetVisible (bool isVisible ){ mCanvasGroup.alpha = isVisible ? 1f : 0f ; mCanvasGroup.blocksRaycasts = isVisible; Visible = isVisible; }
UI对象的显隐
我们可以编写一个UGUIAgent
,在其中编写一系列扩展方法,拓展一种调整缩放来控制显隐的方法,为UI控件的显隐提供新的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 using UnityEngine;using UnityEngine.UI;public static class UGUIAgent { public static void SetVisible (this GameObject obj, bool visible ) { obj.transform.localScale = visible ? Vector3.one : Vector3.zero; } public static void SetVisible (this Transform transform, bool visible ) { transform.localScale = visible ? Vector3.one : Vector3.zero; } public static void SetVisible (this Button button, bool visible ) { button.transform.localScale = visible ? Vector3.one : Vector3.zero; } public static void SetVisible (this Text text, bool visible ) { text.transform.localScale = visible ? Vector3.one : Vector3.zero; } public static void SetVisible (this Slider slider, bool visible ) { slider.transform.localScale = visible ? Vector3.one : Vector3.zero; } public static void SetVisible (this Toggle toggle, bool visible ) { toggle.transform.localScale = visible ? Vector3.one : Vector3.zero; } public static void SetVisible (this InputField inputField, bool visible ) { inputField.transform.localScale = visible ? Vector3.one : Vector3.zero; } public static void SetVisible (this RawImage image, bool visible ) { image.transform.localScale = visible ? Vector3.one : Vector3.zero; } public static void SetVisible (this ScrollRect scrollRect, bool visible ) { scrollRect.transform.localScale = visible ? Vector3.one : Vector3.zero; } }
智能禁用RaycastTarget
很多单纯的Image和Text是不接收用户输入的,因此将其RaycastTarget开着只能浪费性能
我们可以检测新创建出来的Text, Image, Raw Image,将其RaycastTarget属性自动关闭,省去我们手动关闭的麻烦
也不影响其他用于Button等的Image的射线检测正常执行
实现思路如下:
在编辑器模式下,每当Hierarchy窗口发生改变,就检测选中的物体(一般都是新创建的UI对象)名字是否包含Text, Image字样,一旦存在自动关闭其RaycastTarget属性
注:不对Text Mesh Pro生效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 using System.Collections;using System.Collections.Generic;using UnityEditor;using UnityEngine;using UnityEngine.UI;public class SystemUIEditor : Editor { [InitializeOnLoadMethod ] private static void InitEditor () { EditorApplication.hierarchyChanged += HanderTextOrImageRaycast; } private static void HanderTextOrImageRaycast () { GameObject obj = Selection.activeGameObject; if (obj != null ) { if (obj.name.Contains("Text" )) { Text text = obj.GetComponent<Text>(); if (text != null ) { text.raycastTarget = false ; } } else if (obj.name.Contains("Image" )) { Image image = obj.GetComponent<Image>(); if (image != null ) { image.raycastTarget = false ; } else { RawImage rawImage = obj.GetComponent<RawImage>(); if (rawImage != null ) { rawImage.raycastTarget = false ; } } } } } private static void LoadWindowCamera () { if (Selection.activeGameObject != null ) { GameObject uiCameraObj = GameObject.Find("UICamera" ); if (uiCameraObj != null ) { Camera camera = uiCameraObj.GetComponent<Camera>(); if (Selection.activeGameObject.name.Contains("Window" )) { Canvas canvas = Selection.activeGameObject.GetComponent<Canvas>(); if (canvas != null ) { canvas.worldCamera = camera; } } } } } }
一键自动优化Batchs
与NGUI的优化Batchs类似,它会将重新为同层级的对象进行排序,将同图集的对象排列到一起,将Text对象统一排列到末尾
以最大化的降低Batchs
通过导入UGUI-Editor这个开源库,即可使用该功能
悲报:
看了一眼这个UGUI-Editor在GitHub上的库,已经久未更新了,将该库导入到2021版的Unity出现了不少过时警告,但还是能用
使用其PrafabWin窗口也有轻微显示问题,笔者难以保证这个库的其他功能还能在未来的版本里正常使用