Unity recently posted a guide to optimizing the Unity UI:
I was very eager to read it, because up until now there hasn’t been much information about those topics. I look forward to more of this kind of information being included in the manual, but until then it’s great that this has been made available.
It came at a really opportune time, as I was starting to optimize my UI, but I had so many questions that weren’t answered in the manual or the forums. Luckily some of those questions, and probably the most important ones, were answered in those posts. Yay!
One of the things I’ve struggled with is keeping draw calls low. I have a complex UI with many moving parts, and at times it’s had a ridiculous number of draw calls. As I’m still adding functionality, I haven’t properly begun the process of optimization yet. That will come as I replace the placeholder graphics with final graphics. But there are many things I could have done to make the UI more efficient from the beginning.
If you’re just starting out, some of the information in Unity’s guide may go over your head. But there are some real gems in there which can help to make UI creation much more efficient, and definitely increase performance. Here are some of the things I learned:
Don’t use Dynamic Text
I’ve been using a lot of dynamic text as I develop the UI, with a view to swapping it all out for TextMesh Pro text when I do the final design. I’ve been setting the size of each text element to best suit that element without realizing the impact of doing that. I’ve also used
best fit for some elements, although quite apart from performance issues, I’ve found that it usually looks bad to have different sizes of text in similar elements so I’d already planned to remove it.
While I knew that dynamic text isn’t efficient or fast, I learned that that Unity adds to the font atlas for every different size of text, and regularly rebuilds that atlas. It’s much better to specify the character set and size (large enough to accommodate your largest text) so the font atlas is built once and is a known size.
Use Anchors instead of Layout Groups
My UI wasn’t very complex before I was neck deep in layout groups and layout elements, especially as I’ve got quite a lot of dynamic content. Soon those layout groups were my go to component for, well, layout. I didn’t even think of using anchors if a layout group did what I needed.
I learned that those layout groups are re-evaluated every time they are marked as dirty, which basically means lots of calculations any time anything is changed. Calculations that are completely unnecessary if we can accomplish the same thing using anchors.
Use more Canvases
I started out using 3 canvases. One for my background image, one for my main UI elements, and one for elements that need to be placed over the top of everything else.
I learned that every time something in a canvas changes, the entire canvas is re-evaluated and re-drawn. So for anything but the most simple of UI’s, the benefit of splitting a UI into multiple canvases can be significant. Even for a simple HUD, splitting it into a canvas for static elements and a canvas for dynamic elements is likely to be much faster.
The downside is that each canvas increases draw calls. So we still need to balance speed for efficiency when deciding to split a canvas.
Disable invisible UI
I have lots of UI screens moving in and out of the camera view, all contributing to draw calls. Sometimes I can disable the game objects, but other times I still need them to be active. The trouble is that all those active UI elements are still calculated and added to the draw calls even though they aren’t seen.
Unity suggests a few ways this can be dealt with, but there’s a really simple solution that isn’t mentioned in this context, that does a great job all by itself, and is even better when combined with other strategies. Introduced in Unity 5.2, it’s the
Rect Mask 2D.
Mask your UI
Rect Mask 2D component on your main canvas. The
Rect Mask 2D hides all the UI elements not on screen. BAM!! you’re done!
This alone sliced my UI draw calls from around 26-30 to only 6-7 without any other optimization.
What’s a UI shader?
What’s the difference between using no shader and a UI shader? What do the UI shaders do? What’s the fallback used when there’s no shader specified? Why is there no shader on the default UI objects when UI shaders exist? What’s the difference between the different UI shaders?
These are just some of the questions I had about UI shaders. Not having delved into shaders yet, they’re all a bit of a mystery to me. What I really wanted to know was which of the default UI shaders should I use for different things. I didn’t even have a starting point for figuring this out because none of the UI shaders are mentioned in the manual, and none of the default UI elements have shaders specified!
I learned that the
UI Default shader is quite flexible, but overkill for many elements. The
UI Fast-Default shader that’s provided in the post (instead of the application for some reason) is a much faster one to use when we don’t need the fancy stuff.
As to my still unanswered shader questions, when I find the answers I’ll share them in a future post.
Don’t use the default Scroll View
Perhaps my biggest takeaway from the whole thing is this one line in bold:
Despite these issues, all approaches can be improved by adding a RectMask2D component to the Scroll View.
Along with my questions about the UI shaders, I’d been questioning the efficiency of the default UI objects, especially after learning about how much better
Rect Mask 2D is than the standard
UI Mask. Why isn’t the default
Scroll View using
Rect Mask 2D? What does that mean for the other default UI objects?
It’s apparent that even though Unity’s default UI objects are really useful as examples for how to use the various UI components, they aren’t optimized, and in many cases haven’t been updated to use the most recent components or best practices.
Why haven’t they been updated? Who knows. What’s important is that now we know not to rely on them. My next post will be about what we can do to create our own better default UI elements.