---
title: "Load addressable assets in scripts"
content_type: tutorial
url: "https://learn.unity.com/course/get-started-with-addressables/tutorial/load-addressable-assets-in-scripts?language=en"
language: en
container_type: course
container_title: "Get started with Addressables"
container_url: "https://learn.unity.com/course/get-started-with-addressables.md?language=en&version=2022.2"
standalone_markdown_url: "https://learn.unity.com/tutorial/load-addressable-assets-in-scripts.md?language=en&version=2022.2"
previous_title: "Make assets addressable"
previous_url: "https://learn.unity.com/course/get-started-with-addressables/tutorial/make-assets-addressable?language=en&version=2022.2"
previous_markdown_url: "https://learn.unity.com/course/get-started-with-addressables/tutorial/make-assets-addressable.md?language=en&version=2022.2"
next_title: "Build content using Addressables"
next_url: "https://learn.unity.com/course/get-started-with-addressables/tutorial/build-content-using-addressables?language=en&version=2022.2"
next_markdown_url: "https://learn.unity.com/course/get-started-with-addressables/tutorial/build-content-using-addressables.md?language=en&version=2022.2"
difficulty: intermediate
duration_minutes: 30
unity_version:
  - "2022.1"
  - "2022.2"
supported_unity_versions:
  - name: "2022.1"
    url: "https://learn.unity.com/course/get-started-with-addressables/tutorial/load-addressable-assets-in-scripts.md?language=en&version=2022.1"
  - name: "2022.2"
    url: "https://learn.unity.com/course/get-started-with-addressables/tutorial/load-addressable-assets-in-scripts.md?language=en&version=2022.2"
last_updated: "2026-08-04T14:21:37.000Z"
---

# Load addressable assets in scripts

Learn scripting patterns to reference, load, and release addressable assets asynchronously using the Addressables system.

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

## Materials

- [GetStarted-LoadyDungeons.zip](https://unity-connect-prd.storage.googleapis.com/20230511/25187cc3-f9e4-4628-a1f2-49b28e7ddbc3/GetStarted-LoadyDungeons.zip)

## 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](https://learn.unity.com/pathway/junior-programmer), 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**](https://learn.unity.com/project/intermediate-gameplay-scripting) project:

- **Coroutines**, which allow you to perform a function over time instead of instantly. Learn more with [**Intermediate scripting: Coroutines**](https://learn.unity.com/tutorial/coroutines)
- **Delegates**, which are containers for functions that can be passed around or used like variables. Learn more with [**Intermediate scripting: Delegates**](https://learn.unity.com/tutorial/delegates)
- **Events**, which are specialized delegates that can alert a class that something has happened. Learn more with [**Intermediate scripting: Events**](https://learn.unity.com/tutorial/events-uh)

## 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](https://docs.unity3d.com/Packages/com.unity.addressables@0.8/api/UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle.html) 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](https://docs.unity3d.com/ScriptReference/SerializeField.html) 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](https://docs.unity3d.com/Manual/script-Serialization.html).

## 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:

```csharp
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.

```csharp
[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.

```csharp
private ResourceRequest m_HatLoadingRequest;
```

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

```csharp
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:

```csharp
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.

```csharp
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.

```csharp
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: :

```csharp
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:

```csharp
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:

```csharp
[SerializeField]
private AssetReference m_HatAssetReference;
```

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

```csharp
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](https://docs.unity3d.com/Packages/com.unity.addressables@1.19/manual/AssetReferences.html) 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:

```csharp
[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.

![](https://connect-mediagw.unity.com/h1/20230511/learn/images/efcb5ec4-693d-4b00-a68c-5bd4e5075a19_HatAssetReferenceObjectPicker.png)

**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.](https://docs.unity3d.com/Packages/com.unity.addressables@1.20/manual/AssetReferences.html)

## 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:

```csharp
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
```

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

```csharp
[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:

```csharp
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.

```csharp
m_LogoLoadOpHandle.Completed += OnLogoLoadComplete;
```

**6.**  Create the callback method **OnLogoLoadComplete()**.

```csharp
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:

```csharp
[SerializeField]
private AssetReference m_LogoAssetReference;
```

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

```csharp
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.

![](https://connect-mediagw.unity.com/h1/20230511/learn/images/e0fdbda7-333e-449c-a424-6eb3425e48c6_GameManagerLoadyDungeonsLogo.png)

**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:

```csharp
using UnityEngine.ResourceManagement.ResourceProviders;
```

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

```csharp
private static AsyncOperationHandle<SceneInstance> m_SceneLoadOpHandle;
```

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

```csharp
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:

```csharp
using UnityEngine.SceneManagement;
```

**7.**  Add these Addressables namespaces:

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

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

```csharp
private static AsyncOperationHandle<SceneInstance> m_SceneLoadOpHandle;
```

**9.**  In the **loadNextLevel** method, locate these lines:

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

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

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

**10.**  In the **loadNextLevel** method, locate this line:

```csharp
while (!m_SceneOperation.isDone)
```

Replace the line with **m_SceneOperation.isDone** with:

```csharp
while (!m_SceneLoadOpHandle.IsDone)
```

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

```csharp
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:

```csharp
[SerializeField]
private AssetReferenceGameObject m_HatAssetReference;
```

Replace it with this:

```csharp
private GameObject m_HatInstance;
```

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

```csharp
LoadInRandomHat();
```

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

```csharp
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:

```csharp
Instantiate(asyncOperationHandle.Result, m_HatAnchor);
```

Replace it with this:

```csharp
m_HatInstance = Instantiate(asyncOperationHandle.Result, m_HatAnchor);
```

**7.**  Add the following method to the existing script:

```csharp
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
