Unity Performance Improvements
Foreword
A game is no fun if it's lagging. This article covers a few very simple performance improvements that every Unity developer should know about in order to keep the players happy. No one expects you to make a game that looks like a AAA+ title, but instead it should have tons of frames per second.
Little note: when we talk about expensive in a FPS improvement context, we always mean that something is expensive to calculate (what makes our CPU go crazy).
Algorithms and Data structures
The major part when it comes to a game's performance is the developers knowledge about efficient algorithms and data structures. This is a far too big topic to be covered here. If you are new to programming, try to look into the following topics to understand when it's best to use them:
- Lists
- Linked Lists
- HashMaps
- Trees
- Queues
- Stacks
- Sorting (Quicksort, Bubblesort, ...)
Math
To keep our sanity, we will not cover math in this article. However math is important for performance. Imagine a monster in our scene. The player now shoots the Monster - how do we detect if he hit it?
There are countless methods to do that, all of them include math. It's your job to know them and to decide which one is the best method to use. Luckily though, Unity already provides a whole lot of functions that do that job for us. Still, in case of not finding a suitable method, math will come.
Meshes
Let's talk about something that can be learned and applied without much effort: optimizing Meshes. When making a game, we usually have a lot of 3D models in the scene. Each of those models consists of a so called Mesh. A Mesh is just a whole bunch of triangles. The more triangles there are, the less FPS we get, so it's important to keep them as low as possible
For example, a monster model can look really good with only 4000 triangles. There is no need to use a million ones of them. Most of the 3D modeling programs already have Mesh Optimizing features, it's up to you to use them.
If there is no way around a Mesh with a lot of triangles, then there is still another option: LOD (Level of Detail). The LOD concept is simple: when a Mesh is far away from the camera, it is modified so it has less triangles. The player will see no difference since it's far away from the camera. Unity supports this feature, so try it out some day.
Caching GetComponent
Accesing a GameObject's components is usually done with the GetComponent function. This function is expensive in terms of calculations.
Let's take a look at an example about how to not use it:
using UnityEngine;
using System.Collections;
public class Test : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
Health h = GetComponent<Health>();
h.increaseBy(100);
}
}
This example is bad because GetComponent is called once per Update (about 60 times per second). This can be improved very easily like this:
using UnityEngine;
using System.Collections;
public class Test : MonoBehaviour {
// Cache
Health h;
// Use this for initialization
void Start () {
h = GetComponent<Health>();
}
// Update is called once per frame
void Update () {
h.increaseBy(100);
}
}
This example is much better. It offers the same functionality, yet with less calculations because the GetComponent function is only called once at start, and not 60 times per second anymore.
Unity's shortcut properties
To make our lives easier, Unity provides a way to access some standard components without using the GetComponent function, like:
- transform
- gameObject
They are used like this:
using UnityEngine;
using System.Collections;
public class Test : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
Vector3 pos = transform.position;
}
}
It takes some knowledge about Unity to know that this code takes a lot of calculations. The reason is that what it really does is exactly this:
using UnityEngine;
using System.Collections;
public class Test : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
Vector3 pos = GetComponent<Transform>.position;
}
}
Even though it doesn't look like it, it still uses GetComponent every time, which as mentioned above is expensive to calculate.
The proper way to do it would be like this:
using UnityEngine;
using System.Collections;
public class Test : MonoBehaviour {
// Cache
Transform tr;
// Use this for initialization
void Start () {
tr = transform;
}
// Update is called once per frame
void Update () {
Vector3 pos = tr.position;
}
}
GUIs
When using GUIs, the easy way is usually to use the GUILayout class instead of the GUI class. Sadly the GUILayout class is much slower than the GUI class, so think twice if it's worth it or not.
Shaders
Shaders can make graphic cards go completely crazy. Unless you are a Shader expert, it's a good idea to only use the ones that Unity provides. Besides those, there are also Unity shaders with the suffix "Mobile". Those shaders are especially for phones, but they can bring performance improvements on normal computers as well.
Note: Unitys shaders are well crafted, but they are still expensive to calculate. Less is more!
Baking
Let's make some cookies...
Unity has several baking features. For example, when we want to have shadows in our game, what first comes to mind is this method:
In each draw call:
- Position the Lights
- Draw the Scene
- Draw the Shadows
It means that in each draw call the shadows are calculated over and over again. Now what baking does is similar to the caching that we used above. It simply calculates the shadows once and already paints them on our textures, so they don't have to be calculated over and over again. This has huge performance benefits.
Try it out, the feature can be found under Window - Lightmapping. There are a few more baking methods for things other than lighting, just play around with them and see if they benefit your game's performance.
Dynamic Lights
Baking the lights has the downside that if one of those objects moves, its shadow does not move with it since it was baked onto the texture a long time ago.
The opposite of baking lights is called dynamic lights. Wwhen a object moves, the shadow moves too.
Though this sounds cool, there is a huge downside: as a rule of thumb, for each additional light the scene has to be drawn one more time. So if we use 2 lights in the scene, we might end up with half the FPS (and so on).
While this is a rule of thumb and Unity is still pretty fast when it comes to drawing lights, it's generally a good idea to use as less lights as possible.
Allocations
Let's take a look at the following code:
using UnityEngine;
using System.Collections;
public class Test : MonoBehaviour {
// Update is called once per frame
void Update () {
SomeClass x = new SomeClass("Some", 123, "Parameters");
}
}
In each Update call the SomeClass x is created("allocated") with the keyword new. When using the new keyword, Unity allocates some memory from our computer. When the object is not used anymore, it will free this memory again. When done often enough, this can be a very expensive calculation.
There is a lot more behind the whole concept, but for us it's just important to remember that we should avoid new whenever we can.
An alternative could look like this:
using UnityEngine;
using System.Collections;
public class Test : MonoBehaviour {
// Cache
SomeClass v = new SomeClass();
// Update is called once per frame
void Update () {
v.setName("Some");
v.setLevel(123);
v.setSomethingElse("Parameters");
}
}
Summary
Performance is one of the major problems that computer science deals with. We disussed the basic methods to increase our game's FPS without using any scary math. However a big (if not the biggest) part lies in the programmers knowledge about math, algorithms and data structures. Those topics can be overwhelming, but on your journey it's important to learn as much about them as you can.
On a side note: our other articles are usually not optimized for performance in order to keep them as easily understandable as possible. It's up to you to apply what was learned in this article.