Optimized menu system: SetActive() vs. nested Canvases vs. nested CanvasGroup

Hi,

Our game is 100% based on the new UI. I want to know your opinion about best performant optimized solution to make menus and screen transitions using the new UI.

Our original solution:

A root GameObject with a single Canvas + GraphicRaycaster. Child objects are game screens. Per game screen, I am using a root GameObject with a custom PanelScript and an animator for opening and closing animations. The animations are simple we just translate the screen panel in and out of the screen in one of the 4 directions using RectTransform’s anchors.

After profiling, I realized that the animator is being (re)initialized every time I (re)activate the GameObject to which it is attached. The first time an animator instance is initialized takes longer than subsequent ones for same animator type as I guess there are some IO/serialization operations being done under the hood. So I was thinking I should have this executed during splash screen. Other than the animator’s initialization, we decided to remove as many [H/V]Layout as possible and merge as many Text components as possible.

Now we came up with 3 different approaches to do more optimization and would like to know your opinion:

  1. All screen GameObjects are active by default in the scene and will be deactivated once the first screen (splash) is opened. Toggle GameObject screens: SetActive(true) when a screen is about to be opened and SetActive(false) for the closed screen.
  2. Keep all screen GameObjects active in the scene, have a disabled Canvas per screen (a GraphicRaycaster is required per Canvas, top one is removed though). Toggle Canvas: canvas.enabled=true when a screen is about to be opened and canvas.enabled=false for the closed screen.
  3. Keep all screen GameObjects active in the scene, have a CanvasGroup with alpha=0f per screen. Toggle CanvasGroup: canvasGroup.alpha=1f when a screen is about to be opened and canvasGroup.alpha=0f for the closed screen.

I believe the best possible solution would be: Main Canvas->Screen Canvas + GraphicRaycaster + Canvas Group.
Once a screen is closed Canvas Group alpha = 0 and disable the GraphicsRaycaster, do not disable the object. With the alpha at 0 it should no longer render and with the GraphicsRaycaster disabled it won’t try and receive any input. Also with a lot of screens thats a lot of GraphicsRaycasters and they can cause a hitch when all of them are processing the same click.

If you move a UI element within the canvas it marks the canvas as dirty and rebuilds the mesh(rendering optimizations). If you have lots of screens and only one main canvas it needs to rebuild the mesh with all of those screens included, which can take a bit to do with lots of components and screens. If you have pixel perfect checked this will most certainly cause even more performance issues. As for the layouts, unless items are added and removed at runtime, only use them to setup the layout then remove or disable them.

I wonder if this is true. That when alpha is set to 0 via the canvas group, will it stop rendering? Because in the editor you can still see the gizmos.

I believe alpha to zero still renders, there is unite talk from `07 that goes into detail on this if someone is searching

Hi @jimid ,

Do you mean this?
I watched it more than once.
So the answer is 2.? toggle canvas.enabled?

I think the canvas group option is, oddly, the most efficient. I’ve been profiling the various options, and CanvasGroup.alpha = 0 DOES stop rendering. Turning it on and off doesn’t seem to incur the same CPU overhead.

1 Like