Addressables — Modern Asset Loading
Asynchronous loading by address, a replacement for Resources.Load and AssetBundles.
Historically, Unity has had several ways to load an asset at runtime: Resources.Load,
AssetBundles, and Addressables (com.unity.addressables). The latter is the recommended modern
path, and in new projects you should avoid the others.
What’s wrong with Resources.Load
Resources.Load<Texture>("MyTexture") is a simple API: you fetch an asset from the Resources/ folder by path.
It sounds convenient, but:
- Everything in Resources goes into the build in full. Unity packs any asset in
Resources/even if it isn’t used in the current scene. This drastically bloats the build size. - No asynchronous loading out of the box.
Resources.LoadAsyncexists, but it doesn’t solve the first problem. - No version management. You can’t update a pack of assets separately from the build.
Unity itself states in the documentation: “We recommend not using the Resources folder.”
The idea behind Addressables
Each asset (or group) is assigned an address — a string key. You load by this key asynchronously. Where the asset physically lives — in the local build, on a remote CDN, in a shared bundle with others — is determined by configuration, not by code. You can update a group of assets on the CDN, and players will get the new versions without redeploying the build.
Code splitting + dynamic import + CDN. You call import('./module-heavy') — the browser
pulls the chunk over the network. You have one build, with heavy modules split out and loaded on demand.
Addressables.LoadAssetAsync<GameObject>("EnemyBoss") — Unity finds which bundle the asset is in,
loads it (from disk or from a CDN), and returns a handle. You use it → you release it.
Setup
- Install the
com.unity.addressablespackage via the Package Manager. - Window → Asset Management → Addressables → Groups — the Groups window opens.
- Select an asset in the Project and tick the Addressable checkbox in the Inspector. By default the address =
the asset’s path; change it to a short name, for example
EnemyBoss. - A group is a container of assets with shared build settings (local/remote, compression, …).
Loading in code
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public class BossSpawner : MonoBehaviour
{
[SerializeField] private AssetReferenceGameObject bossRef;
private GameObject _bossInstance;
public async void SpawnBoss() {
// Load the GameObject prefab asynchronously
AsyncOperationHandle<GameObject> handle = bossRef.LoadAssetAsync<GameObject>();
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded) {
_bossInstance = Instantiate(handle.Result, transform.position, Quaternion.identity);
}
}
private void OnDestroy() {
// IMPORTANT: release the loaded asset so it can be unloaded
if (bossRef.IsValid()) {
bossRef.ReleaseAsset();
}
}
}
AssetReference is the field type for the Inspector. You drag an Addressable asset into it and get
a selection dropdown in the Inspector. It’s typed and more convenient than strings.
You can also use a string if the address is known:
AsyncOperationHandle<Sprite> handle = Addressables.LoadAssetAsync<Sprite>("UI/Icons/Coin");
await handle.Task;
icon.sprite = handle.Result;
InstantiateAsync — spawn immediately
Often you don’t want to “load an asset, then Instantiate”, but to create an instance right away. There’s a short-cut:
AsyncOperationHandle<GameObject> handle = Addressables.InstantiateAsync("EnemyBoss", spawnPos, Quaternion.identity);
await handle.Task;
GameObject boss = handle.Result;
// When the boss is killed:
Addressables.ReleaseInstance(boss); // releases BOTH the asset AND destroys the GameObject
Every Load (or InstantiateAsync) needs a Release. If you don’t release, the asset stays in memory for the entire lifetime of the application. This is the most common source of leaks in Addressables projects.
Groups and profiles
In Addressables Groups you group assets by logic — for example, “WorldStreaming”, “UI”, “DLC_Level1”. Each group is given settings:
- Build Path / Load Path — where the bundle is built and loaded from (locally / by URL).
- Compression — LZ4 (fast) / LZMA (compact) / Uncompressed.
- Bundle Mode — Pack Together / Pack Separately / Pack Together by Label.
Profiles are sets of variables ({LOCAL_BUILD_PATH}, {REMOTE_LOAD_PATH}). Convenient for
switching “Editor / Test / Production” in a single click.
Remote Content and Content Update
The main upside of Addressables is updating content without updating the build. The scenario:
- The “Levels_DLC1” group is built into a bundle with Build Path = a remote CDN.
- On launch the game queries the catalog (a JSON describing the bundle versions).
- If the catalog has been updated, Unity downloads the new bundles transparently.
This is especially valuable for:
- DLC and seasonal content — you don’t rebuild the whole build, you just put new assets on the CDN.
- Hot fixes — you fixed an asset, no need to wait for App Store review.
- Content A/B tests — you give different players different groups of assets.
When Resources is justified after all
- One or two small assets that are always needed — for example,
Resources/DefaultMaterial.mat. - Prototypes — while it’s still simpler than configuring Addressables.
In production, 99% of assets go through Addressables; the rest are embedded in scenes or Direct References.
Profiling and analysis
Under Window → Asset Management → Addressables → Analyze there’s a set of rules that check:
- Do all asset references resolve?
- Are the same assets duplicated across different bundles?
- Are there any unused assets in the groups?
Also: the Addressables Profiler shows at runtime which groups are loaded, how much memory they occupy, and what each one’s reference counter is.
In the next and final chapter of the main section — HLSL shaders in more detail.