1、UI DrawCall分析工具
最近只针对项目中主界面的UI进性DrawCall的优化,主要用到的工具就是Unity自带的Frame Debugger,我们项目使用的是Sprite Packer打图集,如果是从本地加载资源,如图1-1,首先要确保Project Settings中Sprite Packer Mode为Always Enabled,保证游戏运行时所有的图标打到对应的图集中。如果使用AssetBundle方式加载,打包的时候应该已经打成图集了,该项设置不设置就无所谓了。
Frame Debugger打开方式Window->Analysis->Frame Debugger(我用的Unity版本是201848),打开后界面如下:
点击后就会展示游戏中所有的DrawCall信息,如图1-3,Unity UI绘制调用的位置在CameraRender下Render的子组CanvasRenderSubBatch下,后边的68即为UI占用的DrawCall数量。Unity UI绘制调用的位置取决于Canvas的Render Mode,我们使用的Render Mode为Screen Space-Camera。如果Render Mode设置为Screen Space - Overlay,则Ui绘制的位置在CanvasRenderOverlay组中,如图1-4所示;如果Render Mode设置为World Space时也是在CameraRender下Render的子组中,虽然都是子Render,但是和Screen Space-Camera模式会在不同的子组中。
选中某个Draw Mesh右侧就会显示该详细信息,如图1-5,选中的是一个Image,其中1表示该Image渲染的渲染层级为203,2表示该组件使用的shader为UI/Default,3表示Image所在的图集为Common(Group 1)。
再看一个Text的详细信息,如图1-6,基本与Image相同,只是使用的纹理不同。
2、影响Draw Call的因素
(1) 针对Image,需要保证使用的在同一图集且同一个Group中,且使用的材质相同。
a、 如下图所示,一般的Image都不会设置Material即Material为None,此时使用的就是UGUI默认的材质,如图1-4中的2标记的位置,都是默认UI/Default,所以材质的问题一般不需要考虑;
b、 重点考虑的就是图集问题,这是比较麻烦的问题,需要根据使用的范围来规划图集。开始我们把所有主界面用到的都放到一个图集中,但是由于个数太多,而图集又有大小上限(我们项目中设置的为10241024),虽然会打到同一个图集中,但是会被分到不同的Group中。通过Frame Debugger查看即使在同一个图集中,不在同一个Group也不能合批。所以直接把所有放在一个图集中并不能解决问题,还要根据UI进行细分,同一个UI中使用的打到一个图集里,或几个UI中用到的图集打到一个图集中。好多个UI公用的单独放到一个公用的图集中。为了便于规划图集,我还写了个小工具统计每个在各个UI中的使用情况。统计结果如图2-2,每一行显示某个的引用次数,次数后紧跟引用该的UI。
[MenuItem("策划/UI/收集UI中引用情况")]
public static void GetSpriteReferCount()
{
Dictionary<string, Dictionary<string, bool>> spriteToPrefab = new Dictionary<string, Dictionary<string, bool>>();
List<string> allFullPath = MUEditorUtilityGetAllFullPathIn(UI_ASSET_DIR, "prefab");
for (int i = 0; i < allFullPathCount; i++)
{
EditorUtilityDisplayProgressBar("检测", "正在收集UI中的引用情况……", i / (float)allFullPathCount);
string assetPath = MUEditorUtilityFullPathToAssetPath(allFullPath[i]);
GameObject uiGameObj = AssetDatabaseLoadAssetAtPath<GameObject>(assetPath);
if (uiGameObj == null)
{
continue;
}
Component[] allImageComponents = uiGameObjtransformGetComponentsInChildren(typeof(Image), true);
for (int j = 0; j < allImageComponentsLength; j++)
{
Image t = allImageComponents[j]GetComponent<Image>();
if (tsprite != null)
{
string spriteName = tspritename;
if (spriteToPrefabContainsKey(spriteName) == false)
{
spriteToPrefab[spriteName] = new Dictionary<string, bool>();
}
if (spriteToPrefab[spriteName]ContainsKey(uiGameObjname) == false)
{
spriteToPrefab[spriteName][uiGameObjname] = true;
}
}
}
}
Dictionary<string, Dictionary<string, bool>> spriteToPrefab1 = spriteToPrefabOrderBy(o => oValueCount)ToDictionary(p => pKey, o => oValue);
DirectoryInfo dirInfo = DirectoryCreateDirectory(ApplicationdataPath + "/Res/Gui");
FileInfo[] files = dirInfoGetFiles("UI_Sprite_Refertxt");
foreach (var file in files)
{
FileDelete(fileFullName);
}
using (FileStream fileStream = FileCreate(dirInfoFullName + "/UI_Sprite_Refertxt"))
{
string txt = "";
string temp = "";
foreach (KeyValuePair<string, Dictionary<string, bool>> kv1 in spriteToPrefab1)
{
temp = kv1Key + ":";
//temp += " " + GetParentDir(kv1Key);
temp += " " + kv1ValueCount;
foreach (KeyValuePair<string, bool> kv2 in kv1Value)
{
temp += " " + kv2Key;
}
txt += temp;
txt += "\n";
}
StreamWriter writer = new StreamWriter(fileStream);
writerWrite(txt);
writerFlush();
writerClose();
fileStreamClose();
}
EditorUtilityClearProgressBar();
}
c、 在打图集的过程中还遇到一个问题,同一图集中的一个,在总大小没有超过图集上限的情况下,却被打到了该图集的另一个Group中。使用Frame Debugger也看不出问题。然后我就用Sprite Packer在本地打一下图集看一下,这两个Group区别在哪儿。通过对比发现两个Group中的格式不一样,一个为RGBA Compressed DXT5,一个为RGB Compressed DXT1,如图2-3和2-4。
原来Unity打图集时只能将相同格式的打到同一Group中。查看被打到另一个Group中的,发现这张图没有Alpha通道,工程中所有平台的纹理Format都设置为Automatic,Alpha Source设置为Input Texture Alpha,即Unity会根据原图是否有Alpha通道自动设置纹理格式,如图2-5。
我们可以通过重写不同平台上的格式设置,保证该格式与其他格式一致,如图2-6。
(2) 针对Text,保证相邻的Text使用相同字体,相同材质,这个一般情况都可以做到。
(3) 保证可以合批的元素在同一个Canvas下。
为了防止某些动态元素(如倒计时、技能CD等)的改变导致整个UI的网格重建,一般采用动静分离的方式,将动态元素放在单独的Canvas下。这时可能误将一些本可以合批的静态元素放到了不同的Canvas下。我在优化时就遇到了这种情况。如图2-7所示,Image_Kuang和Image_Parent位于同一渲染层级,且他们挂的在同一图集里,但由于Image_Parent节点下有动态变化元素,所以单独挂了Canvas,导致这两个Image不能合批。解决方式就是将Image_Parent的Image移除挂到一个新建的与Image_Kuang位于同一Canvas的节点下,如图2-8所示,在Image_Kuang下单独建了一个Image_PosBack节点挂载Image。
(4) Mask和Rect Mask 2D会将内外元素分离导致不能合批。另外能用Rect Mask 2D解决的尽量不要用Mask,Mask除了使内外元素不能合批,本身也会占用2个DrawCall。Mask和Rect Mask 2D比较如下:
a、 Mask占用两个DrawCall,一个在底下设置Stencil Buffer,一个在顶上还原Stencil Buffer,Mask下的子元素夹在中间本身不占DrawCall;Rect Mask 2D本身不占DrawCall
b、 如果多个Mask绑定的Image组件,属于同一个Atlas,那么Mask之间的元素可以进行合并(包括Mask自己产生的2个DrawCall);否则不能合并如果RectMask2D上绑定了Image,那么多个RectMask2D的Image如果属于同一个Atlas可以合并
c、内外元素 无法合批无法合批
d、完全裁剪元素:Mask 依然占据DrawCall;Rect Mask 2D不占用DrawCall,也不参与Depth计算;
e、Hierarchy 中被分割元素:Mask 可以正常合批;Rect Mask 2D:如果Depth、Atlas与RectMask2D下的某元素相同,则无法合批。
f、裁剪掉的部分:Mask 还会影响其他元素的Depth计算,而它自己的也会受到其他元素的影响。Rect Mask 2D 依然参与Depth计算
g、 多个Mask内的UI节点间如果符合合批条件,可以合批。RectMask2D之间无法合并DrawCall。
(5) UI元素Position或者Rotation改变
a、 Position的Z值不为0,元素会被视为3DUI,不参与合批,如果父节点Z!=0,其下的子元素都无法合批。
b、 Rotation的X或Y修改,导致元素不在UI平面,则无法合批,原因和Position的Z值改变一样。
(6) UI元素重叠和层级深度
这个最终要的结果就是使多个元素的渲染层级相同,就达到合批目的了,如图1-4中1标记的位置。当然这一项的调整也是最耗时最需要耐心的,这就需要我们去分析每个UI中各个元素在Hierarchy中的层级。
下面拿一个例子说明一下,如图2-9所示,图中左侧红框内4个元素,其中Image_Back、Drop_Image、Btn_Image所用在一个图集中,Drop down使用的在其它图集中。这时所有的元素占用3个DrawCall,查看右侧Draw Mesh发现Image_Back和Drop_Image共占一个DrawCall,而Btn_Image和Dropdown分别单占一个DrawCall。
下面我们调整一下Btn_Image的位置,如图2-10所示,整体的Draw Call变成2,Image_Back、Drop_Image、Btn_Image三个元素共占一个DrawCall。Dropdown单独占一个DrawCall。这是因为开始时Dropdown夹在Drop_Image和Btn_Image中间,由于它属于另外的图集,破坏了Drop_Image和Btn_Image的合批。
Unity自带的Profiler工具中可以查看合批中断的原因。如图2-11所示,打开Profiler,滑动到最底部可以看到两个和UI相关的选项,一个UI,一个UI Details,选中任意一个,下边有全部Canvas的一个列表,点击展开任意一个Canvas,就可以看到该Canvas下所有的批处理信息,其中有一列Batch Breaking Reason显示了该批次不能与上一个批次合批处理的原因,合批被破坏的主要原因有两个:Different Texture:即使用的纹理不同,可能是不在同一个图集,也可能是Image和Text相邻导致;Different Material Instance:材质不同,一般由于自己给Image使用了自定义材质或者游戏中使用了Text Mesh Pro UGUI。另外GameObject Count为该批次处理对象的个数;GameObjects列列出了所有对象,可能由于对象太多只显示有几个Objects,这是可以双击这一行,会直接选中该批次处理的所有对象,如图2-12所示。
另外看到一个Cumulative Batch Count:累计批次数量。如图2-11,我们可以看到该列的总值为82,我们再看一下Frame Debugger中 Draw Mesh的个数,如图2-13为69。这两个数不一样,Frame Debugger中显示的是当前帧Batch个数,而Cumulative Batch Count是从开始游戏运行总的批次个数,在游戏过程中有些元素的改变导致批处理的变化,所以这两个数会不一样。
(7) 对于Alpha为0的Image,需要勾选其CanvasRender组件上的Cull Transparent Mesh选项,否则依然会产生DrawCall且容易打断合批。
(8)空Image
有时会使用空的Image来接受点击事件,空Image也会产生DrawCall。我们可以使用Empty4Raycast组件替代空Image。
需要在头文件里面定义一下
我圈中的那哪里,不能没办法获取到image组件
using UnityEngine;
using SystemCollections;
using UnityEngineUI;
using UnityEngineSprites;
public class OnButton : MonoBehaviour {
private Button button;
private Image image;
void Start () {
button=thisGetComponent<Button>();
image=thisGetComponent<Image>();
}
private void ButtonDown()
{
buttoninteractable=false; imageoverrideSprite=ResourcesLoad("Textures/TechBlue/background",typeof(Sprite))as Sprite;//这里就是修改他的,
imagecolor=Colorred;
}
}
最后记住,在哪里我们需要换一下类型,不然没办法修改他的
一、 UGUI布局系统由 布局元素 (Layout Elements)和 布局控制器 (Layout Controller)组成。
1、布局控制器的基础接口为ILayoutController。
(1)ILayoutController有两个方法,布局系统调用这两个方法的顺序是固定的,首先调用SetLayoutHorizontal,再调用SetLayoutVertical:
A、void SetLayoutHorizontal() :处理水平方向的布局;
B、void SetLayoutVertical() :处理垂直方向的布局。
(2)继承ILayoutController的两个接口:
A、ILayoutSelfController :只控制自己RectTransform的改变;继承ILayoutSelfController的两个类如下:
[1] (内容尺寸适配器) Content Size Fitter <—— ILayoutSelfController:根据孩子中布局元素的尺寸及自身包含内容的多少控制自身的尺寸;
[2] (纵横比适配器) AspectRatioFitter <—— ILayoutSelfController:根据自身设置的模式(AspectMode)和比率(Aspect Ratio)以及宽或高调整另一边的大小,不受子元素的影响。
B、ILayoutGroup :控制所有孩子节点的布局变化。这个接口就是我们比较熟悉的接口,平时项目中使用的很多布局组件都继承该接口:
[1] ScrollRect <—— ILayoutGroup;
[2] GridLayoutGroup <—— LayoutGroup <—— ILayoutGroup;
[3] HorizontalLayoutGroup\VerticalLayoutGroup->HorizontalOrVerticalLayoutGroup <—— LayoutGroup <—— ILayoutGroup
2、所有包含RectTransform组件的GameObject都视为布局元素。
(1)布局元素拥有一些定义自身尺寸的属性:
A、Minimum width:最小宽度;
B、Minimum height:最小高度;
C、Preferred width:如果有充足的空间可以分配的最合适宽度;
D、Preferred height:如果有充足的空间可以分配的最合适高度;
E、Flexible width:灵活宽度,一般是相对于父元素的比例;
F、Flexible height:灵活高度,一般是相对于父元素的比例。
默认情况下,Minimum width、Minimum height、Preferred width、Preferred height均为0,Flexible width、Flexible height都是disabled的。如果想调整这些属性就需要添加继承ILayoutElement接口的组件重写这些值。像我们常用的Text、Image组件都继承了ILayoutElement;同时一些布局控制器自身也继承了ILayoutElement,如:LayoutGroup(包括所有继承LayoutGroup的子类)、ScrollRect;UISystem也创建了一个单独组件LayoutElement继承ILayoutElement,任何一个想重写上述属性的GameObject都可以通过添加LayoutElement组件实现。LayoutElement同时继承了ILayoutIgnorer接口,可以通过设置ignoreLayout决定该GameObject是否被Layout System忽略。
(2)ILayoutElement定义了布局元素用到的属性minWidth、preferredWidth、flexibleWidth、minHeight、preferredHeight、flexibleHeight,
同时定义了两个方法:
void CalculateLayoutInputHorizontal():计算布局元素的minWidth,preferredWidth和flexibleWidth值
void CalculateLayoutInputVertical():计算布局元素的minHeight,preferredHeight和flexibleHeight值
二、布局重建过程
1、首先引起布局重建的元素调用LayoutRebuilderMarkLayoutForRebuild() ,然后逐层向上递归查找直到找到最上层的布局根节点,这个根节点必须包含继承ILayoutGroup接口、Behaviour类并且isActiveAndEnabled属性为true的组件。然后以这个根节点初始化一个布局重建器LayoutRebuilder,然后将该LayoutRebuilder添加到CanvasUpdateRegistry中m_LayoutRebuildQueue队列中等待处理。调用过程如下:
static void LayoutRebuilderMarkLayoutForRebuild(RectTransform rect)
->static void LayoutRebuilderMarkLayoutRootForRebuild(RectTransform controller)
->static bool CanvasUpdateRegistryTryRegisterCanvasElementForLayoutRebuild(ICanvasElement element)
-> CanvasUpdateRegistryInternalRegisterCanvasElementForLayoutRebuild(ICanvasElement element)
-> 加入m_LayoutRebuildQueue队列中。
其中有一个比较特殊的就是ScrollRect,在ScrollRect的OnEnable()方法和SetDirtyCaching()方法中会先调用CanvasUpdateRegistryRegisterCanvasElementForLayoutRebuild(this),再调用LayoutRebuilderMarkLayoutForRebuild(rectTransform)。与LayoutRebuilder比较,ScrollRect本身也继承了ICanvasElement接口。在Rebuild()函数中,ScrollRect针对CanvasUpdatePrelayout(布局执行前)和CanvasUpdatePostLayout(布局执行后)两个阶段实现了自己的执行逻辑。而LayoutRebuilder的Rebuild()函数只针对CanvasUpdateLayout阶段实现对应的执行逻辑
2、CanvasUpdateRegistry构造函数中会将PeformUpdate方法加入CanvaswillRenderCanvases事件中,然后在渲染所有的Canvas之前,抛出willRenderCanvases事件从而调用PeformUpdate(),在PeformUpdate函数中执行步骤如下:
(1)首先会删除m_LayoutRebuildQueue中所有无效的元素;
(2)然后根据节点的深度(父节点个数越大越靠前)对m_LayoutRebuildQueue排序;
(3)然后分别以CanvasUpdatePreLayout(布局前),CanvasUpdateLayout(布局),CanvasUpdatePostLayout(布局后)的参数顺序调用每一个ICanvasElement元素的Rebuild方法;
(4)然后调用所有ICanvasElement元素的LayoutComplete()方法。针对该方法只有LayoutRebuilder类中实现:将该LayoutRebuilder从LayoutRebuilder的静态对象池s_Rebuilders中删除;
(5)然后清空m_LayoutRebuildQueue队列;
(6)布局结束后调用ClipperRegistryinstanceCull()遍历所有裁剪组件(继承IClipper接口的组件,如RectMask2D)的裁剪方法PerformClipping()。
3、下面看一下ICanvasElement布局重建过程的实现,重建的实现就在LayoutRebuilder的Rebuild方法中,源码如下:
如上所示,整个布局分四个步骤:
(1)对该节点下所有有效(继承Behaviour且isActiveAndEnabled为true)的布局元素(即包含继承ILayoutElement组件的节点)执行CalculateLayoutInputHorizontal()计算水平方向布局尺寸。对孩子的遍历顺序由下往上,因为父节点尺寸计算依赖于子节点的尺寸;虽然针对所有布局元素,但是真正实现CalculateLayoutInputHorizontal()方法的只有LayoutGroup相关的组件。
LayoutGroup实现了一个虚方法CalculateLayoutInputHorizontal(),主要就是收集其子节点下所有没有被标记 ignoreLayout 的物体存到m_RectChildren列表中。LayoutGroup的子类重写该函数时都会先调用这个虚函数,然后再执行自己特有的逻辑。
A、 水平布局组件 HorizontalLayoutGroup <—— HorizontalOrVerticalLayoutGroup <—— LayoutGroup
重写CalculateLayoutInputHorizontal()。如上,首先调用LayoutGroup中CalculateLayoutInputHorizontal()方法,然后调用HorizontalOrVerticalLayoutGroup中的CalcAlongAxis(int axis, bool isVertical)方法,CalcAlongAxis()是一个公用方法,可以根据传入的参数确定计算哪个方向的尺寸,其中axis表示方向(0:horizontal 1:vertical),isVertical判断是否一个Vertical Group。CalcAlongAxis()执行过程如下:
① 首先遍历所有孩子节点调用GetChildSizes()方法获取对应方向上的尺寸(min、preferred、flexible);
② 然后根据所有子节点的尺寸计算当前节点对应方向上的尺寸totalMin、totalPreferred、totalFlexible;
③ 调用SetLayoutInputForAxis()方法将第二步计算的值设置对应的向量m_TotalMinSize、m_TotalPreferredSize、m_TotalFlexibleSize;
B、垂直布局组件VerticalLayoutGroup 重写方法如下,实现参考HorizontalOrVerticalLayoutGroup
(2)对该节点下所有布局控制器(即包含继承ILayoutController组件的节点)执行SetLayoutHorizontal()设置水平布局。对孩子的遍历顺序自上而下,因为孩子节点实际的布局依赖于父节点的布局;
A、HorizontalLayoutGroup和VerticalLayoutGroup中的SetLayoutHorizontal()会直接调用HorizontalOrVerticalLayoutGroupSetChildrenAlongAxis()方法根据布局方向和布局组件类型设置其下子布局元素的位置和尺寸。
B、GridLayoutGroup也重写了SetLayoutHorizontal()方法,直接调用SetCellsAlongAxis(0),针对水平布局实现如图2-4所示,只设置每个子元素的尺寸,而不会改变子元素的位置。
(3)对该节点下所有布局元素(即包含继承ILayoutElement组件的节点)执行CalculateLayoutInputHorizontal()计算垂直方向布局尺寸;对孩子的遍历顺序由下往上;
(4)对该节点下所有布局控制器(即包含继承ILayoutController组件的节点)执行SetLayoutVertical()设置垂直布局。对孩子的遍历顺序自上而下;
从上面的执行顺序还可以看出,Layout System先搞定水平方向的布局然后才去设置垂直方向的布局。
三、布局重建触发因素
1、 以下函数,都会直接或间接的调用LayoutBuilderMarkLayoutForRebuild()触发布局重建:
(1)OnEnable() :所有继承ILayoutController或ILayoutElement组件(AspectRatioFitter除外)的OnEnable()方法都会都会间接调用LayoutBuilderMarkLayoutForRebuild();
A、Graphic :OnEnable() -> SetAllDirty() -> ( m_SkipLayoutUpdate变量为false时才会调用 )LayoutBuilderMarkLayoutForRebuild();
B、LayoutGroup\ContentSizeFitter\LayoutElement\ScrollRect :OnEnable() -> SetDirty() -> LayoutBuilderMarkLayoutForRebuild()。
(2)OnDisable() :所有继承ILayoutController或ILayoutElement组件的OnDisable()方法都会调用LayoutBuilderMarkLayoutForRebuild()
(3)OnRectTransformDimensionsChange(): RectTransform尺寸变化时调用。继承自UIBehaviour的ContentSizeFitter、LayoutGroup、ScrollRect、Graphic都重写了该方法,具体调用如下:
A、ContentSizeFitter :OnRectTransformDimensionsChange() ->SetDirty() -> (IsActive()为true才会调用)LayoutRebuilderMarkLayoutForRebuild();
B、LayoutGroup :OnRectTransformDimensionsChange() ->(isRootLayoutGroup为true时才会调用)SetDirty() ->(IsActive()为true时才会调用)LayoutRebuilderMarkLayoutForRebuild();
C、ScrollRect :OnRectTransformDimensionsChange() -> SetDirty() ->( IsActive()为true时才会调用 )LayoutRebuilderMarkLayoutForRebuild();
D、Graphic :OnRectTransformDimensionsChange() ->( gameObjectactiveInHierarchy为true时才会调用 )SetLayoutDirty() ->(IsActive()为true时才会调用)LayoutRebuilderMarkLayoutForRebuild();
(4)OnDidApplyAnimationProperties(): 动画导致属性( 重写OnDidApplyAnimationProperties()方法的类自身所特有的属性 )改变时调用。继承UIBehaviour的Graphic、LayoutGroup、LayoutElement都重写了该方法,具体调用如下:
A、Graphic :OnDidApplyAnimationProperties() -> SetAllDirty() -> ( m_SkipLayoutUpdate变量为false时才会调用 )LayoutBuilderMarkLayoutForRebuild()。Graphic本身是个抽象类,不能当作组件直接添加,而继承自Graphic的Image\Text( ——> MaskableGraphic ——> Graphic)组件的一些特有属性,如图3-1红框内标记,当动画改变Text的这些属性时会调用OnDidApplyAnimationProperties()函数。如图3-2红框内标记,当动画改变Image的这些属性时会调用OnDidApplyAnimationProperties()函数。
B、LayoutGroup :OnDidApplyAnimationProperties() -> SetDirty() -> ( IsActive()为true时才会调用 )LayoutBuilderMarkLayoutForRebuild()
如图3-3、3-4、3-5分别是VerticalLayoutGroup、HorizontalLayoutGroup,当动画改变VerticalLayoutGroup的这些属性时会调用OnDidApplyAnimationProperties()函数。
C、LayoutElement :OnDidApplyAnimationProperties() -> SetDirty() -> ( IsActive()为true时才会调用 )LayoutBuilderMarkLayoutForRebuild()
如图3-6所示,当动画改变LayoutElement的这些属性时会调用OnDidApplyAnimationProperties()函数。
(5)OnBeforeTransformParentChanged() :父节点transform属性变化前调用。继承自UIBehaviour的Graphic和LayoutElement类都重写了该方法:
A、Graphic :OnBeforeTransformParentChanged() -> SetAllDirty() -> LayoutBuilderMarkLayoutForRebuild()
B、LayoutElement :OnBeforeTransformParentChanged() -> SetDirty() -> LayoutBuilderMarkLayoutForRebuild()
2、 某些组件特有属性变化或函数调用:
(1)RectTransform 中导致布局变化的属性:Width、Height、Scale、以及锚点变化;
(2)Text 中导致布局变化的属性:text、alignment、fontSize、horizontalOverflow、verticalOverflow、lineSpacing、fontStyle、resizeTextForBestFit、resizeTextMinSize、supportRichText、resizeTextMaxSize、font;方法:FontTextureChanged()。
(3)Image 中导致布局变化的属性:sprite、overrideSprite;方法:SetNativeSize()、OnCanvasHierarchyChanged()。其中OnCanvasHierarchyChanged()只有在canvasreferencePixelsPerUnit变化且Image类型为Sliced或Tiled时才会调用SetLayoutDirty()
(4)LayoutGroup 中导致布局变化的属性:padding、childAlignment;方法:OnTransformChildrenChanged()
(5)GridLayoutGroup 中导致布局变化的属性:startCorner、startAxis、cellSize、spacing、constraint、constraintCount
(6)HorizontalOrVerticalLayoutGroup 中导致布局变化的属性:spacing、childForceExpandWidth、childForceExpandHeight、childControlWidth、childControlHeight、childScaleWidth、childScaleHeight
(7)ContentSizeFitter 中导致布局变化的属性:horizontalFit、verticalFit
(8)LayoutElement 中导致布局变化的属性:ignoreLayout、minWidth、minHeight、preferredWidth、preferredHeight、flexibleWidth、flexibleHeight、layoutPriority
(9)ScrollRect 中导致布局变化的属性:verticalScrollbar、horizontalScrollbar、horizontalScrollbarVisibility、verticalScrollbarVisibility、viewport
四、布局重建分析工具
1、第三节我们总结了一些可能导致布局重建的原因,游戏中尽量避免执行这些 *** 作以减少布局重建,同时尽可能少的使用Layout相关组件以减少CPU时间的占用。通过第二节对重建过程的分析,我们知道每个引起布局重建的元素的元素都会先加入CanvasUpdateRegistry中m_LayoutRebuildQueue队列中,所以我们可以通过反射的方式来获取游戏中引起布局重建的元素(引用 >
以上就是关于UGUIDrawCall优化全部的内容,包括:UGUIDrawCall优化、UGUI怎么获取Image,怎么动态的更换Image、UGUI Layout等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)