Unity UI Best Practices

I spent a lot of time reading these forums, forum/net searches, etc. just to find out what information I could on the new UI and how to do it because I don’t see an actual ‘best practices’ thread anywhere. The tutorial project was fantastic to learn the basics, but then you’re left with questions when you delve in.

  • First and most importantly - watch the tutorial videos: UI Beginner Tutorials
  • Download the example project and play with it: Asset Store UI Example Project
  • How to disable a UI object from blocking raycasts without disabling the object? Add CanvasGroup behavior, disable the BlockRaycast option.

So once you’re done with that, you go build some UI and wonder why ngui/df-gui/etc. performance was so much better. For me I had horrible performance with just a UI (no game) that had few animated texts, 2 scrollRects, and just a bunch of images text. That’s when I decided more reading was necessary.

  • UI/Sprite textures aren’t packed into a texture atlas by default. A tag is needed to group them. Read the documentation / tutorial on the sprite packer.

  • Overlapping text/graphic boundaries with another text/graphic will create additional draw calls, even if the actual visual graphics do not overlap.

  • Grids (other layouts too I presume) need minimum 1 pixel spacing between items, else items are considered overlapping and will create additional draw calls.

  • Images with alpha 0 are still rendered. They are rendered with alpha 0 even though they are not seen.

  • Unity currently does not support non-rectangle shapes as Sprites, so using the TightSpitePacker policy will cause image artifacts.

  • When UI objects are offscreen, they are still batched (rendered as well?). Possible solutions:

  • Change parent to non-UI parent, as UI Camera will no longer detect it

  • Change layer of panel so UI Camera will no longer detect it

  • Disable gameobject when off-screen

  • ScrollRect performance tuning

  • ScrollRect will rebuild / reload everything every frame if pixel perfect is enabled

  • Disabling the layout behaviors when it’s not being used / updated (canvas size fitter, layout groups, layout elements)

  • For larger lists, disable off-screen objects

  • Canvas PixelPerfect has large performance impact. Disable it if possible.

  • Looping animations should be stopped when off-screen.

  • Fonts are not part of the texture atlas, unlike ngui/df-gui/etc. This will produce additional draw calls. Here’s a post on adding your own bitmap font to use, which I believe you can add to the texture atlas? Unity 4.6 Bitmap font? Haven’t tested this, but looks promising.

I’m surprised there isn’t already a topic like this. If I’m wrong on some of these points, please let me know. Add to it. It’s been compiled from reading many, many, many forum posts. After all the optimizations, my draw-call count is very close to my previous UI (with ngui) and performance is very good.

I am sure more can be added. Maybe someone ought to move most of this over to the FAQ. The FAQ page could use some scrubbing, as “asking a question instead of making an actual forum post” doesn’t really make it a frequently asked question.

36 Likes

Fantastic post @Yukichu Couldn’t have put it better myself. @phil-Unity could this be made a sticky?

1 Like

Nice one. Note that if you are doing mobile button controls where the user holds down the button you have to struggle a bit to make it work well. It has been noted that there is no simple ‘isButtonDown’ or better ‘isButtonTouched’ function/getter so you have to manage it yourself

Solutions for isPressed:

BUT - there is an issue still - the standard buttons expect you to start and end your touch on one button, what if you slide your finger onto a button or between buttons as you might well do in a mobile arcade game - the solutions linked above don’t handle that, so then you might resort to raycasts for each touch at the beginning of each frame to see what UI buttons are touched, using EventSystem.current.RaycastAll(…);

1 Like

What a complete headache @andyz – thanks for the addition.

Apparently if you want to interrupt OnDragBegin or OnDrag, you set the PointerEventData pointerDrag to null. As soon as it’s set to null, it stops moving. You can then move it where ever you like, such as snapping back to original position.

1 Like

Great post, Yukichu. A lot of helpful information.

Another solution I found through experimenting is to add the Canvas component to any GameObject that you are hiding through a CanvasGroup. This way the renderer seems to realize that he shouldn’t render GameObjects that have a CanvasGroup.alpha value of 0. Setting the a CanvasGroup.alpha value to 0 is still mandatory though. Moving it offscreen still won’t work.

But remember: If you need interactions on a GameObject you are hiding this way, you also need to add the Graphics Raycaster component otherwise it won’t receive interaction events.

I don’t find this to be the case; unfortunately the batch count remains the same regardless of whether the Canvas Group alpha is zero or not.

I am astonished that off screen UI elements also contribute to the batch count, which is what led me to find this excellent post by Yukichu in the first place.

That’s odd… Because it works for me in Unity 5.0.1f1. As I said, you need to both have ‘Canvas’ and ‘CanvasGroup’ on the GameObject.
Sadly having this many Canvases in a UI structure seems to mess up the rendering though. Especially when used in conjuction with Masks. So this workaround is not really working for us.

Yeah, me too. I hope Unity improves their Canvas rendering so that offscreen and invisible elements are ignored.

[EDIT] Apologies - I hadn’t followed your instructions properly and didn’t add the Canvas component (just the CanvasGroup). It does work, and in addition (for me) it works if I simply add a Canvas component to a GameObject (that has an Image component), and then turn the Canvas off - the batch count decreases by one and the Image is no longer drawn. This is without a CanvasGroup component attached. But I agree this is not an ideal solution for everyone.

I had written a system where it disables and enables off screen UI GameObjects, and while it worked fine on Desktop and iOS, for some reason the same code messed up the rendering order on Android after a GameObject was disabled and enabled for the first (and every subsequent) time. To be specific, Text on top of Image buttons were vanishing and then reappearing after roughly a 1 second delay! But this didn’t happen for every button, even when their structure was identical in some cases, with the only difference being position.

All this wouldn’t be an issue if the UI camera culled stuff in the same way as a regular camera. I wonder what forced them to make it different?

[EDIT 2] denis_va, I owe you a massive thanks! I reworked my system so that instead of disabling / enabling UI GameObjects, it disables / enables attached Canvas components (without CanvasGroup, but with Graphic Raycasters on the elements that are interactive) - and it works on iOS and Android! It might be because my UI is fairly simple, and I don’t really use masks (just in one place), but whatever the reason it has solved my problem. Of course, I would still rather Unity ignored off-screen stuff, but as a workaround this has helped a lot - thanks again!

Glad I could help. Disabling the Canvas directly seems like a good approach to keep the number of components down.

I still hope that they will implement an easier way to hide Objects, like the ‘visible’ parameter in Flash.

1 Like

For world space canvases that are not intractable (like health bars) remove the graphics raycaster. Having a high number of unused raycasters in your scene can chew through performance.

You don’t know how much this guide helped me :slight_smile: thanks you very much kind sir

Yeah I’ve been looking at my UI and I get a new draw call for every UI text element. However, this includes bitmap fonts (in my case using the CJFinc: Bitmap Font Tools extension). I was under the impression that by using an identical bitmap font in mutiple places would batch down to a single call but it looks like no. I still get a fresh draw call for each text element. Hopefully I can find a way around this as it could cripple my game (very text heavy)

That sounds awful. I’ve been wrestling with whether to pay money for TextMeshPro or not. I find it highly annoying that the text solution for the default UI is not very optimized.

I have just begun learning the new UI lately and found that this post is super useful.
Thanks Yukichu

But an overlapping graphic boundaries from the same packing tag won’t create additional draw calls right?

Even if there is no overlap, the hierarchy does effect the draw calls. If I put an image between 2 text widgets in the canvas hierarchy even with no overlap in any of them, they will draw the two texts separately. Doesn’t matter if it is Dynamic or Bitmap Font, tried both. This causes problem as many components need the combination of text and image like the comboboxes. Anyone have any recommendation?

Excellent post, any more lessons learnt since March…

So many thank to yoy sir ! I will bookmark this post

I read about a similar issue with sprite rendering and 2D Layers: apparently, making a “multi-texture layer sandwich” (i.e. sprite from texture A in layer 1, sprite from texture B in layer 2, sprite from texture A in layer 3) will break the sprite batching, and so multiple drawcalls will ensue. Maybe the text / sprite issue is because of a multi-texture hierarchy sandwich?

Absolutely invaluable post thank you @Yukichu .

I had my hidden UI elements offscreen but hit the excessive draw call issue.

Hiding them by moving them to a non canvas parent boosted my FPS on android by 20+ and reduced over 30 draw calls.

Just note its important to not retain world position when setting parent so the UI positioning doesnt go wack when screen resolution or orientation changes.

//Hide component
uiComponent.transform.setParent(HiddenParent, false);

//Show Component
uiComponent.transform.setParent(OriginalParent, false);

Hope this helps!

2 Likes