Optimize Your Games In Unity – The Ultimate Guide

When developing your game, one of the hardest things to do is optimization, no matter if you are creating a mobile, desktop or a console game.
And we all know that if your game is not well optimized, it will result in downloads lost, refunds requested, and ultimately labeling your game and your game development company as the ones who create poor games.
In this post, I will go over everything I learned over the course of 8 years developing games in Unity. I will share with you the wrong way and the right way to do things and I will explain everything I am using in the examples.

Important Information Before We Start

When it comes to optimization the Profiler is your best friend. Always profile your games on the platform you are creating them, especially mobile games.
The Profiler will give you information about the performance of your game and any spikes that are causing low performance.
That being said, everything that I am going to talk about in this post are things that will help you write better and more optimized code and use optimized options for your game which will help you boost your game to > 60 FPS.
The order by which I will write every tip doesn’t matter, I will write everything I know how I remember it and I will add new things as I learn them. You can use the menu navigation on the left side to directly navigate to the part you want to learn.
Now let’s get started.

Always Cache Your Components

A good practice that you need to get used to is to always cache your variables. Code like
is very costly for your game performance, especially on mobile devices. If you need to access any component of any game object, always declare the type of the component you need

and then in Awake get a reference to that variable

I know that we can also use the Start function for this purpose, even the OnEnable function, but I used the Awake function for this example because the Awake function is the first initialization function that is called when the game starts, and if I want to get a reference to variables or initialize variables I always do it in the Awake function as that will be the first thing that will be executed and then the game can start normally.

After that you can safely apply force to your Rigidbody variable:

Caching Components VS SerializeField

There is another way how we can get a reference to cached variables and that is by adding the SerializeField keyword above the variable declaration:

This will expose the variable in the Inspector tab and we can now drag the game object itself providing that it has the desired component attached on it, in the exposed variable field to get a reference to it:

Img-1-2.jpg

Now which of the two methods is more optimized, the answer is SerializeField. Because you don’t need to use code to get a reference to the desired component, and this is very effective especially if you have a lot of game objects such as enemies or collectable items that need to get a certain component when they are spawned.

Now there will be times where you need to get a reference to a component via code, but whenever you can, try to get a reference to the component of a game object by using SerializeField and attaching the desired component in the appropriate slot in the Inspector tab.

Cache Your Non-Component Variables As Well

One of the things that I see A LOT in tutorials and courses online are the following:
Once I profiled a mobile game that I was currently working on, and I found out that by creating a new Vector3 variable in the Update function like you see in the example above, it was taking my code 0.02 ms more to execute the code.
Given the fact that to get to 60 FPS and above, all of your code needs to execute at 16 ms tops, you can imagine how unoptimized this one simple line of code is.
The solution is to cache the variable before you use it:

This is also a rule for any other variable types. It is always better to do

than

So get in a habit to do this with all of your variables especially object type variables like vectors, components and custom classes you create, but also do this with floats, ints, booleans, and strings.

Don’t Use Camera.main

This is also something that I see a lot in online tutorials and course which is killing your performance. Accessing the camera by using
especially if you use this a lot will slow down your game. This is in a way connected to caching that we talked about above, but the solution for this, again, is to cache the camera variable before using i

Avoid Repeated Access to MonoBehaviour Transform

Another thing that you need to be careful of is reusing the transform property of MonoBehaviour. This internally calls GetComponent to get the Transform component attached on the game object.

Again, the solution for this is to cache the transform variable:

Optimizing Strings

One of the heaviest performances in your Unity game are the strings that you are using. Yeah, you read it right, STRINGS.
The first mistake you will see everything online is when collision check is performed:

This is not the way to go. When you are checking the tag of the collided game object it is better to use CompareTag function

Another common mistake is when declaring an empty string people usually write:

A better way is to use string.Empty:

Strings And Text UI

When using strings and text UI you need to be careful when you are updating the text often, especially if that happens in the Update function.

This is something a lot of people do with timers, the usually write code that looks like this:

While this looks like a very simple operation, it is going to slow down your game significantly, especially a mobile.

The reason for that is because a string is an object type variable. Every time you concatenate a string like you see in the line 9 in the code above, you create a new object.

Now imagine doing this in the Update function which is called every frame. You are creating a new object that is stacked up memory every single frame and this is something mobile devices can’t handle.

The solution is to use StringBuilders.

Use string builders wherever you need to use strings often especially when it comes to timers and countdowns.

Avoid Using Instantiate Function During Gameplay

When it comes to Instantiate function, which creates a copy of the provided prefab, you will find different opinions online. The majority say don’t use Instantiate during gameplay.

This is somewhat true. I say somewhat because I’ve used Instantiate during gameplay in one of my mobile games and when I profiled the game it was running smoothly never going below 60 FPS.

This goes to show that you should always revert back to the Profiler and the stats you see there.

That being said, it is always a better idea to use the pooling technique instead of relying on Instantiate, especially if you are spawning bullets, collectable items or any other game element that is often spawned in the game.

If you don’t know what is pooling, I am going to leave a basic generic pooling class below for you to inspect the code and learn:

But sometimes there will be situations where you simply need to use Instantiate as that is the shortest solution, and there is no harm in using it if you see that the Profiler is not showing any issues, so keep that in mind.

Remove Empty Callback Functions

As you already know, Awake, Start, and OnEnable initialization functions are called when the game object is spawned.

Update and LateUpdate are called every frame and LateUpdate is called every fixed frame rate.

The issue with these functions is that they will be called even if they are empty because Unity doesn’t know that these functions are empty e.g. they don’t have any code inside.

even if they are empty like in the example above, Unity is going to call them.
Now you might say, okay but they don’t have any code inside so what is the harm when nothing is going to be executed.
The harm is the fact that whenever a game object is instantiated in the game, Unity will add any defined callbacks, such as Awake or Update, to a list of functions that will be called at specific moments(Awake will be called when the object is spawned, Update will be called every frame and so on).
This will waste CPU power due to the cost of the engine invoking these functions. And this can be a problem especially if you leave empty callback functions in let’s say a bullet prefab that you call every time you shoot, or a collectable item that is spawned when the player makes an achievement and so on.
Not to mention if you are creating a larger game and over time you populate your scenes with thousands of game objects with empty Awake, Start, Update, and so on, which can cause slow scene load time.
I am going to attach the two functions on two prefabs and I am going to spawn 1000 copies of each prefab using the following code:
Let’s take a look at the Profiler and see what happens when we instantiate the object that has empty callback functions and what happens when we instantiate the object that doesn’t have empty call back functions:

As you saw from the example, just by having empty Start and Update functions in the class the spike on the Profiler skyrocketed when we created objects which had that class attached to them.

When Raycasting Use Zero Allocation Code

Avoid using raycast code that allocates memory. All raycast functions have their non memory allocation version:

Leave a Reply

Your email address will not be published. Required fields are marked *