July 21 2020

Unity Addressables & SpriteAtlas: How to Efficiently Use Sprites


Are you using Unity UI or Sprite Renderers in your game?

You might then have heard of the Unity SpriteAtlas feature…

SpriteAtlas helps you reduce your game draw calls when rendering UI or sprites in Unity.

However, sprite atlases are hard-to-tame beasts that can bite your hand off.

For example, a single reference to a SpriteAtlas’ sprite will make you load the entire atlas in memory, even if you just need one sprite… or even if you’re not actively using the sprite itself.

Just the reference can skyrocket your memory usage and loading times.

What you want is to have a say about how Unity manages your sprite atlas’ memory so you don’t choke your game performance budget.

Well, you can do exactly that by using the super Unity Addressables & SpriteAtlas wombo combo.

Can they work together?

You bet they can.

TL;DR:

  • Mark only your SpriteAtlas as an addressable asset
  • Use AssetLoadAsync to load a specific sprite from an addressable sprite atlas…
    • Through a AssetReferenceAtlasedSprite variable, OR
    • Through a hardcoded subasset path such as “myspriteatlaskey [myspritename]
  • Use Addressables.Release to unload your sprite

Who’s Ralf and Why Is Ralf Furious?

It’s a sunny Tuesday in summer.

You wake up and, for some reason, you feel extremely happy.

You’re so happy that you start singing a made-up song about how today you are going to improve the architecture of your game.

It’s been decided. You take no more crap.

So you read some documentation and decide your next step: you’re going to put your heavy sprites in a single SpriteAtlas.

That must be a great idea, you think.

Just like you expected, you manage to reduce:

  • Draw calls by 10x
  • Memory usage (RAM + Flash).

So you hit the submit button and call it a day.

It was a great day… and you reward yourself with a pineapple pizza*.

* My italian girlfriend doesn’t approve of this.

However, just as you’re about to sleep, your phone rings… loudly.

“Wh… Who is it?”, you ask, your voice trembling.

“I’m your boss, Ralf. You fu***d up the game. You’re canned for good”.

Hearing these words stuns you for very long 5 seconds, losing a stamina point in the process.

“W…ait what..?” You barely manage to ask as an eerie chill runs down your spine and cold sweat pours down your forehead.

“Nah I’m joking, you’re not fired yet. But get your ass down here and fix the crash that happens in the score screen”, Ralf says just before disconnecting.

Shaking badly anth fever symptoms, you boot up your PC and wait 10 minutes for Unity to load your project.

Eager, you get to the score screen and BAM.

The memory usage exploded…

because Unity loaded the entire sprite atlas in the worst moment possible

After some debugging, you realized that you left out a single reference to a sprite that you did not even draw.

That sprite reference was just there.

Smilying at you.

Waiting for the right moment to get you into trouble.

And… that sprite really got ya.

But you fixed it and learned the BIG lesson:

The Problem: Unity SpriteAtlas’ Default Behavior

When you reference a sprite that lives within a SpriteAtlas, you load the entire atlas into memory and then use the specific region your sprite takes.

Packing and referencing your sprites unoptimally can have nasty side effects, such as crashes and increased loading times.

The first thing you will want to do in Unity when dealing with sprite atlases is to engineer your sprite atlases in a way that it maximizes the economy of your sprites.

That means, you want to pack together the sprites that you use at the same time.

But even if you pack your sprites perfectly, you might still run into trouble.

For instance, you might load the entire sprite atlas too early… when you cannot really afford to.

Imagine you have a complex UI system with TONS of sprites and sprite atlases… a game like The Sims with 4000+ expansions and millions of different item preview sprites.

Here’s the truth: you can’t pay the price of having all these sprite atlases loaded in memory at the same time. And yet, you need to load them on certain user interactions.

Bad news: just referencing them for later (“just in case”) will actually load them immediately in memory, causing the evil final boss Ralf to fire you.

But you can keep your paycheck: we can use Addressables to load/unload your sprites on sprite atlases on demand, no matter the references you have.

In short: let’s you choose when & how to load these sprite atlases.

Level Up: Unity SpriteAtlas & Addressables

[Check the Unity Addressables Benefits first if you don’t know much about addressables]

Loading/unloading sprites from a sprite atlases becomes easy with Addressables.

Just mark the SpriteAtlas (not the individual sprites) as an addressable asset.

This is the wombo combo you’re looking for:

public class AddressableImage : MonoBehaviour
{
    public AssetReferenceAtlasedSprite addressableSprite;
    IEnumerator Start()
    {
        var image = GetComponent<Image>();
        yield return new WaitForSeconds(6);
        
        // Option A) Load atlas & take a sprite
        var asyncOperationHandle = addressableSprite.LoadAssetAsync<Sprite>();
        
        // Option B) Addressable asset key + subasset selection
        //var asyncOperationHandle = Addressables.LoadAssetAsync<Sprite>("Sprite Atlas[TheGamedevGuru-Sample-1]");
        
        yield return asyncOperationHandle;
        image.sprite = asyncOperationHandle.Result;
        
        // Release at some point
        yield return new WaitForSeconds(6);
        image.sprite = null;
        Addressables.Release(asyncOperationHandle);
    }
}

Let me translate that from C# to English…

It reads:

A letter to Unity

Dear Unity,

Thanks for not loading this sprite atlas automatically as you would by default.

I know that you always tell me to use Addressables if I want control over its memory lifecycle, so that’s exactly what I did (line 3).

Now, the time has come. Please load this sprite asynchronously (lines 11/14) and assign it to this blessed UI Image (line 17).

But don’t forget: in a few seconds, I’ll ask you to unload the whole thing from memory when I have no further use for it (line 22).

Thanks,

Your happy Unity Pro subscriber

The result?

You only pay the price when you really need it because Addressables lets you manage this memory yourself.

Doing this is now easier than ever with the subasset referencing possibility (both in inspector and code).

By using addressables with sprite atlases you will reduce your draw calls and your loading times.

Better performance, in turn, will engage your players further into your game.

What’s Next?

If you want to learn more about developing high-performing games that respect your memory boundaries, read my Unity Addressables Tutorial post.

It’s a great one, I promise.

Happy #gamedev

~Ruben

The Gamedev Guru Logo

Performance Labs SL
Paseo de la Castellana 194, Ground Floor B
28046 Madrid, Spain

This website is not sponsored by or affiliated with Facebook, Unity Technologies, Gamedev.net or Gamasutra.

The content you find here is based on my own opinions. Use this information at your own risk.
Some icons provided by Icons8