UGUI性能优化

Unity UI的一些最佳优化技巧

Posted by Bob on November 13, 2018

本文为我翻译的官方优化文档-Some of the best optimization tips for Unity UI。能力有限,凑活看吧~~~~

You’re sure to find a tip or two (or more) on this page to improve Unity UI performance. You’ll find many more in this great session by Unity engineer Ian Dundore, Squeezing Unity: Tips for raising performance (section on Unity UI starts at 23:38).

您肯定会在此页面找到一两个或更多提高Unity性能的技巧,Unity工程师Ian Dundore有个关于《提升性能技巧》不错的演讲(这是2017年在欧洲的Unit大会视频需要翻墙看,他在北京也讲过一次可惜国内没找到相关视频,Unity UI部分在23:28开始)您将在其中发现更多信息。

Divide up your canvases

划分你的多个Canvas

Problem: When one or more elements change on UI Canvas, it dirties the whole Canvas.
问题:当UI Canvas上的一个或多个元素发生变化时,它会污染整个Canvas。

The Canvas is the basic component of Unity UI. It generates meshes that represent the UI elements placed on it, regenerates the meshes when UI elements change, and issues draw calls to the GPU so that the UI is actually displayed.

Canvas是Unity UI的基本组件。它生成Mesh来表示放置在其中的UI元素,在UI元素更改时重新生成Mesh,并向GPU发出Draw Call,以便显示UI。

Generating these meshes can be expensive. UI elements need to be collected into batches so that they’re drawn in as few draw calls as possible. Because batch generation is expensive, we want to regenerate them only when necessary. The problem is that, when one or more elements change on a Canvas, the whole Canvas has to be re-analyzed to figure out how to optimally draw its elements.

生成这些Mesh可能很费(性能)。需要将UI元素收集到合批中,以便尽可能少地Draw Call调用。由于batch生成很费,我们只想在必要时重新生成它们。问题在于,当Canvas中一个或多个元素在发生改变时,必须重新分析整个Canvas以找出如何最佳(合批方式)绘制方式这些元素。

Many users build their entire game’s UI in one single canvas with thousands of elements. So, when they change one element, they can experience a CPU spike costing multiple milliseconds (to hear more about why rebuilding is expensive, go to the 24:55 mark in Ian’s talk).

许多开发者在一个Canvas包含数千个元素来构建整个游戏的UI。因此,当他们改变一个元素时,他们可能会遇到耗时数毫秒的CPU峰值(更多地了解为什么重建是昂贵的,请参阅Ian在24:55的谈话)。

Solution: Divide up your canvases.
解决方案:划分你的多个Canvas。

Each canvas is an island that isolates the elements on it from those of on other canvases. So, slicing up your canvases in the main tool available for resolving batching problems with Unity UI.

每个canvas独立为一个孤岛,将其上的元素与其他canvas上的元素隔离开来。因此,分割canvas成为用于解决Unity UI合批问题主要手段。

You can also nest canvases, which allows designers to create large hierarchical UIs without having to think about where different things are onscreen across many canvases. Child canvases also isolate content, from both their parent and sibling canvases. They maintain their own geometry and perform their own batching.

您还可以嵌套canvas,这样允许设计师创建庞大结构的UI,而无需考虑屏幕上不同位置的内容交叠许多canvas。子canvas也相对父兄canvas是独立区域。它们保持自己的几何形状并执行自己的批处理。

When subdividing canvases with child canvases, try to group things based on when they get updated. For example, separate dynamic elements from static ones (at around 29:36, Ian provides a nice example of smart subdivision of canvases).

在使用子canvas细分canvas时,请尝试根据更新时间对它们进行分组。例如,单独的动态元素与静态元素(在29:36左右,Ian提供了一个很好的canvas智能细分的例子)。

Optimal use of Graphic Raycaster

Graphic Raycaster的最佳使用

Problem: Optimal use of the Graphic Raycaster:
问题:Graphic Raycaster的最佳使用:

The Graphic Raycaster is the component that translates your input into UI Events. It translates screen/touch input into Events, and then sends them to interested UI elements. You need a Graphic Raycaster on every Canvas that requires input, including sub-canvases.

Graphic Raycaster是将您的输入转换为UI事件的组件。它将屏幕/触摸输入转换为事件,然后将它们发送给感兴趣的UI元素。您需要在每个需要输入的Canvas上使用Graphic Raycaster,包括子Canvas。

Despite its name the Graphic Raycaster is not really a raycaster: by default, it only tests UI graphics. It takes the set of UI elements that are interested in receiving input on a given canvas, and performs intersection checks: it checks that the point at which the input event occurs against the RectTransform of each UI element on the Graphic Raycaster’s Canvas is marked as interactive.

尽管它名称叫Graphic Raycaster,但并不是真正的raycaster:默认情况下,它只测试UI图形。它需要一组在canvas上愿意接收输入的UI元素,并执行交叉检查:它检查输入事件发生的点下Canvas上每个有Graphic Raycaster的UI元素的RectTransform,并被标记为在交互。

The challenge is that not all UI elements are interested in receiving updates.

挑战不是所有UI元素都需要接收输入更新。

Solution: Turn off the Raycast Target for static or non-interactive elements.
解决方案:关闭静态或非交互元素的Raycast目标。

For example, text on a button. Turning off the Raycast Target will directly reduce the number of intersection checks the Graphic Raycaster must perform each frame.

例如,按钮上的文本。关闭Raycast Target将直接减少Graphic Raycaster每帧必须执行的交叉检查次数。

image

Problem: In some ways, the Graphic Raycaster does act as a raycaster.
问题:在某些方面,Graphic Raycaster确实充当了raycaster。

If you set Render Mode on your Canvas to Worldspace Camera or Screen Space Camera, you can also set a blocking mask. The blocking mask determines whether the Raycaster will cast rays via 2D or 3D physics, to see if some physics object is blocking the user’s ability to interact with the UI.

如果将“画布”上的“Render Mode”设置为“Worldspace Camera”或“Screen Space Camera”,则还可以设置blocking mask。blocking mask确定Raycaster是否将通过2D或3D物理投射光线,以查看某个物理对象是否阻挡了用户与UI交互的能力。

Solution: Casting rays via 2D or 3D physics can be expensive, so use this feature sparingly.
解决方案:通过2D或3D物理投射光线可能很费,因此请谨慎使用此特性。

Also, minimize the number of Graphic Raycasters by not adding them to non-interactive UI Canvases, since in this case, there is no reason to check for interaction events.

此外,通过不将它们添加到非交互式UI Canvas来最小化Graphic Raycasters的数量,因为在这种情况下,没有理由检查交互事件。

Avoid use of Camera.main

避免使用Camera.main

Problem: World Space canvases need to know which camera their interaction events should come from.
问题:World Space Canvas需要知道它们的交互事件应该来自哪个Camera。

When setting up a Canvas to render either in World Space or in a Camera’s screen space, it’s possible to specify the Camera which will be used to generate interaction events for the UI’s Graphic Raycaster. This setting is required for “Screen Space - Camera” canvases, and is called the “Render Camera”.

设置Canvas在World Space或Screen space中渲染时,可以指定将用于为UI的Graphic Raycaster生成交互事件的Camera。“Screen Space - Camera”画布需要此设置,称为“Render Camera”。

image

However, the setting is optional for “World Space” canvases, and is called the “Event Camera.”

但是,“World Space” Canvas的设置是可选的,称为“Event Camera”。

image

If you leave the Event Camera field blank on a World Space Canvas, this does not mean that your Canvas will not receive Events. Instead, it uses the game’s main Camera. To determine which Camera is the main Camera, it accesses the Camera.main property.

如果将“World Space” Canvas上“Event Camera”字段留空,则这并不意味着Canvas不会接收“事件”。相反,它使用游戏的主摄像头。要确定哪个Camera是主Camera,它将访问Camera.main属性。

image

Depending on the code path Unity takes, it will access Camera.main between 7-10 times per frame, per Graphic Raycaster, per World Space Canvas. And Camera.main calls Object.FindObjectWithTag every time it’s accessed! Obviously, this is not a good thing at runtime.

根据Unity所采用的代码,它将按照每个World Space Canvas,每个Graphic Raycaster,每帧7-10次访问Camera.main,。每次访问时,Camera.main都会调用Object.FindObjectWithTag!显然,这在运行时不是一件好事。

Solution: Avoid the use of Camera.main.

解决方案:避免使用Camera.main。

Cache references to Cameras, and create a system to track the main camera. If you use World Space canvases, always assign an Event Camera. Do not leave this setting empty! If the Event Camera needs to change, write some code that will update the Event Camera property.

缓存对摄像机的引用,并创建一个跟踪主摄像机的系统。如果您使用World Space Canvas,请始终指定一个Event Camera。不要将此设置留空!如果需要更改事件相机,请编写一些更新Event Camera属性的代码。

Avoid Layout Groups where possible

尽可能避免Layout Group

Problem: Every UI element that tries to dirty its Layout will perform at least one GetComponents call.
问题:每个UI元素尝试弄脏其布局,且都将执行至少一个GetComponents调用。

When one or more child elements change on a layout system, it gets dirtied. The changed child element(s) invalidate the Layout system that owns it.

当布局系统上的一个或多个子元素发生变化时,它会变脏。更改的子元素使拥有它的布局系统无效。

A bit about Layout Systems: A Layout System is a set of contiguous Layout Groups that are directly above a Layout Element. A Layout Element is not just the Layout Element Component: UI images, texts and Scroll Rects–these are also Layout Elements. And, Scroll Rects are also Layout Groups.

关于布局系统:布局系统是一组直接位于布局元素上方的连续布局组。布局元素不仅仅是布局元素组件:UI images,text,Scroll Rect也是布局元素。而且,Scroll Rects也是布局组。

Back to the problem: Every UI element that marks its layout as dirty will perform, at minimum, one GetComponents call. This call looks for a valid Layout Group on the Layout Element’s parent. If it finds one, it continues walking up the Transform hierarchy until it stops finding Layout Groups or reaches the hierarchy root–whichever comes first. Therefore, each Layout Group adds one GetComponents call to each child Layout Element’s dirtying process, making nested Layout Groups extremely bad for performance.

回到问题:将其布局标记为脏的每个UI元素至少会执行一次GetComponents调用。此调用在布局元素的父级上查找有效的Layout Group。如果找到一个,它将继续向上移动Transform层次结构,直到它停止查找Layout Group或到达层次根节点 - 以先找到者为准。因此,每个布局组都会向每个子布局元素的弄脏过程添加一个GetComponents调用,从而使嵌套布局组的性能极差。

Solution: Avoid Layout Groups wherever possible.
解决方案:尽可能避免Layout Group。

Use anchors for proportional layouts. On hot UIs with a dynamic number of elements, consider writing your own code that will calculate layouts, and be sure to use this only on-demand, instead of with every single change.

使用锚点进行比例布局。在具有动态数量元素的热门UI上,考虑编写自己的代码来计算布局,并确保仅按需使用此代码,而不是每个都更改。

Pool UI objects the smart way

智能使用UI对象池

Problem: Pooling UI objects the wrong way around.
问题:以错误的方式用UI对象池。

Often, people pool UI objects by reparenting it and then disabling, but this causes unnecessary dirtying.

通常,人们通过重新显示UI对象然后禁用来对其进行池化,但这会导致不必要的污染。

Solution: Disable the object first, then reparent it into the pool.
解决方案:首先禁用对象,然后将其重新显示到池中。

You will dirty the old hierarchy once, but then when you reparent it, you’ll avoid dirtying the old hierarchy a second time, and you won’t dirty the new hierarchy at all. If you are removing an object from the pool, reparent it first, then update your data, then enable it.

您将脏化一次旧层次结构,但是当您重新显示它时,您将避免二次弄脏旧层次结构,并且您根本不会弄脏新层次结构。如果要从池中删除对象,请先重新显示它,然后更新数据,然后启用它。

How to hide a Canvas

如何隐藏Canvas

Problem: How to hide a Canvas
问题:如何隐藏Canvas

Sometimes, you want to hide some UI elements, and canvases. How you can do this most efficiently?

有时,您想要隐藏一些UI元素和Canvas。如何最有效地完成这项工作?

Solution: Disable the Canvas Component itself
解决方案:禁用Canvas组件本身

Disabling the Canvas Component will stop the Canvas from issuing draw calls to the GPU, so the Canvas will not be visible any longer. However, the Canvas won’t discard its vertex buffer; it will keep all of its meshes and vertices, and when you re-enable it, it won’t trigger a rebuild, it will just start drawing them again.

禁用Canvas组件将阻止Canvas向GPU发出draw call,因此Canvas将不再可见。但是,Canvas不会丢弃其顶点缓冲区; 它会保留所有的网格和顶点,当你重新启用它时,它不会触发重建,它只会再次开始绘制它们。

Additionally, disabling the Canvas Component does not trigger the expensive OnDisable/OnEnable callbacks on the Canvas’ hierarchy. Just be careful to disable child Components that run expensive per-frame code.

此外,禁用Canvas组件不会触发Canvas层次结构上昂贵的OnDisable / OnEnable回调。请小心禁用子组件,它代码每帧运行很费。

Optimal use of animators on UI elements

在UI元素上最佳使用animator

Problem: Using animators on your UI
问题:在UI上使用animator

Animators will dirty their elements every frame, even if the value in the animation does not change. Animators have no no-op checks.

即使animator中的值没有改变,animator也会在每帧中弄脏它们的元素。animator没有无操作检查。

Solution:
解决方案:

Only put animators on dynamic elements that always change. For elements that rarely change or that only change in response to events, for a short period of time, write your own code or tweening system (there are a number on the Asset Store).

只将animator放在总是改变的动态元素上。对于很少更改或仅响应事件而在短时间内更改的元素,请编写自己的代码或补间系统(Asset商店中有一些)。

More resources

Squeezing Unity: Tips for raising performance (section on Unity UI starts at 23:38)

Best practices: Optimizing Unity UI

Unity UI Docs

UI Profiler

(tutorials in section “Live Training: Shop UI with Runtime Scroll Lists” are intermediate-level)

Building an easy to use menu system

下面是UI部分演讲截图(原文没有这部分)

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image