分享

【优化UI控件】

 kiki的号 2017-04-07


难度:高级

This section of the Optimizing Unity UIguide focuses on issues specific to certain types of UI controls. While most UIcontrols are relatively similar in terms of performance, two stand out as beingthe causes of many of the performance issues encountered in games close to ashippable state.

Unity UI优化指南的这部分关注与特定的一些UI控件。虽然大部分的UI控件在性能上都比较相似,下面这两个控件在游戏中导致很多性能问题

UI TEXT
UI文本
Unity’s built-in Text component is aconvenient way of displaying rasterized text glyphs within a UI. However, thereare a number of behaviors that are not commonly known, yet frequently appear asperformance hotspots. When adding text to a UI, always remember that the textglyphs are actually rendered as individual quads, one per character. Thesequads 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 thatit unintentionally breaks the batching of other UI elements.

Unity内置的文本组件是一个在UI中显示光栅化文本,很方便的方法。然而,也有一些通常不知道的行为,经常出现性能热点的行为。当给一个UI添加文本时,永远记住,每个字符实际上会被渲染成一个看不见的多边形。基于多边形的形状,它会有在多边形周围有个空的空间,很简单地设置文本的位置,这样会无意的打破UI元素的batching。

Text meshrebuilds
文本网格重建

One major issue is the rebuilding of UItext meshes. Whenever a UI Text component is changed, the text component mustrecalculate the polygons used to display the actual text. This recalculationalso occurs if a text component, or any of its parent GameObjects, is simplydisabled & re-enabled without changes to the text.

一个主要的问题是UI文本网格的重建。每当UI文本组件改变时,文本组件必须重新计算用来显示文本的的多边形。如果一个文本组件或者任何它的父物体没有改变任何文本,只是简单的禁用和重新启动时,重新计算也会发生。

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

这个行为是任何用来显示大量文本Label 的UI,最常见的问题。作为最常见的隐藏和显示Unity UI的方法,是启动和禁用一个包含UI的物体。当显示一个带有大量文本组件的UI时,会经常导致不希望的卡帧。

For a potential workaround to this issue,see the Disabling Canvas Renderers section in the next chapter.

对于这个问题的解决方案,请查看下一节的Disabling Canvas Renderers部分。

Dynamic fontsand font atlases
动态字体和字体图集

Dynamic fonts are a convenient way todisplay text when the full displayable character set is either very large, ornot known prior to runtime. In Unity’s implementation, these fonts build aglyph atlas at runtime based on the characters encountered within UI Textcomponents.

当字符很大或则运行前不确定时,动态字体是显示字体很方便的方法。在Unity的实现中,这些字体在运行时基于文本UI组件遇到的字符,创建一个图形集。

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

每个加载的不同的文字对象都会保持它自己的纹理图集,即使它是同一种字体。例如,在一个控件上使用Arial和加粗文字,而在另一个控件上使用Arial加粗文字,都会产生相同的输出,但是Unity会保持两个不同的纹理图集—一个个Arial,一个给Arial Bold。

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

从性能角度,最重要的事是,Unity UI的动态字体给每个大小,风格和字符不同组合,在字体纹理图集中保持了一个图形。也就是说,如果一个UI包含两个文本组件,两个都显示“A”,那么:

If the two Text components share the same size, the fontatlas 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 containtwo copies of the letter ‘A’ at different sizes.

如果两个组件不共享一样的大小(比如一个是16,一个是24),那么文字图集将有两个不同大小的“A”的副本。

If one Text component is bold and the other is not, thenthe font atlas will contain a bold 'A' and a regular 'A'.

如果一个文本组件是粗体,一个不是,那么字体图集将包含一个粗体的“A”,一个常规的“A”。

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

每当使用动态字体的UI文本对象,遇到没有光栅化到文本纹理图集的图形时,文本纹理图集必须要重建。如果新的图形适合现在的图集,它被添加,图集重新上传到图形设备。但是,如果当前的图集太小,系统会尝试重建图集。它会用两阶段做这件事。

First, the atlas is rebuilt at the samesize, using only the glyphs currently being shown by active UIText(1) components. If the system succeeds infitting all currently-in-use glyphs into a new atlas, it rasterizes that atlasand does not continue to the second step.

首先,图集重建相同大小的图集,仅适用通过UI文本组件目前正在显示的图形。如果系统成功将所有现在正在使用的图形加入一个新的图集,它栅格化图集,并不会继续第二步。

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

其次,如果现在使用的图形不能放入一个和现在图形大小一样的图集,通过加倍当前图集大小,一个更大的图集被创建。例如,一个512*512的图集,会扩展为512*1024的图集。

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

由于上述的算法,一个动态的文字图集创建的时候,只会增加一次大小。鉴于重建纹理图集的成本,就必须在重建过程中尽量减少重建纹理图集。这可以通过两种方式来完成。

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

尽可能使用非动态字体和预配置所需字形。这通常可以很好支持使用一个完全约束字符集的UI,例如仅Latin/ ASCII字符,并在小范围。

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

如果需要支持一个非常大的范围,如整个Unicode集,那么字体必须设置为动态。以避免性能问题,在启动时通过一组适当的字符Font.RequestCharactersInTexture事先准备图形图集。

Note that font atlas rebuilds aretriggered individually for each UI Text component that is changed. Whenpopulating an extremely large number of Text components, it may be advantageousto collect all unique characters in the Text components’ content and prime thefont atlas. This will ensure that the glyph atlas need only be rebuilt onceinstead of being rebuilt once each time a new glyph is encountered.

注意文字图集重建会在UI文本组件被修改时被单独触发。当填充一个非常大的文本组件,它可能收集所有文本组件中不同的字符,加入文字图集。这会确保图形图集只被重建一次,而不是每遇到新的图形就重建。

Also note that, when a font atlasrebuild is triggered, any characters that are not presently containedin an active UI Text component will not be present in the new atlas, even ifthey were originally added to the atlas as a result of a call to Font.RequestCharactersInTexture. To workaround this limitation, subscribe to the Font.textureRebuilt delegateand query Font.characterInfo to ensure that alldesired characters remain primed.

还需要注意的是,当一个文字图集重建被触发,调用 Font.RequestCharactersInTexture所有没有在当前UI文本组件中显示的文字都不会在新的图集中,即使他们最初已经添加在图集中了。要解决这个限制,使用Font.textureRebuilt 委托和查询Font.characterInfo来确保所有需要的字符保持初始状态。

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

Font.textureRebuilt委托目前没有记录在文档。它是single-argument Unity事件。它主要是为了纹理重建。使用该事件,需要遵循以下要求:
public void TextureRebuiltCallback(Font rebuiltFont){/*...*/}

Specializedglyph renderers
专业的字形渲染器

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

总所周知,对于图形和每个固定位置相关情况,编写一个自定义的组件来显示精灵显示这些图形是很重要的。这样的一个例子可能是一个得分显示。

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

对于成绩,显示的字符是从数字0-9的图形集,不用跨位置显示,并在固定位置出现。分解一个整数成一个数字和恰当显示数字的精灵,比较繁琐。这种专业的数字显示系统可以被创建,不需要分配,并且计算、动画和显示都比画布驱动的UI文本组件快。

Fallback fontsand memory usage
后备字体和内存使用

For applications that must support a largecharacter-set, it is tempting to list a large number of fonts in the “FontNames” field of a font importer. Any fonts listed in the “Font Names” fieldwill 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 inthe “Font Names” field.

对于必须支持大量字符集的应用程序,它会尝试将大量的字体列入“Font Names”中。如果一个图形不能被位于之前字体内,任何列入“Font Names”的字体都会被作为后备使用。后备顺序由“Font Names”中文字的顺序决定。

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

但是,为了支持这种行为,Unity会保持加载内存时,所有的字体都列入“Font Names”中。如果一个字体的字符集很大,那后备字体的内存消费会过大。这中情况在使用象形字体,如日语字体和中文字体时经常遇到。

Best Fit and performance
最佳匹配和性能

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

一般情况下,UI文本组件的最佳匹配设置不应该被使用。
“Best Fit” dynamically adjusts the sizeof a font to the largest integer point size which can be displayed within aText component’s bounding box without overflow, clamped to a configurableminimum/maximum point size. However, because Unity renders a distinct glyphinto the font atlas for each distinct size of character being displayed, use ofBest Fit will rapidly overwhelm the atlas with many different glyph sizes.

“Best Fit”动态的调整字体大小到最大整数点大小,在文本组件内显示而不出现overflow,介于最小/最大点之间的大小。然而,因为Unity会为显示的每个不同大小的字符渲染一个不同的图形到字体图集,使用Best Fit将会用很多不同大小的图形,快速填充图集。

As of Unity 5.3, the size detection usedby Best Fit is nonoptimal. It generates glyphs in the font atlas for each sizeincrement tested, which further increases the amount of time required togenerate font atlases. It also tends to cause atlas overflows, which causes oldglyphs to be kicked out of the atlas. Due to the large number of tests requiredfor a Best Fit calculation, this will often evict glyphs in use by other Textcomponents, and force the font atlas to be rebuilt at least once more after theappropriate font size has been calculated. This specific issue has been correctedin Unity 5.4, and Best Fit will not unnecessarily expand the font's textureatlas, but is still considerably slower than statically-sized text.

.3,使用Best Fit来改变大小不是最佳选择。它会给每个不同大小的文字生成图形,进一步增加生成文字图集的时间。由于旧的图形在图集中被移除,这也会导致溢出。由于Best Fit计算需要大量的测试,这会经常移除其他文本组件在使用的图形,并强制图集在计算出合适大小字体后,至少被多重建一次 。这种特殊的问题在Unity5.4中已经被解决了,Best Fit不必扩展字体的纹理图集,但仍然比静态大小的文本慢。

Frequent font atlas rebuilds willrapidly degrade runtime performance as well as cause memory fragmentation. Thegreater the quantity of text components set to Best Fit, the worse this problembecomes.

频繁的字体图集重建将迅速降低运行效率,也会导致内存碎片。文本组件设置的Best Fit质量越高,问题越严重。

SCROLL VIEWS
滚动视图

After fill-rate problems, Unity UI’sScroll Views are the second most common source of runtime performance issuesseen. Scroll Views generally require a significant number of UI elements torepresent their content. There are two basic approaches to populating a scrollview:

填充率问题后,Unity UI的滚动视图是常见地引起运行效率问题的原因。滚动视图需要一些UI元素在它的内容显示。有两种方法填充一个滚动视图:

·        Fill it with all of the elements necessary to representall of the scroll view’s content

用所有需要出现在滚动视图的元素填充滚动视图

·        Pool the elements, repositioning them as needed torepresent visible content.

用池处理这些元素,根据需要重新放置它们的位置


Both of these solutions have issues.

这两种解决方法都有问题

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

第一个解决方法,由于被显示的实例UI元素增加,需要的时间会增加,也增加了重建滚动视图需要的时间,例如在一个只需要显示一些文本组件的滚动视图中,那么这个方法仅仅因为它很简单而受青睐。

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

第二个解决方法,需要一定的代码,基于当前的UI和布局系统,正确的执行。两种可能的方法将在下面详细地讨论。对于任何相对复杂的滚动界面,需要一些池方法来避免性能问题。

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

尽管存在这些问题,所有的方法可以通过给滚动视图添加一个RectMask2D组件来提高。该组件确保在滚动视图窗口外面的滚动视图元素不会出现在可画的元素列表中,当重建一个画布时,该列表的几何体必须已经生成,排序和分析。

Simple ScrollView element pooling
简单滚动视图元素集池

The simplest way to implement objectpooling with a Scroll View while also preserving as much of the nativeconvenience of using Unity’s built-in Scroll View component is to take a hybridapproach:

滚动视图实现对象池最简单的,同时尽量保留Unity内置滚动视图组件便利的方法,是采用混合的方法。

To lay out the elements in the UI, whichwill allow the layout system to properly calculate the size of the ScrollView’s content and allows scrollbars to function properly, use GameObjectswith Layout Element components as “placeholders” forthe visible UI elements.

在UI中布局元素,将允许布局系统合适地使用对象Layout Element组件,作为看得见的UI元素的“placeholders”。

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

然后,给可见UI元素实例一个池,来填充滚动视图看可见区域的课件部分,作为父物体来定位placeholders。由于滚动视图,重用UI元素来显示可以滚动显示的内容。

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

这会减少需要批次的UI元素数量,由于批次的花费只会随着一个画布类画布渲染器的数量,而增加,而不是Rect Transforms的数量。

Problems withthe simple approach

简单的解决问题的方法
Currently, whenever any UI element isreparented or has its sibling order changed, that element and all of itssub-elements are marked as “dirty” and force a rebuild of their Canvas.

目前,每当任何UI元素重新设置父类,或它们同级顺序改变了,该元素及其所有的子元素会被标记“dirty”,强制它的画布重建。

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

这样做的原因是,Unity 还没有将重新设置父类Transform和改变同级顺序分离开。两种事件将回调OnTransformParentChanged 。在Unity UI的图形类(看代码这种Graphic.cs),这个回调会实现并调用方法SetAllDirty。通过标志图形dirty,系统确保图形重建它的布局和在下一帧前,所有的顶点被渲染。

It is possible to assign canvases to theroot RectTransform of each element within the Scroll View, which will thenconfine the rebuild to only the reparented elements and not the entire contentsof the Scroll View. However, this tends to increase the number of draw callsneeded to render the Scroll View. Further, if the individual elements withinthe Scroll View are complex and consist of more than a dozen Graphiccomponents, and particularly if there are significant number of Layoutcomponents on each element, then the cost of rebuilding them is often highenough to noticeably reduce the frame rate on lower-end devices.

可以将画布分配给在滚动视图内的每个元素根RectTransform,这会限制仅仅重新设置的父类元素重建,而不是滚动视图所有的内容。然而,这会导致滚动视图需要的draw calls数量增加。此外,如果视图窗口内不可见的元素很复杂,并带有十几个图形组件,特别是如果每个元素都带有布局组件,那么在低端设备,重建的开销会高到明显减少帧率。

如果一个滚动查看UI元素不具有可变大小,然后布局和顶点的这个完整计算是不必要的。然而,避免这种行为,需要基于位置的对象池解决方案的改变,而不是父母或兄弟姐妹,为了改变的执行。

If a Scroll View UI element does nothave a variable size, then this full recalculation of layout and vertices isunnecessary. However, avoiding this behavior requires the implementation anobject pooling solution based on position changes instead of parent orsibling-order changes.

如果滚动视图UI元素没有可变大小,那么完全重新计算布局和顶点,不是必须的。然而,避免这种行为,需要启用基于位置改变而不是同级顺序改变的对象池解决方法。

Position-basedScroll View pools
基于位置的滚动视图池

In order to avoid the problems describedabove, it is possible to create a Scroll View that pools its objects by simplymoving the RectTransforms of its contained UI elements. This avoids the need torebuild the contents of the moved RectTransforms if their dimensions are notaltered, significantly improving the performance of the Scroll View.

为了避免上述提到的问题,可能创建一个滚动视图,通过简单地移动它包含的UI元素的Transforms,池管理物体。这避免了需要重建移动RectTransform,大小没有改变的元素,显著提高了滚动视图的性能。

To accomplish this, it is generally bestto either write a custom subclass of Scroll View or to write a custom LayoutGroup component. The latter is generally the simpler solution, and can beaccomplished by implementing a subclass of Unity UI’sLayoutGroup abstract base class.

要做到这点,通常最好是要么写一个自定义的滚动视图的子类或者写一个自定义布局组的组件。或者通常是相对简单的方法,可以通过继承Unity UI的LayoutGroup抽象基类的子类来实现。

The custom Layout Group can analyze theunderlying source data to examine how many data elements must be displayed andcan resize the Scroll View’s Content RectTransform appropriately. It can thensubscribe to Scroll View change eventsand use these to reposition its visibleelements accordingly.

自定义布局组可以分享源数据,检查多数数据元素需要显示,并可以适当调整滚动视图内容的RectTransform。可以查看Scroll View change event,使用它来重新定位可见元素。

FOOTNOTES
附注
1.    This includes UI Text components whoseparent Canvases are enabled, but that have disabled Canvas Renderers. ?

这包括父类画布启动但是画布渲染器被禁用的UI文本组件。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多