You’ve built your game and look at it in awe. But something’s not right. Whether it’s the low framerate, high CPU usage or rapidly draining battery, the dreaded words slowly come to mind.
“I need to optimise this.”.
Today I will be writing about one of the less known ways you could be losing performance in Unity and how to fix that. But before we dive deep into UIs, know this: every time you optimise your game, your first step should be always the same – make sure you know what the cause is. Unity’s Profiler is a great tool to help you find out. There you can verify which part of the game slows it down, which may be scripts running, physics being calculated, interfaces being rendered and others.

But wait, you say, I’m just sitting here in the main menu doing nothing, and the CPU is going crazy!*

Well, my friend, I’m afraid your game might have a ‘breaking’ problem.

Let’s talk dirty

Actually, let’s talk batching first. What is batching and why should you care?
Batching is the process of generating visual results based on the Canvases you’ve setup in Unity. All your Canvas components such as Images, Texts etcetera have their meshes combined and sent to Unity’s graphic pipeline to render on screen. This process is smart and caches the results until any Canvas changes occur. So, when you render your main menu, the result is reused every frame, until you make changes and ‘break batching’. And if you’re not aware of how it works, chances are you might be breaking batching every frame, causing your UI to be pointlessly rerendered.

When does batch breaking occur on a Canvas? Whenever it is marked as dirty.

What causes it to be marked as dirty?

Everything.

I literally mean that, any single change you make to a canvas or the child of that canvas, will travel up the hierarchy tree and dirty it. Changing a Text value, transform position, colours, scale, rotation, setting Game Objects active – they all mark their parent Canvas as dirty. You may be thinking “Oh God” right now, and rightfully so. If you have a fancy spinner rotating as a part of a complex UI, that spinner may be causing your entire Canvas to be rebuilt every single frame. On desktop this may or may not be a problem, but on mobile it affects your battery.

Possible solutions

Fortunately, there are ways to fix or minimize this.

Remember, any changes you make to an UI element mark the parent Canvas as dirty. The solution to that is using nested Canvases.

But don’t go and add a Canvas component to every single Game Object in your scene just yet, because that will add time to the batch building process as well. The best way to utilize this is to sort your objects under a Canvas into static and dynamic. For example, if you have a health bar composed of a border, background image, fill image and text, you’d separate the dynamic fill image and text from the static rest, and add a Canvas component to it. This way, whenever you change your image’s fill amount, you will only dirty that small fraction of the whole UI and cause only it to rebuild.

Another way to save CPU time is to disable the Canvas component instead of setting the game object inactive. When you turn it back on it will reuse it’s last results without rebuilding. Although, if you have many MonoBehaviours working under that Canvas, if you don’t set it as inactive, that means you have to manually disable each of them. Just keep that in mind.

Last but not least, to squeeze those milliseconds of performance.

If you work on a complex UI that gets populated during runtime, such as an inventory system, you will often find yourself in the process of creating a new element and configuring it. You should know, that as long as the game object is inactive, it will not bother it’s parent Canvas with marking it as dirty. If it is active, it will send messages up the hierarchy tree, checking every single parent for Canvas components and marking it dirty with every change.

You can minimize that operation to occur only once with structuring your code NOT like this:

MonoBehaviourInventoryClass uiElement = Instantiate(prefab,layout).GetComponent();
uiElement.gameObject.SetActive(true); //set object to active, marking the canvas as dirty
uiElement.image.fillAmount = 0.2f; //make a change, marking the canvas as dirty
uiElement.header.text = “Sword”; //also marking the canvas as dirty

But like this:

MonoBehaviourInventoryClass uiElement = Instantiate(prefab,layout).GetComponent();
uiElement.image.fillAmount = 0.2f;
uiElement.header.text = “Sword”;
uiElement.gameObject.SetActive(true); //this is the only call that will mark the canvas as dirty

I hope this was a productive read for you, now go and get those framerates up!

*overreaction for dramatic purposes

From the blog

the latest news, technology & app development insights

Key differences between Azure and AWS

Compute power Both Azure and AWS EC2 allow their users to configure virtual machines and specify the amount of processing power and amount of memory, along with the locality of the VM. Azure users can spin up a clean VM, or can optionally provide their own VHD or...

A Brief Enquiry Into The Smart Phone Era We Are Now Living In

The smart phone epidemic has rapidly spread throughout our society. Smart phones have become a prominent feature in the everyday work and personal life of the general public, and this is on a constant up rise. This advance in society is due to new technology being...

GET IN TOUCH

let’s explore your idea

+44 (0) 1234 834822