Attilio Carotenuto
Posted on November 15, 2023
Addressables are a powerful way to structure your game in logical blocks that can then be exported separately and added to the main executable whenever needed. They are used to load and unload assets, and to configure, build and load asset bundles that you can then distribute to your players on demand.
This has many advantages, such as keeping your initial download size small, allowing developers to add and deliver content on-demand to users, and to update content on the fly without requiring a full update. It also allows you to selectively deliver localised content to a specific region, or enable chunks of the game only during a specific time, for example during a live event.
Another advantage is being able to reference and dynamically load your assets by name, control when they are loaded and unloaded from memory, and use soft references so you can have asset references without directly loading them in memory.
If you have used the Resources folder in the past, or are still taking advantage of it, you should strongly consider replacing it with Addressables. While there are similarities, the Resources folder has many disadvantages, such as creating a large blob that is always loaded into memory, severely increasing your game start loading time since the whole binary has to be loaded at once, having generally bad performance at runtime, and being limited to 4GB in total. It will also prevent your build system from figuring out what assets are unused and strip them out, instead all assets in the folder will be included in your executable, increasing build size.
Let’s take a look at how to setup and use them in your project, and how to best organise your assets to take full advantage of this system.
For this tutorial I am using:
- Unity 2022.3.10 LTS
- Addressables package v.1.21.18
- CCD Management package v.2.2.2 (for the UGS upload)
As the packages evolve, some details and APIs might change, but the general concepts should stay the same. Similarly, as the UGS team keeps developing their online services, some interfaces might change, new features might appear and so on, so keep that in mind.
Setting up the project
The first step is to install the Addressables package from the Package Manager. As usual, open the Package Manager window, switch to the Unity Registry, and look for Addressables (com.unity.addressables), then Install.
The Addressables package also contains some samples and some utilities that you can import in your project, if you’d like to experiment with them.
After installing, you will need to create a Settings file. To do that, go to the Addressables window under Window - Asset Manager - Addressables - Groups, then “Create Addressables Settings”
A new folder will be created in your Assets directory called AddressableAssetsData. You don’t need to manage these files directly, as you’ll instead do your bundles setup through the Editor windows.
Managing Groups
In order to split your game in chunks, you need to divide your assets in groups.
To do so, you need to analyse your game and the overall user experience, and determine which assets are more frequently used together, and where it makes sense to pack them in a group. Every game will be different in this aspect, the goal is to minimise the amount of bundles downloaded and assets kept in memory at once. If you ever packed sprites in a spritesheet, it's a similar concept.
For example, imagine you are developing a mobile hack-and-slash with multiple worlds, playable characters, and special live events. One possible approach would be to include the initial hero, the tutorial world and the related enemies in the actual game build. Whenever players will actually finish the tutorial, then the game will download the required bundles to proceed to the new world. Of course some assets will be re-used in different worlds, so in that case you may want to group shared assets in another bundle.
As you’re running a live game, some assets will be required for live events that only run for a certain period of time. A dedicated group for this type of events will be ideal, as it allows you to only distribute these assets when needed, change them on your end without users having to update their game, and make them inaccessible when the event is over.
Imagine your game has certain asset variations that you want to deliver to specific regions. In order to do so you can create separate bundles for this, and then deliver the corresponding one either programmatically, or at the CDN level using Region-based routing.
There is no limit to the number of bundles you can define, however keep in mind that your base game build still requires at least one scene to bootstrap and get things started.
Let’s put that in practice in the Addressables window. Right click anywhere and select Create New Group - Packed Assets to define the desired groups for your game, as shown below for example.
Then, navigate to the assets or scenes that you intend to make Addressables, tick the “Addressable” checkbox right at the top of the inspector, and assign the desired group.
As you noticed, by default the Asset name (the one next to the checkbox) will be the same as the Asset path. That is used to reference the asset in scripts. You can freely change the name, without affecting the asset path, and this allows you to move the asset to a different folder without breaking any reference to that asset.
You also have the option to set one or more labels for the asset, either from the Inspector via the small blue icon on the bottom right of the screenshot above, or directly from the Addressables window. Labels provide a few alternative ways to group and pack assets, as we'll learn later in this tutorial.
The great thing about this system is that when you load an asset, Unity will figure out the dependencies for you and load them too.
One caveat is that, even if an Asset is not marked as Addressable, it can be included in a bundle due to being a dependency to another included asset. This can lead to unintended side effects such as object duplications, for example if you need to reference a ScriptableObject from two different bundles. To avoid this, you should make sure that dependencies are also marked as Addressables, then the system can figure out how to pack and load them reliably for you.
Scenes can also be marked as Addressables, just like other types of assets. Note that if you place in your Addressable scene an asset that is not marked as Addressable, that asset will still be added to the resulting asset bundle of that scene as an implicit dependency. On the other hand, if you place Addressables assets to a Scene that is not marked as Addressables, those assets will be included both in the built-in scene data and in the asset bundle. In either scenarios this can again lead to duplicates assets in memory, so it is recommended to fully rely on the Addressables system to manage your assets and avoid mixing different asset strategies.
It is also possible to mark an entire folder as Addressable. In this case, the entire content of that folder will be added to that group.
If you mark an asset within the Resource folder as Addressable, it will be moved out of that folder as the two systems are not compatible, and no Resource file can be addressable. Mixing Resources and Addressables in the same project is generally not a good idea, and as mentioned earlier, you should aim to move away from the Resources system altogether.
Building
Now that you’ve grouped your assets, the next step is to build the asset bundles.
Before building, let’s look at Profiles by going to Window - Asset Manager - Addressables - Profiles. You will find a Default Profile available for you, where Local is set to Built-in and Remote to Editor Hosted.
Local is intended for assets that you want to distribute with the build, to be accessible also when the user has no internet or hasn’t downloaded any additional package, such as the main menu, intro, tutorial and possibly the first few levels. The default value resolves to the StreamingAssets folder
Remote is for subsequent levels, optional content, skins etc that can instead be downloaded later on, reducing the build size and allowing you to make changes to it and deliver new content to the users without pushing a full build update.
In most cases you’ll only need a single profile, unless you have a more complex setup such as following a multi-cloud strategy. If needed, you can create a new profile from this window.
We can leave the default settings for now, we will cover Cloud Content Delivery and Custom uploads in the Upload section.
Let’s build the bundles now. To do this, from the Addressables Groups window, select Build - New build - Default Build Script. If it’s your first time building, you will be asked if you would like to enable Debug Build Reports, which is a pretty handy feature, so I’d recommend doing so.
On completion, if enabled, an Addressable Report will be shown to you within the editor, with general info like Platform, unity version and size. From there you can also inspect the bundles and the assets they contain, go through each asset dependencies and references, and check a list of potential issues automatically detected by Unity.
For groups set to Remote, by default the resulting bundle will be exported to a folder above Assets, called ServerData/YourPlatform. Later on we will see how to upload directly to UGS after building.
Whenever you make changes, you can select Update a Previous build, which will update your asset bundle without needing to rebuild everything. This is generally faster.
By default, an asset bundle will be created for each group (Pack Together). You also have the choice to split every asset within a group into its own asset bundle, or to pack assets together based on labels. To do so, select a group, go to the Inspector, open the Advanced Options pane, and tweak the Bundle Mode entry depending on your preference.
One caveat is that Scenes cannot be mixed in the same Asset Bundle with any other type of assets. For this reason, Scene assets are packed in a separate asset bundle, even if grouped together with other non-Scene assets in the same group. These bundles are called Streamed Scene Asset Bundles
Finally, you can also delete build cached within the "Build/Clear Build Cache" dropdown. Selecting "Build Pipeline Cache" will clear the SBP (Scriptable Build Pipeline) cache, effectively deleting the BuildCache folder from your Library.
Selecting "All" will delete the SBP cache, and all cached data created by content builders. Each content builder will have a different set of cached data, for the default BuildScriptPackedMode this will include the catalog, settings files, built Asset Bundles and link.xml files, while for example for the BuildScriptVirtualMode builder it will just be the catalog and settings files.
If you want to delete cached data for a specific content builder, you can do so from the "Content Builders" sub-menu. You can also see what content builders you have, and define new ones, from the Addressables System Settings menu.
Uploading
Now that your bundles are packaged and ready, we need to upload them somewhere in order to serve them to the users.
There are many different ways and solutions to store your bundles. You could store them in a S3 bucket and serve them via AWS CloudFront, you might have your own hosting solution, or you can take advantage of the built-in Unity hosting to easily upload and serve them.
For the purpose of this tutorial, we’ll leverage the Unity Game Services hosting solution, Cloud Content Delivery.
Setting up Cloud Content Delivery
The first thing to do is setting up your environment in the UGS dashboard.
Note that this is a paid service, so you will need to provide your payment credentials to access this, and you may be charged based on consumption and traffic, also when following this tutorial. At the time of writing, the first 50GB of bandwidth are free (as shown below), but make sure to get the most up-to-date pricing in the Usage Overview tab.
The general idea is to upload your bundles into buckets, which are then assigned to different environments, such as dev, staging or production. If you are familiar with AWS S3, you will find some of the concepts familiar.
By default you should find a production environment already created for you. Let’s add a separate staging one. To do so, open the dropdown and select Manage Environments.
Click on Add Environment, and set your name. I will go with “staging”.
From this page you can also delete environments, by clicking on the three dots menu next to it and selecting Delete.
Let’s go back to the Buckets tab, and select Create Bucket.
First, choose a name and a description for the bucket.
Then, under Write Conditions you can define who has the rights to write to it. Selecting Open to all means that anyone within your organisation can upload directly to it. Promotion Only means that Owners and Managers can promote assets to it, but nobody can upload or delete content directly. We will cover releases and promotions later in this tutorial.
Finally, Bucket Privacy lets you define your privacy policy, so whether you need a token to access the content.
In the next step, you’ll need to add the bucket to one or more environments. I’ll just go with production for this one.
Now that we have a bucket ready, let’s go back to the Editor and see how we can upload to it.
Uploading from the Editor
There are various ways to upload bundles to your bucket, such as via the CLI, or directly from the dashboard, but in many cases it will be easier to let the engine integration do the work for you.
From the Addressables Groups window, select your profile, and change the Remote settings to Cloud Content Delivery - Automatic, then select the production environment.
Automatic means that the game will upload and pull the bundles from the environment you selected, based on the Release and Badges you setup in the dashboard. The other option is “Specify the Environment, Bucket and Badge”, which as the name describes allows you to directly target from a specific bucket and badge. In most cases you will want to select Automatic, as it will allow you to do your setup work in the dashboard without requiring a game update.
If it is the first time using Cloud Content Delivery in your project, the Editor will likely prompt you to install the CCD Management package. Make sure to do so from the Package Manager before proceeding. Also, go to the Addressables Settings window, and ensure that “Enable CDD Features” at the bottom is enabled.
If you haven’t done so previously, you will then be asked to Link your Unity project to a Project ID. As usual, select an organisation in the Project settings and a project. Make sure it’s the same org where you previously created your bucket.
When all of this is done, you will notice a new option in the Addressables window called Build to CCD. Run this as you did previously for a local build, and the bundles will be created and uploaded to the bucket you selected in the profile.
Finally, return to the dashboard and inspect your bucket. You will find new entries such as your bundles and the catalog file.
Scripting
Now that we have our Addressables setup and available, we can see how to load and unload them during gameplay.
Loading via Asset References
AssetReference allow you to hold a reference to an asset without directly loading it into memory, and allowing you to load it at a later point in time. If you're familiar with Unreal Engine, they are essential Soft References.
When you load an Addressable asset, Unity will figure out the dependencies for you and download the required AssetBundles if needed. It will then load those bundles into memory.
There are different ways to work with asset references. The first option is to manually load the asset in memory, then create instances from it.
As you can see in the snippet below, we expose an Asset Reference in the inspector, which you can use to assign an asset to. If you assign to this field an asset that is not marked as Addressables, Unity will mark it for you and move it to the Default Group. We then load and unload it through the respective methods.
[SerializeField]
private AssetReference SeasonBannerRef;
private GameObject bannerInstance;
public void LoadSeasonBanner()
{
AsyncOperationHandle handle = SeasonBannerRef.LoadAssetAsync<GameObject>();
handle.Completed += OnBannerLoadCompleted;
}
private void OnBannerLoadCompleted(AsyncOperationHandle handle)
{
if (handle.Status == AsyncOperationStatus.Succeeded){
Debug.Log($"Loaded {SeasonBannerRef.RuntimeKey} from Asset Reference");
bannerInstance = Instantiate(SeasonBannerRef.Asset, transform) as GameObject;
}
else
{
Debug.LogError($"Failed to load {SeasonBannerRef.RuntimeKey} from Asset Reference");
}
}
public void UnloadSeasonBanner()
{
if (bannerInstance)
{
Destroy(bannerInstance);
}
if (SeasonBannerRef.IsValid())
{
SeasonBannerRef.ReleaseAsset();
}
}
AssetReference in this case is a generic type that supports any type of asset. If you know what type of object you want to load, you should use a derived class such as AssetReferenceGameObject or AssetReferenceSprite, which will also give you a better experience in the editor inspector.
In this scenario, you are responsible for loading and unloading the asset manually. Releasing the asset will not automatically destroy the instances, so you need to keep track of those and clean up your scene accordingly.
If you try to load an asset that’s already been loaded, you’ll get a runtime exception and the call will be ignored. In the same way, releasing an asset that’s not loaded will give you a warning and will be ignored.
Another alternative, if you do not need to separate loading the asset and instantiating it, is to use InstantiateAsync
private GameObject instBanner;
public IEnumerator InstantiateBanner()
{
if (SeasonBannerRef != null)
{
AsyncOperationHandle<GameObject> instantiateHandle = SeasonBannerRef.InstantiateAsync(transform);
yield return instantiateHandle;
instBanner = instantiateHandle.Result;
}
}
public void DestroyBanner()
{
if (instBanner != null)
{
bool destroyed = Addressables.ReleaseInstance(instBanner);
if (!destroyed)
{
Debug.LogWarning("Tried to destroy a game object that was not created via Addressables");
}
}
}
In this case, for each call to InstantiateAsync you will need to release the resulting instance. This method has some additional overhead, so ideally you should follow the first pattern, calling LoadAssetAsync and then instantiating manually, especially if you intend to create a lot of instances.
Loading via asset address
You can also follow a similar pattern, but using the asset address instead. Here is how that looks like:
private AsyncOperationHandle handle;
public void LoadSeasonBannerByName(string assetName)
{
handle = Addressables.LoadAssetAsync<GameObject>(assetName);
handle.Completed += OnBannerLoadByNameCompleted;
}
private void OnBannerLoadByNameCompleted(AsyncOperationHandle handle)
{
if (handle.Status == AsyncOperationStatus.Succeeded)
{
GameObject loadedAsset = handle.Result as GameObject;
bannerInstance = Instantiate(loadedAsset);
}
else
{
Debug.LogError($"Failed to load {SeasonBannerRef.RuntimeKey} from Asset address");
}
}
public void ReleaseAssetLoadedByName()
{
if (bannerInstance)
{
Destroy(bannerInstance);
}
if (handle.IsValid())
{
Addressables.Release(handle);
}
}
You can also load multiple assets at once, using the corresponding method:
private AsyncOperationHandle
<IList<GameObject>> loadAssetsHandle;
public void LoadSeasonBannersByName(IEnumerable assetNames)
{
loadAssetsHandle = Addressables.LoadAssetsAsync<GameObject>(assetNames,
asset =>
{
// We got the GameObject asset loaded, do something with it
},
Addressables.MergeMode.Union,
false);
}
As you can see, for each loaded asset a callback will be called. The flag in the end, set to false, means that we don’t fail the whole operation if an asset fails to load.
Scenes loading
Scenes loading works similarly to regular assets, with a few caveats.
You will need to replace calls to SceneManager.LoadSceneAsync with Addressables.LoadSceneAsync. This new method uses the SceneManager under the hood, while also taking care of loading and unloading the asset and all its dependencies.
Addressables.LoadSceneAsync takes a activateOnLoad parameter that lets you determine whether the scene should be activated as soon as it's loaded (true by default). If you set this to false, you will need to activate the scene manually using the resulting SceneInstance you receive from the async call. One important side-effect is that, while the scene awaits to be activated, the entire loading queue will be blocked, also for other Addressables assets. For this reason, it should be used with caution and left it to true whenever possible.
When switching scene by selecting Single load mode, the previous scene will be discarded and unloaded from memory.
Pre-loading Assets
When following the approaches we covered so far, there can be cases where the asset loading takes a long time, particularly for assets with a long dependency chain or that are part of a large bundle that needs to be downloaded.
To solve this problem, you can preload bundles using Addressables.DownloadDependenciesAsync and passing the name of the asset. This will download and then cache it locally, making subsequent calls such as LoadAssetAsync much faster. You can also pass a label, and all assets with that label will be downloaded.
This returns a handle that you can either use to yield, if inside a Coroutine, or subscribe to events such as Completed to proceed when the download is completed. When the Completed callback is called, you can check the status to confirm that the operation succeeded. You can also check the progress of the download using that handle, using the PercentComplete field.
You can also use labels to download multiple assets that are not necessarily in the same group. For example, if you’re building a demo using the same project, you might use a “demo” label to collect assets for that demo without necessarily having a dedicated group. This might result in multiple asset bundles being downloaded, along with the dependencies of each asset with that label.
Clearing the caches
Normally, bundles will be cached in the user device for a fairly long time. There might be cases though where you want to delete a cached asset, which will lead your game to fetch it again from the remote source if necessary.
To do so, you can use the Addressables.ClearDependencyCacheAsync method, passing the corresponding key(s) for the asset bundles or the assets you wish to clear from the local cache.
You can also clear the entire cache by calling Cache.ClearCache.
Note that this will lead to higher costs due to increased traffic, and longer loading times, so you should use this sporadically and have a good reason to do so.
An alternative to this is setting an expiry time on the content downloaded, so it doesn’t linger in the user’s device for too long taking up storage space. To do so, you can set the Cache.expirationDelay value (in seconds), which by default is 150 days.
Testing
While Addressables are a great asset delivery system, building and uploading assets can be time consuming while developing your game. Luckily, there are a few ways to expedite this while we work in the editor.
Play Mode Script
This is available in the dropdown in the Addressables Groups window.
You can select “Use Asset Database” for easier testing, as it will force all assets to be loaded locally from the Editor Asset database, rather than fetching from remote origins. This also doesn’t require building your asset bundles beforehand. As the dropdown indicates, this is the fastest testing method.
If you want to test your groups without building beforehand, you can choose “Simulate Groups”. Assets will still be loaded locally from the Editor Asset database, but following the dependencies defined in your groups. You can check the Addressables Profiler module (or if you’re on an older version, you can use the Addressables Event Viewer window instead) to see what assets get loaded and unloaded as a result.
Finally, if you already have a build available, you can select "Use Existing Build" and it will load assets from there just like in the regular game. This includes fetching remote asset bundles from your CDN, for groups that are marked as Remote.
Addressables Profiler module
In order to analyse what's going on under the hood while your game is running, you can use Addressables Profiler Module. This is an optional tool that you can enable from the Profiler, using the Profiler Modules dropdown, and it allows you to monitor what Addressables are loaded in memory, reference counts, Catalogs loaded and so on.
If you have used Addressables in the past, you are probably familiar with the Event Viewer, a tool that had a similar purpose to the Profiler module. While you can still find The Event Viewer under Window - Asset Manager - Addressables - Event Viewer, it is now deprecated and replaced with the Addressables Profiler Module.
The Catalog
The catalog is a file that describes what assets are contained in your bundles, their locations and other useful information, and it’s used by the engine to understand how to load assets at runtime.
Normally, Unity will use a default catalog generated implicitly for you, which is fine for simple scenarios, but will prevent your game from supporting remote content updates. There are also more complex scenarios where you might need to manage online catalogs in order to do versioning, or load multiple catalogs to allow for a more granular asset delivery architecture.
In order to do so, you’ll need to explicitly export the catalog, by going to the Addressables Settings window and enabling the “Build Remote Catalog” option.
Then, when you rebuild your bundles locally, you will notice two new files in the target directory, a JSON file containing your catalog, plus a hash file used by Unity to properly cache your catalog.
The next step is to ensure the Build and Load Paths are set to Remote, which will ensure the catalog and its hash file are uploaded to UGS. Then, the next time you upload your bundles to your bucket, you’ll see the catalog files included.
On the next asset loading operation, Unity will check the hash to determine whether there was an update to that bundle, and if so, download the new catalog.
Note that this behaviour can be disabled by selecting the “Only update Catalogs manually” in the Settings window. You will then need to do so using the related UpdateCatalogs and LoadContentCatalog APIs, which is outside the scope of this tutorial.
If you are uploading files manually, it is essential that the hash file maintains the same name as the catalog file, otherwise catalog caching will not work, resulting in increased loading times and network traffic.
Keep in mind that updating your catalog at runtime, after you already loaded some asset bundles, may lead to conflict and duplicate assets being loaded. One way to avoid this is to unload all assets before updating your catalog, but this might also lead to long loading times. For this reason, ensure you have a clear update strategy in your game, loading and unloading at specific times where a long loading time would not negatively affect your user experience.
Managing Releases and Badges
As shown so far, uploading your bundles to UGS allows you to manage your game files on the fly, making changes and enabling game content with a few clicks. In order to do so, we need to leverage Releases and Badges.
A release is essentially a snapshot of the current content of your bucket, along with Release notes and attached Badges. It is used to deliver targeted asset bundle to your users.
Releases are immutable, so if you want to add or change content you’ll need to create a new release.
Badges are used to tag releases, and they allow you to make changes to what content you deliver without requiring a client update. As we’ve seen before, UGS will create the “latest” badge by default, but you can define new ones if needed, if you are in need of a more complex content delivery strategy.
A badge can be associated with just one release, but a release can have multiple badges, as shown below.
Each badge has a related Addressables URL defined, so you can use that to refer to the release linked to it. You can either pass that URL in the Remote path in the editor, or if you’re using the Unity CDD Package as I’ve covered earlier, you can directly select the badge from the Profiles editor window without having to pass URLs around.
A common workflow is to create and test releases in a staging environment and bucket, and then transfer them to your production bucket and environment by promoting them, as shown in the screenshot below.
When you promote a release, you can add new Release Notes as well. If you are familiar with the release process on Google Play, many of the concepts are the same.
Final Words
That was surely a lot to take on. Addressables are versatile and very powerful when used correctly, it will also take some time to get familiar with them and figure out how to best design your project to leverage their functionality.
There are a lot of advanced options and settings that you can use to customise your usage of Addressables, way more that I can cover in this guide. You can create custom caches, write your own custom hosting service and logic, create your own Play Mode scripts for testing and other really cool stuff. In most cases the default options will work for your project.
This should give you a good starting point to get familiar with the system and convert your project to Addressables.
Please feel free to get in touch if you have any feedback or content you'd like to see covered.
Posted on November 15, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.