Unity UI 优化建议,对官方文档的理解性翻译 (四)

5.Optimizing UI Controls - 优化UI控件

 

UI text

 

Unity’s built-in Text component is a convenient way of displaying rasterized text glyphs within a UI. However, there are a number of behaviors that are not commonly known, yet frequently appear as performance hotspots. When adding text to a UI, always remember that the text glyphs are actually rendered as individual quads, one per character. These quads tend to have a significant amount of empty space surrounding the glyph, depending on its shape, and it is very easy to position text in such a way that it unintentionally breaks the batching of other UI elements.

 

Unity的内置Text组件是在UI中显示栅格化文本字形的一种便利方式。然而,有很多不经意的情况下会导致性能消耗严重。当向UI添加文本时,请始终记住文本符号实际上是作为一个quad来显示的,每个字符一个。根据字形的形状,这些四元组往往在字形周围有大量的空白,因此很容易无意中破坏了其他UI元素的合批处理。

 

Text mesh rebuilds

 

One major issue is the rebuilding of UI text meshes. Whenever a UI Text component is changed, the text component must recalculate the polygons used to display the actual text. This recalculation also occurs if a text component, or any of its parent GameObjects, is simply disabled and re-enabled without changes to the text.

This behavior is problematic for any UI that displays large numbers of textual labels, with the most common being leaderboards or statistics screens. As the most common way to hide and show a Unity UI is to enable/disable a GameObject containing the UI, UIs with large numbers of text components will often cause undesirable frame-rate hiccups whenever they are displayed.

For a potential workaround to this issue, see the Disabling Canvases section in the next step.

 

一个主要的问题是 UI text网格的重绘(rebuild)。无论何时更改UI Text组件,组件都必须重新计算用于显示实际文本的多边形。如果只是简单地禁用或重新启用Text组件(或其任何父游戏对象),而不对文本进行更改,也会发生这种重新计算。

这种行为对于显示大量文本的UI是有问题的,最常见的是排行榜或数据结算界面。由于隐藏和显示Unity UI最常见的方式是启用/禁用包含该UI的GameObject,所以拥有大量文本组件的UI在显示时通常会导致意料之外的帧率下降。

想解决这个问题,请看下一张的Disabling Canvases部分

 

Dynamic fonts and font atlases - 动态字体和字体图集

 

Dynamic fonts are a convenient way to display text when the full displayable character set is either very large, or not known prior to runtime. In Unity’s implementation, these fonts build a glyph atlas at runtime based on the characters encountered within UI Text components.

Each distinct Font object loaded will maintain its own texture atlas, even if it is in the same font family as another font. For example, using Arial with bolded text on one control while using Arial Bold on another control will produce identical output but Unity will maintain two distinct texture atlases - one for Arial and one for Arial Bold.

当需要显示的字库非常大时,或者在运行之前都不知道需要显示什么字,就需要动态字体了。在Unity的实现中,这些字体在运行时根据UIText组件中需要的字符构建一个字形图集。

每个不同的字体都保存在自己的图集里,即使它只是相同字体家族中的另一种字体。例如,在一个控件上使用加粗的Arial字体,而在另一个控件上使用Arial Bold字体,最终他俩看上去是一样的,但Unity却需要维护两个不同的字体图集——一个用于Arial,一个用于Arial Bold。

 

From a performance perspective, the most important thing to understand is that Unity UI’s dynamic fonts maintain one glyph in the font’s texture atlas for each distinct combination of size, style & character. That is, if a UI contains two text components, both displaying the letter ‘A’, then:

  • If the two Text components share the same size, the font atlas will have one glyph in it.
  • If the two Text components do not share the same size (e.g. one is 16-point, the other 24-point), then the font atlas will contain two copies of the letter ‘A’ at different sizes.
  • If one Text component is bold and the other is not, then the font atlas will contain a bold 'A' and a regular 'A'.

 

Whenever a UI Text object with a dynamic font encounters a glyph that has not yet been rasterized into the font’s texture atlas, the font’s texture atlas must be rebuilt. If the new glyph fits into the current atlas, it is added and the atlas re-uploaded to the graphics device. However, if the current atlas is too small, then the system attempts to rebuild the atlas. It does this in two stages.

从性能的角度来看,最重要的是要理解Unity UI的动态字体在字体图集中为每个不同的大小、样式和字符的组合维护一个字形。也就是说,如果一个UI包含两个text 组件,都显示字母“A”,那么:

  • 如果两个Text 组件具有相同的大小,则字体图集中只会生成一个字形。
  • 如果两个Text 组件大小不相同(例如,一个是16号,另一个是24号),那么字体图集将包含两个不同大小的字母“A”。
  • 如果一个Text 组件是粗体,而另一个不是,那么字体图集将包含一个粗体'A'和一个普通'A'。

当使用了动态字体的UI Text输入了还没有添加到到字体图集的字形时,必须重绘字体图集。如果新的字形可以直接放入当前图集,则添加它并将图集重新上传到图形设备。但是,如果当前图集太小,则系统将尝试重新创建图集。它分为两个阶段。

 

First, the atlas is rebuilt at the same size, using only the glyphs currently being shown by active UI Text components. This includes UI Text components whose parent Canvases are enabled, but that have disabled Canvas Renderers. If the system succeeds in fitting all currently-in-use glyphs into a new atlas, it rasterizes that atlas and does not continue to the second step.

Second, if the set of currently-in-use glyphs cannot be fit into an atlas of the same size as the current atlas, a larger atlas is created by doubling the atlas’ shorter dimension. For example, a 512x512 atlas expands into 512x1024 atlas.

首先,创建一个一样大小的图集,将当前界面上所有开启状态的UIText组件,包括那些开启了父Canvas但禁用了Canvas Renderers的UIText组件上的字形尝试加入这个图集。如果系统成功地将所有当前使用的字形放入一个新的图集中,它将对该地图集进行栅格化处理,并且不再继续进行第二步。

第二,如果当前使用的所有字形不能放入与当前图集相同大小的图集中,则通过将图集的较短维度加倍来创建较大的图集。例如,512x512图集扩展为512x1024图集。

 

Due to the above algorithm, a dynamic font’s atlas will only grow in size once created. Given the cost of rebuilding the texture atlases, it is imperative to minimize during rebuilds. This can be done in two ways.

Whenever possible, use non-dynamic fonts and preconfigure support for the desired glyph set. This generally works well for UIs using a well-constrained character set, such as only the Latin/ASCII characters, and with a small range of sizes.

由于上面所述的算法,动态字体图集只会越来越大。考虑到重建图集时的性能消耗,最好最低限度的降低重建的频率。有两种解决办法,第一种:如果可能的话尽量使用非动态字体,也就是将需要的字形提前做好一个图集。这通常适用于对文本规范良好的UI(就是已知都有什么字),比如只使用拉丁/ASCII字符和较小的字号范围。

 

If an extremely large range of characters must be supported, such as the entire Unicode set, then the font must be set to Dynamic. To avoid predictable performance problems, prime the font’s glyph atlas at startup time with a set of appropriate characters via Font.RequestCharactersInTexture.

Note that font atlas rebuilds are triggered individually for each UI Text component that is changed. When populating an extremely large number of Text components, it may be advantageous to collect all unique characters in the Text components’ content and prime the font atlas. This will ensure that the glyph atlas need only be rebuilt once instead of being rebuilt once each time a new glyph is encountered.

如果需要的字符范围非常广,比如需要整个Unicode字集,那就不得不使用动态字体。为了避免性能问题,在游戏启动时可以通过调用 Font.RequestCharactersInTexture方法来初始化这些文字。

请注意,字体图集重建是针对每个被更改的UI Text 组件分别触发的。在书写大量UI Text 组件时,最好先收集Text组件内容中的所有唯一(不重复)字符以提前更新图集。这将确保字体图集只需要重建一次,而不是每次遇到新的字形都要重建一次。

 

Also note that, when a font atlas rebuild is triggered, any characters that are not presently contained in an active UI Text component will not be present in the new atlas, even if they were originally added to the atlas as a result of a call to Font.RequestCharactersInTexture. To work around this limitation, subscribe to the Font.textureRebuilt delegate and query Font.characterInfo to ensure that all desired characters remain primed.

The Font.textureRebuilt delegate is currently undocumented. It is a single-argument Unity Event. The argument is the font whose texture was rebuilt. Subscribers to this event should follow the following signature

还需要注意,当一个字体图集触发重建时,除了现在所有激活(activeSelf=true)的UI组件上显示的文字以外,其它的都不会被添加进新图集,即使它们最初是通过 Font.RequestCharactersInTexture方法添加到图集中的。要解决这个限制,请实现Font.textureRebuilt 委托并通过Font.characterInfo 方法来确保所有需要的字符保持显示状态。

这里说Font.RequestCharactersInTexture方法没有文档,实际上现在API手册里面已经有了,用法查一下就好。

 

Specialized glyph renderers - 特殊的字形渲染器

For situations where the glyphs are well-known, with relatively fixed positions between each glyph, it is significantly more advantageous to write a custom component to display sprites displaying those glyphs. An example of this might be a score display.

For scores, the displayable characters are drawn from a well-known glyph set (the digits 0-9), do not change across localities, and appear at fixed distances from one another. It is relatively trivial to decompose an integer into its digits and display appropriate digit sprites. This sort of specialized digit-display system can be built in a manner that is both allocationless and considerably faster to calculate, animate and display than the Canvas-driven UI Text component.

这里的意思就是说一些常用的(众所周知)字形,而且字与字之间的距离也是固定的,那么使用一个自定义的Sprite来显示比使用由Canvas驱动的UI Text组件更加好用。比如说显示分数(0-9),不随位置变化,彼此之间的距离也是固定的。

 

Fallback fonts and memory usage - 字体回退(Fallback)与内存使用

For applications that must support a large character-set, it is tempting to list a large number of fonts in the “Font Names” field of a font importer. Any fonts listed in the “Font Names” field will be used as fallbacks if a glyph cannot be located within the primary font. The fallback order is determined by the order in which the fonts are listed in the “Font Names” field.

However, in order to support this behavior, Unity will keep all fonts listed in the “Font Names” field loaded into memory. If a font’s character set is very large, then the amount of memory consumed by fallback fonts can become excessive. This is most often seen when including pictographic fonts, such as Japanese Kanji or Chinese characters.

如果APP中需要大量的文字(字形),很容易在字体导入器(font importer)的“Font Names”字段中列出大量字体。如果一个字形不能在它正使用的字体中找到的话,那么它将在“Font Names”字段中列出的所有字体中找一个拥有这个字形的。寻找的顺序由“Font Names”字段中列出字体的顺序决定。

为了支持这个操作,Unity将把“Font Names”字段中列出的所有字体都保存到内存中。如果字体的字符集非常大,那么搜索字体所消耗的内存可能会过多。尤其是像日文和中文这种象形字。

 

Best Fit and performance - 最佳适配和它的性能

In general, the UI Text component's Best Fit setting should never be used.

通常,不应该使用 UI Text 组件的Best Fit设置。

“Best Fit” dynamically adjusts the size of a font to the largest integer point size which can be displayed within a Text component’s bounding box without overflow, clamped to a configurable minimum/maximum point size. However, because Unity renders a distinct glyph into the font atlas for each distinct size of character being displayed, use of Best Fit will rapidly overwhelm the atlas with many different glyph sizes.

“Best Fit”动态调整字体大小为可以显示在边框内而不溢出的最大整数,并剪裁在可配置的最小/最大范围之内。但是,因为Unity会为显示的每个不同大小的字符在字体图集中绘制不同的字形,所以使用“Best Fit”会导致字体图集很快被大量不同大小的字形所淹没。

 

As of Unity 2017.3, the size detection used by Best Fit is nonoptimal. It generates glyphs in the font atlas for each size increment tested, which further increases the amount of time required to generate font atlases. It also tends to cause atlas overflows, which causes old glyphs to be kicked out of the atlas. Due to the large number of tests required for a Best Fit calculation, this will often evict glyphs in use by other Text components, and force the font atlas to be rebuilt at least once more after the appropriate font size has been calculated. This specific issue has been corrected in Unity 5.4, and Best Fit will not unnecessarily expand the font's texture atlas, but is still considerably slower than statically-sized text.

在Unity 2017.3中,Best Fit使用的尺寸检测不是最优的。它为检测到的的每个大小的字形增量的加到图集中,这进一步增加了生成字体图集所需的时间。它还会导致图集溢出,导致旧的字形被从图集中踢出。由于Best Fit计算需要大量的检测,这通常会排除其他Text组件使用的字形,并在至少计算了一次适当的字体大小之后强制重新构建字体图集。这个问题已经在Unity 5.4中得到了修正,Best Fit不会不必要地扩展字体的图集,但是仍然比静态大小的text慢很多。

 

 

Frequent font atlas rebuilds will rapidly degrade runtime performance as well as cause memory fragmentation. The greater the quantity of text components set to Best Fit, the worse this problem becomes.

频繁地重新构建字体图集会迅速降低运行时性能,并导致内存碎片。设置为Best Fit的text组件的数量越多,这个问题就变得越严重

 

TextMeshPro Text

TextMesh Pro (TMP) is a replacement for Unity’s existing text components like Text Mesh and UI Text. TextMesh Pro uses Signed Distance Field (SDF) as its primary text rendering pipeline making it possible to render text cleanly at any point size and resolution. Using a set of custom shaders designed to leverage the power of SDF text rendering, TextMesh Pro makes it possible to dynamically change the visual appearance of the text by simply changing material properties to add visual styles such as dilation, outline, soft shadow, beveling, textures, glow, etc. and to save and recall these visual styles by creating/using material presets.

Until the release of 2018.1, TextMesh Pro was included in one’s project as a Asset Store package. As of 2018.1 TextMesh Pro will be available as a Package Manager package.

Text Mesh Pro(TMP)替代了Unity现有的文本组件。TextMesh Pro使用Signed Disatance Fild(SDF)作为首选文本渲染管线,使其可以在任意尺寸和分辨率中清晰的渲染文本。使用自定义的shader来提升SDF文本渲染的能力,TextMesh Pro可能通过简单的改变材质来动态地改变视觉效果。

 

Text mesh rebuilds

Much like Unity’s built-in UIText component, making changes to the text displayed by the component will trigger calls to Canvas.SendWillRendererCanvases and Canvas.BuildBatch which can be costly. Minimize changes to the text field of a TextMeshProUGUI component and make sure to parent TextMeshProUGUI components whose text changes often to a parent GameObject that has its own Canvas component to ensure that Canvas rebuild calls remain as efficient as possible.

Do note that for text displayed in world space, we recommend that users use the normal TextMeshPro component instead of using TextMeshProUGUI as using Canvases in Worldspace can be inefficient. Using TextMeshPro directly will be more efficient given it doesn't have incur the canvas system overhead.

Unity内置的UIText组件在改变文本的时候将触发Canvas.SendWillRendererCanvas方法和Canvas.BuildBatch方法,非常浪费性能。将TextMeshProUGUI组件中的文本变动最小化并且将其发生变化的组件放置到专门的Canvas 上,使Canvas 重绘效率达到最高。

在文本需要显示在世界空间的时候,建议直接使用TextMeshPro,将更加高效,因为他不会产生canvas 系统的开销。

 

 

Scroll Views

After fill-rate problems, Unity UI’s Scroll Views are the second most common source of runtime performance issues seen. Scroll Views generally require a significant number of UI elements to represent their content. There are two basic approaches to populating a scroll view:

  • Fill it with all of the elements necessary to represent all of the scroll view’s content
  • Pool the elements, repositioning them as needed to represent visible content.

Unity UI的Scroll View是紧随fill-rate问题第二常见的性能问题出现的原因。Scroll Views需要大量的UI元素表示其内容。这有两种基本方式填充滚动视图:

  • 一次性将滚动视图全部需要的元素进行加载
  • 缓存元素,在需要元素的时候重新定位它们

这两种解决方案都会有一些问题。

 

The first solution requires an increasing amount of time to instantiate all of the UI elements as the number of items to be represented increases, and also increases the time required to rebuild the Scroll View. If there are only a small number of elements required within a Scroll View, such as in a Scroll View that only needs to display a handful of Text components, then this method is favored for its simplicity.

The second solution requires significant amounts of code to implement correctly under the current UI and layout system. Two possible methods will be discussed in further detail below. For any significantly complex scrolling UI, some sort of pooling approach is generally needed to avoid performance problems.

Despite these issues, all approaches can be improved by adding a RectMask2D component to the Scroll View. This component ensures that Scroll View elements that are outside of the Scroll View’s viewport are not included in the list of drawable elements that must have their geometry generated, sorted and analyzed when rebuilding a Canvas.

第一个解决方案需要更多的时间来生成所有UI元素,而且随着要显示的元素的增加,Scroll View的rebuilt的所需时间也会跟着增加。如果Scroll View中只有少量的元素,比如Scroll View只需显示一些Text组件,那么使用这个解决方案简单易行,最为合适。

第二个解决方案需要大量的代码来实现正确的当前要显示的UI和layout系统。下面将进一步详细讨论两种可行的解决方案。对于那些大量的复杂的要滚动的UI来说,通常需要使用某些对象池的方法来避免性能问题。

尽管存在这些问题,所有的方法都能够通过给Scroll View添加RectMask2D组件来得到改进。这个组件可以确保Scroll View的viewport之外的元素不包括在要绘制的元素列表中,在列表中的元素在Canvas的rebuild的时候必须生成它们的几何体,排列,并且进行分析。

 

Simple Scroll View element pooling - 简单的Scroll View元素池

The simplest way to implement object pooling with a Scroll View while also preserving as much of the native convenience of using Unity’s built-in Scroll View component is to take a hybrid approach:

To lay out the elements in the UI, which will allow the layout system to properly calculate the size of the Scroll View’s content and allows scrollbars to function properly, use GameObjects with Layout Element components as “placeholders” for the visible UI elements.

Then, instantiate a pool of visible UI elements sufficient to fill the visible portion of the Scroll View's visible area, and parent these to the positioning placeholders. As the Scroll View scrolls, reuse the UI elements to display content that has scrolled into view.

This will substantially cut down on the number of UI elements that must be batched, as the cost of batching only increases based on the number of Canvas Renderers within a Canvas, not the number of Rect Transforms.

实现Scroll View对象池最简单同时保留Unity内置的Scroll View组件的大多数原生便捷性的方法是使用一种混合的方法:

在UI中布局元素,这将允许布局系统来正确计算Scroll View的content大小,并且允许scrollbar正常工作,使用挂载Layout Element组件的游戏物体来作为可见UI元素的“placeholders(占位符)”。

然后,实例化足以填充Scroll View的可视区域的可视部分的可见UI元素,并且设定父物体为定位好的占位符。当Scroll View滚动时,重用UI元素来显示滚动到视口中的内容。

这将大大减少必须要batch的UI元素的数量,因为batch的开销增长只基于Canvas内的Canvas Renderer数量,而不是Rect Transform的数量。

 

Problems with the simple approach - 解决问题的简单方法

Currently, whenever any UI element is reparented or has its sibling order changed, that element and all of its sub-elements are marked as “dirty” and force a rebuild of their Canvas.

The reason for this is that Unity has not separated the callbacks for reparenting a transform and altering its sibling order. Both of these events will fire an OnTransformParentChanged callback. In the source of Unity UI’s Graphic class (see Graphic.cs in the source), that callback is implemented and invokes the method SetAllDirty. By dirtying the Graphic, the system ensures that the Graphic will rebuild its layout and vertices before the next frame is rendered.

It is possible to assign canvases to the root RectTransform of each element within the Scroll View, which will then confine the rebuild to only the reparented elements and not the entire contents of the Scroll View. However, this tends to increase the number of draw calls needed to render the Scroll View. Further, if the individual elements within the Scroll View are complex and consist of more than a dozen Graphic components, and particularly if there is a significant number of Layout components on each element, then the cost of rebuilding them is often high enough to noticeably reduce the frame rate on lower-end devices.

If a Scroll View UI element does not have a variable size, then this full recalculation of layout and vertices is unnecessary. However, avoiding this behavior requires the implementation an object pooling solution based on position changes instead of parent or sibling-order changes.

目前,当任何UI元素重新设置父级,或者层级排序变换的时候,该元素及其所有的子元素都将被标“Dirty”,并且会强制rebuild其Canvas。

这个问题的原因是Unity没有分离重新设置父级和改变其层级排序的回调。这些事件都会触发一个叫OnTransformParentChanged的回调。在UGUI源码的Graphic类中(见源码中的Graphic.cs脚本),这个回调实现并执行了SetAllDirty方法。通过将Graphic标Dirty,系统可以确保Graphic将在下一帧渲染前重建其布局和顶点。

可以将Canvas分配到Scroll View中每一个元素的根RectTransform上,这将会限制重建发生的范围只在重新设置父级的元素上而不是Scroll View的整个content。但是这也会增加需要渲染Scroll View的drawcall数量。此外,如果Scroll View内独立的元素是复杂的并且包含了十多个Graphic组件,特别是每个元素上包含了大量的Layout组件,其rebuild的开销也经常可以会高到显著的降低低端设备的帧率。

如果一个Scroll View的UI元素的大小是不可变的,那么完全重新计算布局和顶点就是不必要的。然而,避免这种问题的解决方案需要实现的对象池是基于位置的改变而不是重新设置父级或是更改层级顺序。

 

Position-based Scroll View pools - 基于位置改变的Scroll View池

In order to avoid the problems described above, it is possible to create a Scroll View that pools its objects by simply moving the RectTransforms of its contained UI elements. This avoids the need to rebuild the contents of the moved RectTransforms if their dimensions are not altered, significantly improving the performance of the Scroll View.

To accomplish this, it is generally best to either write a custom subclass of Scroll View or to write a custom Layout Group component. The latter is generally the simpler solution, and can be accomplished by implementing a subclass of Unity UI’s LayoutGroup abstract base class.

The custom Layout Group can analyze the underlying source data to examine how many data elements must be displayed and can resize the Scroll View’s Content RectTransform appropriately. It can then subscribe to Scroll View change events and use these to reposition its visible elements accordingly.

为了避免上述问题,可以创建一个仅仅是移动其包含的UI元素的RectTransform的对象存储池。这样就通过移动RectTransform避免了rebuild尺寸未改变的内容,显著的提高了Scroll View的性能。

要做到这一点,通常最好是要写一个用户自定义的Scroll View子类和写一个用户自定义的Layout Group组件。后者通常是更简单的解决方案,可以通过实现UGUI的LayoutGroup的抽象基类来完成。

用户自定义的LayoutGroup可以分析底层的元数据来检查要显示多少数据元素,并且可以重新设置Scroll View的content的RectTransform为合适的。也可以通过监听Scroll View onValueChanged事件并相应的用来重新设置可见元素的位置。

 

6.Other UI Optimization Techniques and Tips - 其他UI优化技术和提示

Sometimes there is just no clean way to optimize a UI. This section contains a handful of suggestions that may help improve UI performance, but some are “unclean” structurally, may be difficult to maintain, or may have ugly side effects. Others may be workarounds for behavior in the UI intended to simplify initial development, but also make it relatively simple to create performance problems.

很多时候并没有一个简洁的方法来优化UI。本章节包含了一些可能会提高UI性能的建议,但是一些是在结构上不简洁的,或是难于维护,或是有一些不好的边际效应。其他的是一些使UI初始开发变成简单的行为的解决方案,但是也会更容易造成一些性能问题。

 

很多时候并没有一个简洁的方法来优化UI。本章节包含了一些可能会提高UI性能的建议,但是一些是在结构上不简洁的,或是难于维护,或是有一些不好的边际效应。其他的是一些使UI初始开发变成简单的行为的解决方案,但是也会更容易造成一些性能问题。

 

RectTransform-based Layouts - 基于RectTransform的布局

Layout components are relatively expensive, as they must recompute the sizes and positions of their child elements each time they are marked dirty. (See the Graphic rebuild section of the Fundamentals step for details.) If there is a relatively small and fixed number of elements within a given Layout, and the Layout has a relatively simple structure, it may be possible to replace the Layout with a RectTransform-based layout.

By assigning the anchors of a RectTransform, the RectTransform’s position and size can be made to scale based on its parent. For example, a simple two-column layout can be achieved with two RectTransforms:

  • The left column’s anchors should be X: (0, 0.5) and Y: (0, 1)
  • The right column’s anchors should be X: (0.5, 1) and Y: (0, 1)

The computations of the size and position of the RectTransform will be driven in native code by the Transform system itself. This is generally more performant than relying on the Layout system. It is also possible to write MonoBehaviours that set up a RectTransform-based Layout. However, this is a relatively complex task and lies beyond the scope of this guide.

Layout组件性能消耗相对昂贵,因为它们必须在其每次标记dirty时重新计算其子元素的大小和位置(有关详细信息,请参阅Fundamentals章节的Graphic rebuild部分)。如果给定Layout中的元素数量相对较少且数量固定,并且Layout结构相对简单,则可以使用基于RectTransform的Layout替换Layout。

通过分配一个RectTransform的锚点,RectTransform的位置和大小就能基于其父级进行缩放。例如,两个RectTransform就能实现一个简单的两列的布局:

左列的锚点应该是X:(0,0.5)和Y:(0,1)(覆盖左边屏幕)

右列的锚点应该是X:(0.5,1)和Y:(0,1)(覆盖右边屏幕)

RectTransform的大小和位置的计算将由Transform系统本身在本机代码中驱动。这通常比依靠Layout系统更高效。编写基于RectTransform的布局的MonoBehaviour脚本也是可以的。但是,这是一项相对复杂的工作,也超出了本指南的范围。

 

Disabling Canvases - 禁用Canvas

When showing or hiding discrete portions of a UI, it is common to enable or disable the GameObject at the root of the UI. This ensures that no component in the disabled UI receives input or Unity callbacks.

However, this also causes the Canvas to discard its VBO data. Re-enabling the Canvas will require the Canvas (and any Sub-canvases) to run the rebuild and rebatch processes. If this happens frequently, the increased CPU usage can cause the application’s frame rate to stutter.

One possible, but hacky, workaround is to place the UI to be shown/hidden onto its own Canvas or Sub-canvas and then to merely enable/disable the Canvas component on this object.

This will cause the UI’s meshes to not be drawn, but they will remain resident in memory and their original batching will be preserved. Further, no OnEnable or OnDisable callbacks will be invoked in the UI’s hierarchy.

Note, however, that this will not disable any MonoBehaviours within the hidden UI, and so these MonoBehaviours will still receive Unity lifecycle callbacks, such as Update.

To avoid this issue, MonoBehaviours on UIs that will be disabled in this manner should not directly implement Unity’s lifecycle callbacks, but should instead receive their callbacks from a “Callback Manager” MonoBehaviour on the UI’s root GameObject. This “Callback Manager” can be informed whenever the UI is shown/hidden, and can ensure that lifecycle events are propagated or not propagated as necessary. Further explanation of this “Callback Manager” pattern is beyond the scope of this guide.

当显示或隐藏一个UI分立的部分时,通常会启用或者禁用这个UI的根游戏物体。这确保了在这个禁用的UI中没有组件接收输入或是执行Unity的回调函数。

然而,这也会导致Canvas抛弃其VBO(顶点缓冲对象)数据。重新启用Canvas会使Canvas(包括所有的子Canvas)强制进行rebuild和rebatch进程。如果这种情况发生的非常频繁,增加的CPU使用会造成应用程序的帧率卡顿。

一种可能的方法是将UI的显示和隐藏控制在其Canvas和子Canvas中,仅仅是启用或者禁用关联到Canvas或是子Canvas的Canvas Renderer组件(并不是指真的Canvas Renderer组件,而是指依赖Canvas Renderer组件的image,text等组件)。

这将UI的网格就不会被绘制,它们将会保持驻留在内存中,它们的原始batch将会被保存。此外,在UI的层级中将不会有OnEnable或是OnDisable回调函数执行。

但请注意,这样的方法将不会消除GraphicRegistry中UI的图形,所以它们仍然会出现在Graphic Raycast要检查的组件列表中。这种方法不会禁用任何隐藏UI中的MonoBehaviour脚本,所以这些MonoBehaviour脚本将会接收Unity的生命周期回调,比如说Update。

为了避免这个问题,将以这种方式禁用的UI上的MonoBehaviour脚本不应该直接来实现Unity的生命周期回调函数,而是应该通过挂载到UI根游戏物体上的“Callback Manager” MonoBehaviour脚本来接收回调函数。每当UI被显示和隐藏的时候,就会通知Callback Manager,这会确保生命周期事件根据需要传播或是不传播。关于Callback Manager模式的进一步解释超越了本指南的范围。

 

Assigning Event Cameras - 分配事件摄像机

If using Unity’s built-in Input Managers alongside Canvases set to render in the World Space or Screen Space – Camera modes, it is important to always set the Event Camera or Render Camera property, respectively. From script, this is always exposed as the worldCamera property.

If this property is not set, then Unity UI will search for the main camera by looking for Camera components attached to GameObjects with the Main Camera tag. This lookup will occur at least once per World Space or Camera Space Canvas. As GameObject.FindWithTag is known to be slow, it is strongly recommended that all World Space and Camera Space Canvases have their Camera properties assigned at design-time or initialization time.

This issue does not occur for Overlay Canvases.

如果使用Unity的内置Input Manager并且Canvas的渲染模式设置为World Space或是Screen Space – Camera模式,始终分别的设置事件摄像机和渲染摄像机非常重要。在脚本中,它始终作为worldCamera属性公开。

如果这个属性没有设置,那么UGUI将会在挂有摄像机的游戏物体中通过主摄像机标签来寻找主摄像机,至少每个World Space或是Camera Space的Canvas都会发生这种查找。GameObject.FindWithTag众所周知非常慢,所以强烈建议所有的World Space和Camera Space的Canvas都在设计时或初始化时设置其Camera属性。

这个问题不会发生在Overlay的Canvas上。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值