Other UI Optimization Techniques and Tips

Checked with version: 5.3

-

Difficulty: Advanced

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.

RectTransform-based Layouts

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 chapter for details.) If there are 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.

Disabling Canvas Renderers

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 Renderer component attached to that Canvas or Sub-canvas.

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 eliminate the UI’s Graphics from the GraphicRegistry and so they will still be present in the list of components to check for a Graphic Raycast. It does 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.

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.