Load, unload and change assets at runtime with Addressables

9 minutes
4.8
(33)

Good memory handling is an important aspect for any game – leaving too much garbage around can drag down the performance and turn your players away. Addressables are “the right” way to properly manage dynamically loading assets within your game. It also helps tremendously with memory allocation. In this tutorial, you will get an overview of what the Addressable system is, the benefits in using it, and how to dynamically load/unload an addressable.

This is a two-part series. Part 2, “Stream content from a remote catalog with Addressables“, covers how to load content through a remote catalog, such as Unity’s Cloud Content Delivery service (free tier).

Learning outcomes

Here is what you will learn:

  1. The benefits in using Addressables
  2. Basic terminology and structure
  3. How to mark an asset as an ‘addressable’, instantiate it and load a texture into memory
  4. How to build your catalog.

Prerequisites

Note: This tutorial was created with 2020.3 LTS but should still work with 2019 LTS.

  1. You will need Unity 2020.3 LTS or later to follow along.
  2. This tutorial assumes you already have basic knowledge of Unity and intermediate knowledge of C#.

Resources

  1. Addressable Documentation
  2. Scriptable Build Pipeline Documentation
  3. Asset Bundle Documentation

Getting started

The Addressable Asset system is an easy way to dynamically load and unload assets in your game by an address. An address is a property you set that represents the location where an asset resides. It is now the recommended way to load assets, rather than using Resources.Load.

Download the project files

This tutorial builds on top of a demo scene that is included in the starter project. You can download the starter project by:

  1. Clone and/or download the GitHub repository.
  2. Navigate to the dynamically-load-with-addressables\projects\starterProject folder in Unity.

You will see several folders folders in Assets/WUG. The starter project comes with a basic demo scene, which you will do the work in. Open the scene by navigating to Assets/WUG/Scenes/Demo.

Install the Addressables package

Open Package Manager by going to Window > Package Manager.

  1. Make sure the Packages view is set to Packages: Unity Registry.
  2. Search for Addressables and install version 1.16.19.

Understanding the basics

As I’ve mentioned, Addressables are a way to easily handle content creation and dynamically loading/unloading assets at runtime. There are a lot of benefits to using it, such as:

  1. Faster iteration workflows: Having an address that doesn’t change means that you do not have to hardcode paths to your assets in code. You are also free to structure your project however you want. This creates less chance for errors due to moving or renaming your assets. Additionally, the system will automatically take care of packaging your Asset Bundles.
  2. Streamlined content packaging & dependency management: The system will build a catalog and asset bundles based on how you’ve organized your addressed assets into groups. The catalog knows about the interdependencies between all of your asset bundles. This ensures that everything required by the asset is properly loaded before serving you back your request.
  3. Better memory management: The system will intelligently handle coordinating which resources are loaded/unloaded. It also comes with a robust profiler to help identify potential issues with memory allocation in your game.

It’s a common misconception that Destroy(object) will instantly unload the asset completely from memory. In truth, it often happens later and can sometimes leave scraps around that the garbage collector (GC) does not pick up. Memory management in Unity is a topic all in itself that I will not go into here. Check out this tutorial if you are interested in learning more.

Concept overview

Part of what makes the Addressable system tricky to learn at first, is that it overlaps with other areas such as the Scriptable Build Pipeline, memory management, and Asset Bundles. It also introduces it’s own set of concepts for you to know. This tutorial will focus on two must have core concepts – asset bundles and addressable terminology/structure. Here’s an overview of key terms that you’ll hear frequently:

  1. Asset Bundle: A container that holds non-code assets that can be loaded at runtime. Among other things, they are commonly used for downloadable content (DLC) and loading optimized assets for different target platforms.
  2. Addressable Asset: An asset that is marked as “Addressable” and will now be picked up by the Addressable System.
  3. Content Catalog: The data store that is used to look up an asset’s physical location via the address.
  4. Address: Property of the asset that represents how it can be looked up in the content catalog (the key).
  5. Group: How you organize the addressable assets into asset bundles.
  6. Label: An optional property that provides an extra layer of control over querying for addressable assets.

Ok, great. Crystal clear… right? 😀 Let’s take a look at how all of these maps together in a hierarchy of sorts:

In the diagram above, there’s one Content Catalog that has two Asset BundlesProjectiles and Level 2. The Projectiles asset bundle contains three addressable assets Shotgun Sound, Scatter Decal, and Energy Decal. Each of the assets are assigned labels. This makes it easy to query for projectile assets of a certain type (Shotgun versus Energy).

Configure the Addressable system

In this example, you’ll instantiate several logs at runtime, which will float down the river and then unload when they reach the end. Before you can write any code, you’ll need to first mark assets that should be handled by the Addressable system as Addressable. To do this:

  1. Open Inspector view of the Log prefab, located under Assets/WUG/Prefabs.
  2. Check the Addressable box to enable it.
  3. Rename the Address from Assets/WUG/Prefabs/Log.prefab to Log.

Repeat this process for a texture that you’ll load later:

  1. Open Inspector view of the LogTexture, located under Assets/WUG/Models and rename the Address from Assets/WUG/Models/LogTexture.png to LogTexture.

That’s it. When working with addressables, you’ll want to do this for every asset that the system should know about.

Addressable groups & labels

Groups are how you specify which asset bundle your addressable assets will be built into. How you structure your groups/asset bundles is really up to you and is project dependent. For larger projects with a complicated setup, you’ll likely want pick an initial structure that makes sense and than use the Addressable profiling tools to help balance out memory usage.

Assets loaded from a bundle can only be unloaded when the source bundle is fully unloaded. A large asset bundle with unrelated stuff could add memory overhead as you could use some of it, but not all. On the other hand, having many smaller bundles reduces the efficiency of delivery.

A few things to know about groups:

  1. You can open the group panel by going to Window > Asset Management > Addressables > Groups.
  2. By default, all addressed objects will be put into Default Local Group.
  3. You can add new groups through the Create menu and drag / drop addressable assets between existing groups to move them.
  4. You can change the default group at any time by right clicking on a group and picking Set as Default.
  5. This panel is where you can set the labels for the assets.

The default settings will work just fine, so close the window.

Addressable settings

Speaking of settings, the next two areas that may be helpful to understand are the System Settings and Profiles:

  1. System Settings define the top level settings that the addressable system will use including things like compression, whether to build a remote catalog, how to name the bundles, etc.
  2. Profiles are a way to have multiple configurations that define how the system should handle building and loading the addressable catalog. This lets you adapt the system to work with various scenarios such as preparing a local build for staging versus doing a automatic build for release or delivering different assets to different platforms (Android versus PC).

Both of these can be found under the Window > Asset Management > Addressables menu. For this project, the default settings are fine.

Part 2 explores how you can setup the system to work with a remote service. See Addressable Asset Settings and Addressable Asset Profiles in the documentation you are interested in reading more now.

Spawn the logs

Now that all of that is out of the way – let’s get on to the good stuff. It’s time to load a reference to the log asset and instantiate some number of them. Create a new script called LogSpawner and add the following code:

[SerializeField] private bool m_Spawning;
[SerializeField] private Transform m_SpawnPosition;
[SerializeField] private AssetReference m_LogPrefab;

private IEnumerator Start()
{
    float waitTime;

    while (m_Spawning)
    {
        waitTime = UnityEngine.Random.Range(2f, 5f);

        Addressables.InstantiateAsync(m_LogPrefab, 
            m_SpawnPosition.position, Quaternion.identity, 
            transform, true);
        yield return new WaitForSeconds(waitTime);
    }
}

When the game starts, LogSpawner will continuously spawn logs at some random interval between 2 and 5 seconds each. The interesting part of this code is how the instantiation is done:

  1. m_LogPrefab is of type AssetReference, which is a way to store the address (key) of an asset.
  2. Addressables.InstantiateAsync is basically the equivalent of GameObject.Instantiate, except first the Addressable system is checking to see if a reference to the asset is already loaded in memory. If it is not, it’ll automatically search the catalog to find and load a reference to it.

Hop back over to the Demo scene and find the Spawner object in your hierarchy. Do the following:

  1. Add LogSpawner as a new component.
  2. Check the Spawning box.
  3. Add the child SpawnPosition game object as a reference to Spawn Position.
  4. Add the Log prefab located under Assets/WUG/Prefabs/Log as the reference in Log Prefab.

Push play and you should see the logs start spawning and moving. Watch for a few moments to see a log reach the end and not be destroyed. You will fix that next.

The logs are animated with the LogAnimator script, located on the prefab.

Destroy the logs

Destroying an instantiated game object with Addressables is very easy – instead of using Destroy(), you call Addressables.ReleaseInstance(gameObject). For this project, you will need to update the Update() method on the LogAnimator script. LogAnimator is located in Assets/WUG/Scripts. Open it and add the following if statement to the end:

private void Update()
{
    m_Time += Time.deltaTime / m_RemainingTime;
    transform.position = Vector3.Lerp(m_startPosition, m_EndPosition, m_Time);

    m_Model.localPosition = Vector3.up * (Mathf.PingPong(Time.time, m_BobAmount) - .5f);

    if (Vector3.Distance(transform.position, m_EndPosition) < .1f)
    {
        Addressables.ReleaseInstance(gameObject);
    }
}

LogAnimator moves the log from the start of the river to the end of the river. The new code uses Vector3.Distance to monitor for when the log has reached the end. Once it has, it notifies the addressables system to release it’s instance. To use “old” terminology – you’re destroying it.

Push play and you should now seem the disappear.

Change the texture of the logs

The previous method is great for instantiating game objects at runtime. But you’ll probably need to instantiate other types of assets such as a texture, material or a model. The process is a bit different. To learn how to do this, you will swap out the texture of every other the log with a lighter wood color. Open LogSpawner, and add two new global variables:

private Texture m_LogTexture;
private int m_LogCount = 0;

Add a new Spawn_Completed method and change Start() to have the following code:

private IEnumerator Start()
{
    float waitTime;

    Addressables.LoadAssetAsync<Texture>("LogTexture").Completed += handle =>
    {
        m_LogTexture = handle.Result;
    };

    while (m_Spawning)
    {
        waitTime = UnityEngine.Random.Range(2f, 5f);

        Addressables.InstantiateAsync(m_LogPrefab, m_SpawnPosition.position,
            Quaternion.identity, transform, true).Completed += Spawn_Completed;

        m_LogCount++;
        yield return new WaitForSeconds(waitTime);
    }
}

private void Spawn_Completed(AsyncOperationHandle<GameObject> handle)
{
    if (m_LogTexture != null & m_LogCount % 2 == 0)
    {
        handle.Result.GetComponentInChildren<MeshRenderer>()
           .material.mainTexture = m_LogTexture;
    }
}

Loading any addressable asset in memory can be done by calling Addressables.LoadAsync<T>. Let’s review what is happening in more depth:

  1. Lines 5-8: Makes a request to load the new texture into memory. LogTexture is the address that you set earlier. Since this is an async method, you need to wait for it to complete before doing anything. You can be notified of it’s completion by hooking a listener to the Completed event. Once completed, the handle.Result is stored into a global variable for access later.
  2. Line 14-15: The instantiate call is also async, which means you once again need a listener for the Completed Event in order to access the resulting game object. Once triggered, it calls the Spawn_Completed method.
  3. Line 17: Tracks the amount of logs that have been spawned.
  4. Line 26: If the log isn’t null then it’ll change the texture on the instantiated game object to m_LogTexture. You ensure that this only happens for every other log by testing m_LogCount % 2 == 0.

That’s it. Run your game in play mode and you should now see the logs spawning and the texture changing for every other one.

Build the Catalog

A Catalog is the data store that is used to look up an asset’s physical location via the address. You will need to build it before you produce a local or remote build. This is done through the Addressables Groups window:

  1. Click on the Build drop down and select New Build > Default Build Script.

I build the catalog every time I adjust the assets marked as an addressable or shift around the groups just so I don’t forget later. Another option is to create a build script that you run, which first produces new Addressables build and then produces your actual game build. You can read more about the second option here.

You can also adjust how you read the catalog in play mode. There are three options:

  1. Use Asset Database: Uses the asset database and does not do any analysis or asset bundle creation. This is the default option.
  2. Simulate Groups: Analyzes the content for layout and dependencies without creating the asset bundles. Assets are still loaded from the Asset database.
  3. Use Existing Build: Uses a built catalog instead of the asset database. Use this option to more closely simulate an actual build of your game.

Where to go from here

This tutorial covered the basics of the Addressables system and should be enough if you intend to package all your content with your game. However, if you are interested in using any remote service, including Unity’s Cloud Content Delivery, then check out part 2, “Stream content from a remote catalog with Addressables“.

DON’T MISS A TUTORIAL

Be the first to know when new tutorials are available.

We don’t spam! New tutorials are currently released 1-2 times a month. Read our privacy policy for more info.

How useful was this tutorial?

Share this tutorial...

Thank you for rating - Do you have any feedback you'd like to share?