Load addressable assets in scripts

Tutorial

·

intermediate

·

+10XP

·

30 mins

·

(396)

Unity Technologies

Load addressable assets in scripts

In this tutorial, you'll learn several ways to load addressable assets into your game and how to release them from memory, using scripts. We've included some resources and reviews of scripting concepts that you'll use with the Addressables system.

By the end of this tutorial, you'll be able to do the following:

  • Define an asynchronous operation
  • Identify different ways of referencing an addressable asset in scripts
  • Identify different methods of loading and releasing addressable assets asynchronously

Languages available:

1. Overview

In this tutorial, you'll learn how to load and unload addressable assets into your game using scripts. Throughout these exercises, you will remove the Resources API code in the Loady Dungeons project and replace it with Addressables API code.

2. Before you begin

If your only experience with C# is the Junior Programmer pathway, or if you need a review of some intermediate scripting concepts, here are some additional resources that you might find helpful.

Asynchronous operations

In the Addressables API, many tasks load assets and data and then return a result. When those assets or data are on a remote server, these tasks can take extra time. To avoid slowing down your game's performance and delaying other tasks, the Addressables system uses asynchronous operations, which means that tasks can run concurrently.

To implement asynchronous operations with the Addressables API, you'll need to understand these concepts, all of which are included in the Intermediate scripting project:

Structs

The Addressables API uses a struct, named AsyncOperationHandle, to help you keep track of asynchronous operations in your code.

A struct is a value type in C# that can be used to group related data together in a single object. They are similar to classes in that they can contain fields and methods, but unlike classes, they are passed by value instead of by reference. This means that when you create a struct and pass it to a method or assign it to a variable, a copy of the struct is created rather than a reference to the original object.

The AsyncOperationHandle struct has a Boolean value to indicate whether a method succeeded or failed, and the return value of the request operation.

Serialization

Serialization is the process of transforming and storing data between sessions of an application, and deserialization is the process of taking that stored data so that it can be reconstructed when an application runs again.

For these tutorials, you don't need to become an expert on serialization. You do need to know that Unity serializes fields during build time so that it can deserialize the data stored in them while the application is running. The [SerializeField] attribute works to mark private fields as serialized, and serialized fields become available in the Inspector for editing, just like public variables.

As you use the Addressables system and API, you'll use serialization to optimize the ways your project consumes assets.

Learn more about Script serialization in the Unity Manual.

3. Load an addressable prefab

There are several different ways to load prefabs using the Addressables API. Let’s start with loading an asset by using its address, which you'll do as you convert the PlayerConfigurator script to use the Addressables API.

To rewrite PlayerConfigurator.cs to use the Addressables API instead of the Resources API, follow these instructions:

1. In the Project window, in Assets > Scripts, open the PlayerConfigurator.cs in your preferred integrated development environment (IDE).

2. Near the top of the script, add the Addressables namespaces:

using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

You will use these namespaces to load an asset by its address and access the AsyncOperationHandle that is returned.

3. In the PlayerConfigurator class, create a new string variable representing the address. Precede it with the [SerializeField] attribute so that you can serialize it and change its value in the Inspector.

[SerializeField]
private string m_Address;

When this serializable field appears in the Inspector, Unity will remove the prefix and underscore, and label it Address.

4. Locate the following line in the existing script. This line uses the Resources API.

private ResourceRequest m_HatLoadingRequest;

Replace the line above with the following line, which uses the Addressables API.

private AsyncOperationHandle<GameObject> m_HatLoadOpHandle;

The line above defines the variable m_HatLoadOpHandle as the AsyncOperationHandle (or handle for short) of the GameObject.

5. In the SetHat() method, remove the body of the method and replace it with this code:

m_HatLoadOpHandle = Addressables.LoadAssetAsync<GameObject>(m_Address);

This line will load the prefab asset asynchronously, by its address, m_Address. The handle m_HatLoadOpHandle will have a Boolean indicating whether the asset was loaded successfully. If the load was successful, the handle also has the result of the load request, which is the prefab itself.

6. Since the asset will be loaded asynchronously, the next lines of code will run without waiting for the LoadAssetAsync() method to return a value. Therefore, to find out whether the method was successful, you can register an event to detect that the call is completed.

In the next line, you'll register an event handler (which you'll define afterwards) to notify you when the hat is loaded, using the AsyncOperationHandle’s Completed event.

m_HatLoadOpHandle.Completed += OnHatLoadComplete;

7. Locate the method OnHatLoaded and replace it with method OnHatLoadComplete to print the status of the handle. This method is called by Addressables when it has completed the request to load the prefab asset.

private void OnHatLoadComplete(AsyncOperationHandle<GameObject> asyncOperationHandle)
{
    Debug.Log($"AsyncOperationHandle Status: {asyncOperationHandle.Status}");
}

8. In the OnDisable() method, remove the body of the code and replace it with the following, to disable the event if this game object is disabled. This will prevent OnHatLoadComplete being called by the Addressables system if PlayerConfigurator should not be in operation: :

m_HatLoadOpHandle.Completed -= OnHatLoadComplete;

9. Save your script and return to the Unity Editor.

10. To test this script, navigate to Assets > Scenes and open the Level_00 scene.

11. In the Project window, navigate to Assets > Prefabs and select the Player prefab.

12. In the Inspector window, locate the PlayerConfigurator component and set the new Address variable to the address of a hat, such as Hat_BunnyEars.

13. Enter Play mode and watch the Console window, where you'll see “AsyncOperationHandle Status: Succeeded”. At this point, the hat prefab asset will load successfully, but you won't see it in the scene because you haven't instantiated the prefab yet. You’ll do that next.

14. Replace the code in the OnHatLoadComplete() method with the following code:

private void OnHatLoadComplete(AsyncOperationHandle<GameObject> asyncOperationHandle)
{
    if (asyncOperationHandle.Status == AsyncOperationStatus.Succeeded)
    {
        Instantiate(asyncOperationHandle.Result, m_HatAnchor);
    }
}

This code will check to see if the status of the load operation was a success. If so, there will be a prefab asset that was retrieved by the load operation in Result. It will be passed along to be instantiated.

15. Enter Play mode and note that the player is moving with the hat you selected.

16. Experiment by changing the Address to a different hat prefab and see the result.

Note: In this example, the input Address is a string that isn't validated as an address until you make the load request. You could enter anything in this property and cause an error. Make sure that the text matches an address in the Groups window.

4. Load an addressable prefab by an AssetReference

An AssetReference is a type that references an addressable asset. It is intended to be used as a serializable field in Unity classes like MonoBehaviour or ScriptableObject. When you add an AssetReference to one of these classes, you can assign an address to it in the Inspector with an object picker. The selection is limited to addressable assets.

On the surface, you'll add AssetReferences to your scripts just like you would add direct references, through public fields and private serializable fields. AssetReferences do not store a direct reference to the asset. The AssetReference stores the global unique identifier (GUID) of the asset, which is used by the Addressables system to store the object for retrieval at runtime.

The main benefit of using AssetReference over a string address is that it will restrict the selection of addresses from the Inspector, which avoids problems like typos in string addresses.

To rewrite PlayerConfigurator to use an AssetReference instead of an address, follow these instructions:

1. In the Project window, navigate to Assets > Scripts and open the PlayerConfigurator.cs script.

2. Replace the definition of the m_Address field with:

[SerializeField]
private AssetReference m_HatAssetReference;

3. In the SetHat() method, change the line that uses the Addressables.LoadAssetAsync() method to use the AssetReference instead:

if (!m_HatAssetReference.RuntimeKeyIsValid())
{
  return;
}

m_HatLoadOpHandle = m_HatAssetReference.LoadAssetAsync<GameObject>();

The call to RuntimeKeyIsValid checks to find out if the value chosen in the object picker is a valid address. In this context, it will forbid LoadAssetAsync from being called if the AssetReference is set to None.

4. Save the script and make sure that Level_00 is the active scene in the Editor.

5. In the Project window, navigate to Assets > Prefabs and select the Player prefab.

6. In the Inspector window, locate the PlayerConfigurator component. Note that the Address field is gone and replaced with Hat Asset Reference. Open the object picker for the new field and select an addressable hat prefab, such as Hat01.

7. Enter Play mode and note that the player is moving with the hat you selected.

8. Experiment by changing the AssetReference to a different hat address and observe the result.

Learn more about the AssetReference type in the Unity Manual.

5. Load an addressable prefab by an AssetReferenceGameObject

The object picker for AssetReference restricts the selection to assets that are addressable, but you may have noticed that it also listed the texture LoadyDungeonsLogo and the scene LoadingScene. These types can be selected with the object picker, but if PlayerConfigurator attempts to load them as GameObjects it will have a runtime error in the Addressables code. You may want to refine this further and make only prefabs selectable in the object picker. To do this, you can use AssetReferenceGameObject instead of AssetReference.

To rewrite PlayerConfigurator to use AssetReferenceGameObject instead of AssetReference, follow these instructions:

1. In the Project window, navigate to Assets > Scripts and select PlayerConfigurator.cs script to open it in your preferred IDE.

2. Replace the definition of the m_HatAssetReference field with:

[SerializeField]
private AssetReferenceGameObject m_HatAssetReference;

3. Save the script and make sure that Level_00 is the active scene in the Editor.

4. In the Project window, navigate to Assets > Prefabs and select the Player prefab.

5. In the Inspector window, locate the PlayerConfigurator component, open the object picker for the new Asset Reference property, and select an addressable Hat asset, such as Hat02. Note that the object picker no longer includes the logo sprite since it is not a GameObject.

6. Enter Play mode and note the selected hat. Try on other hats as desired.

You can learn more about this and other AssetReference subclasses in the Addressables documentation.

6. Load sprites

Sprites and sprite atlases are assets that can have subobject assets. There are several different ways to load sprites using the Addressables API. Let’s start with loading an addressable asset with subobjects by an address.

1. In the Project window, navigate to Assets > Scripts and select the GameManager.cs script to open it in your preferred IDE.

2. At the top, add the Addressables namespaces:

using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

3. In the GameManager class, create a new string and an AsyncOperationHandle field:

[SerializeField]
private string m_LogoAddress;

private AsyncOperationHandle<Sprite> m_LogoLoadOpHandle;

Note that you are loading by a sprite instead of GameObject in this case, so the type in the generic parameter list reflects that.

4. In the OnEnable() method, remove the line with the call to Resources.LoadAsync() method and everything below it, then add this line:

m_LogoLoadOpHandle = Addressables.LoadAssetAsync<Sprite>(m_LogoAddress);

5. In the same method, after capturing the handle returned by the LoadAssetAsync() call with the m_LogoLoadOpHandle field, register an event handler that will notify you when the logo is loaded. Use the AsyncOperationHandle’s Completed event, like you have before.

m_LogoLoadOpHandle.Completed += OnLogoLoadComplete;

6. Create the callback method OnLogoLoadComplete().

private void OnLogoLoadComplete(AsyncOperationHandle<Sprite> asyncOperationHandle)
{
    if (asyncOperationHandle.Status == AsyncOperationStatus.Succeeded)
    {
        m_gameLogoImage.sprite = asyncOperationHandle.Result;
    }
}

When the load is a success, the result will be passed along to the UI element.

7. In the Project window, navigate to Assets > Scenes and select the MainMenu as the active scene in the Editor.

8. In the Hierarchy window, select the GameManager GameObject.

9. In the Inspector window, locate the GameManager component and set the new Address field to “LoadyDungeonsLogo”, the address of the logo.

Note that subobjects usually have a subscript notation such as MainObject[SubObject], which indicates that Unity will retrieve a specific subobject, for example LoadyDungeonsLogo[LoadyDungeonsLogo]) is also valid.

10. Enter Play mode and you'll see that your script is loading the logo.

11. Exit Play mode and save the scene.

7. Load an addressable asset with subobjects by an AssetReference

When AssetReferences are assigned to an asset with subobject assets, an additional object picker appears that allows you to specify the subobject to reference.

1. In the Project window, navigate to Assets > Scripts and select the GameManager.cs script to open it in your preferred IDE.

2. Replace the definition of the m_LogoAddress field with:

[SerializeField]
private AssetReference m_LogoAssetReference;

3. In the OnEnable method, change the line that uses Addressables.LoadAssetAsync method to use the AssetReference instead:

if (!m_LogoAssetReference.RuntimeKeyIsValid())
{
  return;
}

m_LogoLoadOpHandle = Addressables.LoadAssetAsync<Sprite>(m_LogoAssetReference);

4. Save the script and make sure that MainMenu is the active scene in the Editor.

5. In the Hierarchy window, select the GameManager GameObject. In the Inspector window, locate the GameManager component. Open the object picker for the new Asset Reference property and select the texture of LoadyDungeonsLogo and subobject (Subasset) field.

6. Enter Play mode and note that the logo is loaded.

7. Exit Play mode and save the scene.

8. Load an addressable sprite by an AssetReferenceSprite

To restrict the object picker to only make sprites selectable, you can use AssetReferenceSprite instead of AssetReference.

You have enough experience now to make this change yourself. Give it a try and test your code. When you use the object picker in the GameManager component, your only choice will be the logo sprite, because it is the only sprite with an address.

9. Load an addressable scene by its address

If a scene has been enabled as addressable, you can use addressable assets in the scene just as you would any other assets.

To update the scripts to load addressable scenes, follow these instructions:

1. In the Project window, navigate to Assets > Scripts and select the GameManager.cs script.

2. At the top of your script, add the ResourceProviders namespace:

using UnityEngine.ResourceManagement.ResourceProviders;

3. In GameManager class, create a new AsyncOperationHandle field:

private static AsyncOperationHandle<SceneInstance> m_SceneLoadOpHandle;

4. In the LoadNextLevel method, replace SceneManager.LoadSceneAsync with Addressables.LoadSceneAsync. Save the script after these changes.

public static void LoadNextLevel()
{
    m_SceneLoadOpHandle = Addressables.LoadSceneAsync("LoadingScene", activateOnLoad: true);
}

If you check the Addressables Groups window, the LoadingScene string you’ve used for the LoadSceneAsync() method should match the address you have for the LoadingScene addressable asset.

5. Save the script and in the Project window, navigate to Assets > Scripts and select the Loading.cs script.

6. At the top of your script, locate this line and remove it:

using UnityEngine.SceneManagement;

7. Add these Addressables namespaces:

using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;

8. In Loading class, replace the definition of the m_SceneOperation field with:

private static AsyncOperationHandle<SceneInstance> m_SceneLoadOpHandle;

9. In the loadNextLevel method, locate these lines:

m_SceneOperation = SceneManager.LoadSceneAsync(level);
m_SceneOperation.allowSceneActivation = false;

Replace the lines with SceneManager.LoadSceneAsync and m_SceneOperation.allowSceneActivation with:

m_SceneLoadOpHandle = Addressables.LoadSceneAsync(level, activateOnLoad: true);

10. In the loadNextLevel method, locate this line:

while (!m_SceneOperation.isDone)

Replace the line with m_SceneOperation.isDone with:

while (!m_SceneLoadOpHandle.IsDone)

11. In the loadNextLevel method, replace the all references to m_SceneOperation.progress with:

m_SceneLoadOpHandle.PercentComplete

12. Remove the GoToNextLevel method and save the script.

13. Open the MainMenu scene and enter Play mode.

14. Select the Start button to load the LoadingScene.

15. Note that the LoadingScene is visible and that Level_00 loads in.

10. Reference counting

So far, our usage of the Addressables system has not required us to control our asset memory beyond loading, but what if you wanted to load and unload assets in and out of the same level? Or if multiple items are using the same asset, like everything drawn using the lit shader?

One benefit of the Addressables system is that it helps you manage the loading and unloading of the assets in and out of memory. This is done internally through a reference counting system, which manages the way the resources are shared, but also decides when the resources are actually loaded and unloaded from memory.

In practice, when you write scripts that request assets from the Addressables system, you simply make the load request, keep the AsyncOperationHandle that was returned, and pass the handle back to the Addressables system when you no longer need the result of the operation. The Addressables system handles the actual loading and unloading.

There are benefits to this approach. If there are two different requests for the same asset from the Addressables API, the reference counting system makes sure that there isn’t double loading of assets. Likewise, you can avoid attempts to deallocate an asset twice, or avoid deallocating the asset when another item is using it.

For example, let's randomly load a hat at the start of the level and give the player the option to select a new random hat.

To limit the amount of loaded assets, follow these instructions:

1. In the Groups window, change the address of Hat_BunnyEars to “Hat00”. This will make the naming consistent across all hats so that we can programmatically select them.

2. In the Project window, in Assets > Scripts, open the PlayerConfigurator.cs script.

3. Locate this line:

[SerializeField]
private AssetReferenceGameObject m_HatAssetReference;

Replace it with this:

private GameObject m_HatInstance;

4. In the Start method, remove the body and add this line:

LoadInRandomHat();

5. Remove the SetHat method and add the following method to the existing script:

private void LoadInRandomHat()
{
    int randomIndex = Random.Range(0, 6);
    string hatAddress = string.Format("Hat{0:00}", randomIndex);

    m_HatLoadOpHandle = Addressables.LoadAssetAsync<GameObject>(hatAddress);
    m_HatLoadOpHandle.Completed += OnHatLoadComplete;
}

This method will pick a random number from 0 to 5, then format a string with the hat’s address so that it is between Hat00 and Hat05, and then attempt to instantiate that hat using the randomized address.

6. In the OnHatLoadComplete method locate this line:

Instantiate(asyncOperationHandle.Result, m_HatAnchor);

Replace it with this:

m_HatInstance = Instantiate(asyncOperationHandle.Result, m_HatAnchor);

7. Add the following method to the existing script:

private void Update()
{
    if (Input.GetMouseButtonUp(1))
    {
        Destroy(m_HatInstance);
        Addressables.ReleaseInstance(m_HatLoadOpHandle);

        LoadInRandomHat();
    }
}

Instead of allowing all of the hat instances to accumulate in memory, the Destroy() method removes the hat from memory before we load the next. We also no longer the prefab in memory, so ReleaseInstance() will notify Addressables that the hat prefab is no longer needed here before attempting to load a new random hat asset. Since this was the only reference to the hat prefab across the game scripts, Addressables chooses to unload it.

8. Open the MainMenu scene and enter Play mode.

9. Select the Start button to load the LoadingScene. Then select the Play button in the game.

When you right click anywhere in the Game view, the hat will change to a different randomly-loaded hat. Behind the scenes, you're managing memory effectively by releasing each instance of the hats that are replaced.

11. Next steps

Now that you have converted some Loady Dungeons assets and scripts to use the Addressables system, next you will learn to build your application with your addressable assets

Complete this tutorial