Unity 3D optimization tips

by NSDG • Jul 7, 2018 • 0 Comments
834
Unity 3D is a game engine that is popular not only among indie game developers but a big companies aswell.

It features a user-friendly interface, powerful Rendering pipeline, an easy-to-use component system and lastly, it supports a wast array of platforms.

But with an easy-to-use interface it's even easier to overcomplicate your game (for example by placing alot of unncesessary objects etc.), so it's important to have optimization in mind during the whole course of development.

So here is an important tips for 3 major categories (Rendering, Scripting and Audio) that will help you improve the performance of your game:

Rendering


Tip 1: Keep the objects with Renderer component unscaled

Unscaled objects are those that have a scale of (1, 1, 1). In that case Unity does not have to do additional calculatinons to rescale the object each frame.

Example: Let's say you have a model of a house which is too big or too small for your level. The natural thing to do is to scale it like this:

Building resize

Most of the time it's Ok to scale the object in Scene view, but if you plan to have alot of duplicated instances of that object it's preferable to change the scale in the Import Settings.

First thing you need to do is to scale the model until it fits your needs (ex. the building above was scaled from (1, 1, 1) to (0.49482, 0.49482, 0.49482)), then select the model in the Project view and in the Import Settings note the Scale Factor (Usually it's either 1 or 0.1).

Set the new value, which should equal default scale factor x the new scale (In my case it's 1 x 0.49482 = 0.49482), then hit Apply. Now go back to the model in the scene view and set the scale back to (1, 1, 1).

Unity 3D Scale factor settings

The object is now scaled the way you need while preserving the default (1, 1, 1) scale.

This tip is especially important for animated objects that use SkinnedMeshRenderer as this component is more expensive to render, and having a scale of (1, 1, 1) simplifies the rendering process.

Tip 2: Use as few lights as possible

There is 3 types of lights in Unity (Directional light, Point light and Spot light). In terms of performance Directional light is the cheapest for rendering, then Point and lastly the Spot light.
Generally you don't want to have more than 1 directional light per scene and for a spot and point lights try to have as few as possible (or none at all).

In terms of realtime shadows, while it enhances the visual aspect of the game, it has a high performance overhead so generally it is better to disable them or bake them into lightmaps and light probes.

You can read more about Lightmapping by clicking here and to learn more about Light Probes click here.

Tip 3: Use legacy shaders

Not long ago Unity have introduced a new Standart shader (which eventually become a default shader). But while it offers alot of features like Physically Based Shading and better visual quality, it has a higher performance overhead compared to legacy shaders.

So if the platform you targeting has a limited resources you should use Legacy Shaders/Diffuse or Mobile shaders if you targeting mobile platform.

Scripting


Tip 1: Always cache component references

You should always cache component references if you plan to access them every Update.

For example check the script below:

using UnityEngine;

public class Script1 : MonoBehaviour
{
    float someValue = 0;

    // Update is called once per frame
    void Update()
    {
        someValue = GetComponent<Script2>().someValue2;
    }
}
Here we have Script1 which get's the variable "someValue2" from Script2 and assigns it to a local variable.
Now, calling just one GetComponent each frame won't have any impact on performance, however you should adopt a habit of caching the components that will be used frequently.

There is two ways you can cache a component, either create a public variable and assign it in Inspector view, or create a private variable and assign it from Start or Awake.
Check the example below:

using UnityEngine;

public class Script1 : MonoBehaviour
{

    float someValue = 0;

    Script2 script2Cached;

    // Use this for initialization
    void Start()
    {
        script2Cached = GetComponent<Script2>();
    }

    // Update is called once per frame
    void Update()
    {
        someValue = script2Cached.someValue2;
    }
}
Much better, Script2 can now be accessed each Update without performance overhead.

Do the same for built-in components, such as BoxCollider, Rigidbody etc. (except transform and gameObject, those are already cached by default so you can access them right away).

Tip 2: Use SendMessage with caution

SendMessage let's you call a specific function (if exist) on every MonoBehaviour that is attached to a game object.

For example when you shoot a weapon in the game you can quickly inflict damage when the bullet hits the enemy, without the need to use GetComponent and other extra stuff.
However this method should not be called too frequently as it's really slow.

Tip 3: Use GameObject.Find and GameObject.FindWithTag with caution

GameObject.Find, GameObject.FindWithTag and GameObject.FindGameObjectsWithTag let you quickly search for the objects in the Scene. These methods are much slower than GetComponent and should only be used during initialization.

Tip 4: Avoid using OnGUI

Historically OnGUI was the only way to make menus in Unity 3D. But since then, an alternative was added called UI Canvas which is much better in terms of performance, but may be a bit tedious to setup.
Nevertheless OnGUI still remains a viable way to make UI in Unity and if you absolutely need to use it, keep in mind that OnGUI is called atleast twice per frame (twice as much as Update and LateUpdate) so do not use it for any calculations besides UI.

One thing you can do, is to have a separated script that only has OnGUI in it and enable/disable it when needed.
For example:

UIScript.cs
using UnityEngine;

public class UIScript : MonoBehaviour {

    void OnGUI()
    {
        if(GUI.Button(new Rect(5, 5, 125, 25), "Button 1"))
        {
            //Button 1 was pressed, Do Something
        }
        if (GUI.Button(new Rect(140, 5, 125, 25), "Button 2"))
        {
            //Button 2 was pressed, Do Something
        }
    }
}
Script1.cs
using UnityEngine;

public class Script1 : MonoBehaviour
{

    UIScript uiScript;

    // Use this for initialization
    void Start()
    {
        uiScript = GetComponent<UIScript>();
        uiScript.enabled = false;
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Tab))
        {
            //toggle UIScript when Tab is pressed
            uiScript.enabled = !uiScript.enabled;
        }
    }
}

Both UIScript and Script1 are attached to the same GameObject. Script1 controls when to show the menu.
When the player press Tab the UIScript is enabled, showing the buttons. Pressing Tab again deactivates it, hiding the buttons.
While UIScript is deactivated, OnGUI method is not called which in turn improves performance.

Audio


Audio in Unity has a big effect on performance and the wrong import settings could drastically reduce the framerate.

Usually there is 3 types of sounds: Those that are played frequently, periodically / rarely and the ambient sounds.
Each type need to have a correct import settings for the best performance.

For example in my project I have 3 sounds with a different purposes and as you can see all of them have default import settings which are not suitable for every sound type.

Audio clips import

1. Frequently played sounds

These are the sounds that are played in large quantities (ex. weapon sounds, footsteps, impact sounds etc.).
Such sounds work best with the settings below (These settings are also suitable for any short sounds (under 10 seconds) even if they are not played frequently)):

Load Type: Decompress on Load
Compression Format: ADPCM

(From Unity documentation) Decompress On Load: Audio files will be decompressed as soon as they are loaded. Use this option for smaller compressed sounds to avoid the performance overhead of decompressing on the fly. Be aware that decompressing Vorbis-encoded sounds on load will use about ten times more memory than keeping them compressed (for ADPCM encoding it’s about 3.5 times), so don’t use this option for large files.
(From Unity documentation) ADPCM: This format is useful for sounds that contain a fair bit of noise and need to be played in large quantities, such as footsteps, impacts, weapons. The compression ratio is 3.5 times smaller than PCM, but CPU usage is much lower than the MP3/Vorbis formats which makes it the preferrable choice for the aforementioned categories of sounds.


2. Periodical or rare playing sounds

Sounds that do not need to played frequently, for example an announcer voice at the beginning of the round, a timer sound at the beginning of the racing game or basically any sound that is over 10 seconds but under 1 minute.

Load Type: Compressed In Memory
Compression Format: ADPCM

(From Unity documentation) Compressed In Memory: Keep sounds compressed in memory and decompress while playing. This option has a slight performance overhead (especially for Ogg/Vorbis compressed files) so only use it for bigger files where decompression on load would use a prohibitive amount of memory. The decompression is happening on the mixer thread and can be monitored in the “DSP CPU” section in the audio pane of the profiler window.

3. Background/Ambient sounds

Usually background sounds are over minute long so the settings above will not be suitable for these types of sounds.
The settings below should be applied to the sounds that are over 1 minute long.

Load Type: Streaming (or Compressed In Memory if you are targeting WebGL)
Compression Format: Vorbis (Adjust the Quality according to your needs)

(From Unity documentation) Vorbis/MP3: The compression results in smaller files but with somewhat lower quality compared to PCM audio. The amount of compression is configurable via the Quality slider. This format is best for medium length sound effects and music.

Hope you found these tips useful. Feel free to share this post on social media!