How To Optimize a Unity Game

Join the Discord. Talk about Game Dev. Talk about Gaming.

Last updated: 7 September, 2021.
I will update this periodically if I discover anything new.

Understanding Optimization

Before reading this article it helps to understand what we mean when we use the word optimize. To optimize your game means to make sure it runs at it’s best possible performance. There are a few key areas that will affect this more than others:

  • The size of the game
  • The size of files within the game
  • RAM and CPU usage
  • The physical size of graphical objects
  • Calculations

When your game runs, it is set to run at a consistent frame rate, eg 60 frames per second, or 60fps. That means your game will attempt to process everything you’ve told it to do, 60 times per second. What we want to do is limit how much the computer has to do every frame, every second, which in turn will speed up your game. We also want all files to be only as small as they need to be, and no bigger.

This optimization guide won’t cover everything you can possibly do to optimize a game, but I can share the tips I’ve learned so far that got me the most benefit for the least effort.

My Experiences

I’ve worked on a couple of larger Unity projects now. One was a study RPG project called World Beneath. The other was my recent game Puzzledorf on Steam.

World Beneath was in 3D. It ran fine on PC but didn’t run well on the ps4 dev kit initially, there was a lot of lag.  Eventually I got it to run smooth on ps4. Below is the result:

Puzzledorf also ran really well on my PC, but I noticed it used up way more RAM than it should have and the file size was bigger than it should have been. I haven’t yet tested it on a console dev kit but hope to do so soon.

Profiling

Learn how to use the Unity Profiler and Memory Profiler. Both of these tools work differently but they will help you to find out where your bottlenecks are in terms of RAM, performance and file size. Below are some further links to investigate on the subject.

Memory Profiler here.

Unity Profiler here.

Image & Texture Optimization

Powers of 2

One of the biggest drains on performance was the size of textures and sprites. Where possible, try and use images with sizes at the power of 2, eg, 2, 4, 8, 16, etc. The software draws invisible boxes around objects in multiplies of powers of 2 – if the image is 9 pixels, there will be a box 16 pixels, wasting memory.

This is Unity’s default settings:

  • Max Size: 2048
  • Comression: Normal

That means even if your image is 240 x 100 pixels, it’s actually using up the same amount of memory as if it was 2048 x 2048 pixels. The larger the object is, in pixels, the more memory (RAM) it uses up.

Scaling Up Images At Run-time

If you load an image in at a smaller size and scale it up at runtime, it uses less memory / RAM.

Solid Colours

If an image is a large, solid colour, you can optimize by creating it at the smallest size possible in accordance with the powers of 2 rule, then scale it up at run-time with code.

For example, if your game is 1920 x 1080, and you create an image that is solid black that is also 1920 x 1080, that you’re wasting resources because an image that large uses up a lot of space. The game file becomes larger, but also uses more RAM.

You’d be using less memory by making the image 48 x 27, then scaling it up 40x, or even smaller. You could even just make it a single pixel and stretch it.

In 2D, you’re scaling a sprite. In 3D, then we’re talking about scaling a texture on a mesh. However, in that scenario you may also want to consider using the simplest 3D shape / model if we’re talking about a solid background colour, with quads being the cheapest on resources.

Large Backgrounds and Images Optimization

If an image is really large, consider if it can be broken up into smaller sections. Eg, if you have a long background image, can you cut it up into several chunks that fit in to better powers of 2? This would save lots of memory

3D Object Textures

For World Beneath, we optimized most enemy textures to:

  • Max Size: 256
  • Resize Algorithm: Mitchel
  • Compression: Low Quality

Technically it lowered the visual fidelity but we couldn’t tell a difference.

Also check the dimensions of the texture and make sure you never import it bigger than you need to in Unity.

Pixel Art & Sprites

For pixel art and sprites, check how big your images actually are, then use the smallest size you can for import settings. The sprite sheet for Puzzledorf is 120 x 444 pixels, so I set that to 512 pixels. Nothing needed to be 2048.

Compression didn’t make much difference but it’s worth trying to use stronger compression anyway, so long as you aren’t losing noticeable visual quality.

Sound File Optimization

The RAM usage for Puzzledorf was huge compared to what it should be, and when profiling, I found out that the size of the audio files was far larger than they should be. My music files were around 5mb each but Unity said they were close to 40mb! Apparently this is because the Unity import settings, by default, makes audio files larger which nobody wants. Here are the optimized settings I used for audio files (click on the audio file in Unity explorer):

You could also lower the quality but those settings were just perfect for me. I did that for both sound effects and music files.

I got most of my information from this article which is good extra reading, but my focus was on reducing RAM. While he recommended using uncompressed files for short sound effects, this uses up more RAM and I opted not to do this. The CPU usage for playing sounds is so minimal I would rather spare the RAM.

Object Pooling

Destroying and creating lots of objects at run time uses a lot of memory. The more objects you create or destroy at one time, the more memory you are using up. The bigger the objects, the more memory you use.

It’s better that you re-use a pool of objects, and instead of creating and destroying, you activate and deactivate them.

For example, say you wish to create a fireworks particle effect. You would create the necessary number of particles at the start of the level, and then when you need them, you activate them and move their position, instead of creating new particle objects, then deactivate them when done.

Mainly useful for things you have lots of copies of, like bullets or coins. Any time you are creating objects or particles at run time, especially lots of objects, consider if it might not be better to pre-create them at the start of the level off-screen to use them later.

Active Objects & Culling

Almost to contradict what I said before, be careful you don’t end up having way more objects in a scene than you need.

Each object uses up memory. The more objects you have, the more memory that will take. And if each object has scripts attached running code, especially in Update, that’s an even bigger drain.

There are 2 things you can do:

  • Have less objects
  • Cull objects not in use

By culling I mean, use clever coding to reduce the workload. For example:

  • Do a distance check.
  • Deactivate objects beyond a certain distance
  • Turn off scripts or use clever if statements / conditions to not do unnecessary checks if the player is far away
  • Don’t render things beyond a certain distance
  • Destroy things you really don’t need

In terms of graphics rendering, Unity is supposed to do some render culling for you, ie, not rendering things out of view, but do some research into clever ways to do culling to save on performance.

Resolution

  • It is best to make the game at the lowest resolution possible, especially with pixel art, and let Unity scale the game up at run-time.
  • If you designed graphics at 270 x 480 but want the game to run at full HD, then create the game at 270 x 480 and let Unity scale it up at run-time.
  • My games scale up dramatically at run time without losing any quality

Unused Animators

If you have objects that aren’t using their animators, remove the component. Animators that do nothing still use up a lot of performance even if the object isn’t animated, because Unity still checks them every frame.

Terrain

If at all possible, avoid ever using Unity Terrain. We lost so much performance because of this.

Models

Lower poly models can help, but don’t go too crazy. We actually ended up increasing the tri’s on a number of our plant models. It partly depends on how many objects are active in the scene at once. Textures are a bigger issue.

Combining meshes can also apparently increase performance, but I’ve never tried this. Tools I was recommended to look at for that:

  • Mesh Combine Studio
  • Mesh Baker

Lighting And Rendering

We had lots of point lights which was terrible with forward rendering, the Unity default. Deferred rendering gave a big improvement in performance and lighting quality.

Depending on how you do your lighting and other factors deferred rendering isn’t necessarily for you, so start doing research into choosing a rendering path.

Trigger Events & Collisions

Remember that the player isn’t the only thing that can activate OnTriggerEnter events. If you have an object like a coin sitting inside some sort of trigger, it could be firing lots of OnTrigger events, especially if you have lots of coins sitting inside triggers.

Consider making it so that such objects can’t interact, ie using the physics layers, or deactivating objects beyond a certain distance.

Moving Colliders

Moving lots of colliders can be expensive, so find ways to avoid it. We had hundreds of coins rotating by a script that also rotated their colliders. This meant Unity was constantly doing maths to recalculate the colliders and caused huge lag.

Code Optimizations

Code optimizations are the hardest to do, and generally best done at the end of your project (while in development, best to just get things working). Ideally focus on image and sound optimization first as they are usually the easiest, but code optimizations can be super important. This is where using the Unity profilers can help you to determine where the bottlenecks actually are.

Remember that if your game runs at 60fps, Unity will be trying to run all of your code 60 times per second. With that in mind, you don’t want Unity wasting time and resources checking things it doesn’t have to. So you want to use conditional statements to limit what Unity has to do. Here’s a few tips:

  • Reduce GetComponent calls where possible, they’re very expensive (can you do it once and store it? Or use a public variable?)
  • Reduce unnecessary looping (use break; to end a loop asap, or don’t even run the loop if you don’t have to)
  • Deactivate far objects
  • Limit how much you do in Update
  • Consider whether some code in Update can instead be put inside a co-routine
  • Consider if your co-routines can run less frequently, ie, run them once every second instead of every millisecond, or even once a minute depending on what it is
  • Reduce unnecessary calculations (ie, any time Unity has to measure something, do math, etc. Maybe you can pre-store some of that information, or do it less often, or check against less objects? Any way you can cull unnecessary calculations entirely?)
  • Watch out for refactoring arrays, lists, dictionaries, etc. Too much sorting, resizing and general refactoring of arrays can be very expensive. Don’t do it if you don’t have to or look at optimizing it
  • Use CompareTag() instead of == “tag”, it’s faster, example below
  • Use smart conditional statements. Example below
    if (target.collider != null && target.collider.CompareTag("Enemy"))
    {
        // Do something
    }

When you have an if statement that checks for multiple conditions, it will stop at the first condition that is not true. This is good for optimization. For example, in the above code sample, if target does not have a collider attached, it won’t even try and compare the tag. So by cleverly organizing your conditional statements, in order of left to right, you can reduce the number of conditions checked, which is an optimization if you do it on a large scale, especially if the condition check itself is expensive. That’s on top of the fact that if the condition is false, it won’t run the code { }.

Other Things to Think About

  • Find the settings that are best for your particular project.
  • Don’t stress too much early on in the project about optimisation – you can do it closer to the end, but keep it in mind.
  • Be careful what you download from the asset store – there’s stuff on there that isn’t well made and can harm your performance.
  • Remember that your game generally runs better in build mode than it does in editor.
  • Debug messages and other Debug functionality running in editor can slow your game down a lot, especially error messages, so don’t forget to turn Debug messages off.

Further Reading

Unity has a pretty good article here on graphics optimization.

Join the Discord. Talk about Game Dev. Talk about Gaming.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

Up ↑

%d bloggers like this: