Unity Addressables patterns for asset loading, memory management, reference counting, and remote content delivery.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add IdoCohen560/claude-unity-game-studio --skill tools-unity-addressables --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Tools Unity Addressables?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/idocohen560-tools-unity-addressables-claude-unity-game-studio)More formats (shields.io, HTML) on the badges page.
---
name: tools-unity-addressables
description: Unity Addressables patterns for asset loading, memory management, reference counting, and remote content delivery.
---
# Unity Addressables
## Overview
Addressables provides a system for loading assets by address, with automatic dependency management, memory tracking, and remote content delivery support.
## When to Use
- Loading assets dynamically at runtime
- Managing asset memory lifecycle
- Implementing remote asset downloads
- Reducing build size with asset bundles
- Loading scenes asynchronously
## Core Concepts
### Asset References
```csharp
// In inspector - reference to addressable asset
[SerializeField] private AssetReference _prefabReference;
[SerializeField] private AssetReferenceT<AudioClip> _audioReference;
[SerializeField] private AssetReferenceSprite _spriteReference;
[SerializeField] private AssetReferenceGameObject _gameObjectReference;
```
### Loading by Address (String)
```csharp
// Load by address string
var handle = Addressables.LoadAssetAsync<GameObject>("Prefabs/Enemy");
var prefab = await handle.Task;
// Or with UniTask
var prefab = await Addressables.LoadAssetAsync<GameObject>("Prefabs/Enemy").ToUniTask();
```
### Loading by AssetReference
```csharp
// Load from serialized reference
var handle = _prefabReference.LoadAssetAsync<GameObject>();
var prefab = await handle.ToUniTask();
```
## Loading Patterns
### Basic Asset Loading
```csharp
public class AssetLoader
{
private AsyncOperationHandle<GameObject> _handle;
public async UniTask<GameObject> LoadAsync(string address, CancellationToken ct)
{
_handle = Addressables.LoadAssetAsync<GameObject>(address);
try
{
return await _handle.ToUniTask(cancellationToken: ct);
}
catch (OperationCanceledException)
{
// Release on cancellation
if (_handle.IsValid())
Addressables.Release(_handle);
throw;
}
}
public void Unload()
{
if (_handle.IsValid())
{
Addressables.Release(_handle);
_handle = default;
}
}
}
```
### Instantiate with Auto-Release
```csharp
// Instantiate and track for automatic release
var handle = Addressables.InstantiateAsync(address, parent);
var instance = await handle.ToUniTask();
// Later: Release when done (also destroys GameObject)
Addressables.ReleaseInstance(instance);
// OR release handle directly
Addressables.Release(handle);
```
### Load Multiple Assets
```csharp
public async UniTask<IList<T>> LoadAllAsync<T>(string label, CancellationToken ct)
{
var handle = Addressables.LoadAssetsAsync<T>(
label,
obj => Debug.Log($"Loaded: {obj}") // Per-asset callback
);
return await handle.ToUniTask(cancellationToken: ct);
}
// Usage
var allEnemies = await LoadAllAsync<GameObject>("Enemies", ct);
```
### Load with Dependencies
```csharp
public async UniTask<T> LoadWithDependenciesAsync<T>(string address, CancellationToken ct)
{
// Get download size first
var sizeHandle = Addressables.GetDownloadSizeAsync(address);
var size = await sizeHandle.ToUniTask();
if (size > 0)
{
// Download dependencies
var downloadHandle = Addressables.DownloadDependenciesAsync(address);
await downloadHandle.ToUniTask(cancellationToken: ct);
Addressables.Release(downloadHandle);
}
// Load asset
var loadHandle = Addressables.LoadAssetAsync<T>(address);
return await loadHandle.ToUniTask(cancellationToken: ct);
}
```
## Reference Counting
### Understanding Reference Counts
```csharp
// Each Load increments ref count
var handle1 = Addressables.LoadAssetAsync<T>(address); // RefCount = 1
var handle2 = Addressables.LoadAssetAsync<T>(address); // RefCount = 2 (same asset)
// Each Release decrements
Addressables.Release(handle1); // RefCount = 1
Addressables.Release(handle2); // RefCount = 0 (asset unloaded)
```
### Safe Resource Loader Pattern
```csharp
public class ResourceLoader : IDisposable
{
private readonly Dictionary<string, AsyncOperationHandle> _loadedAssets = new();
private readonly object _lock = new();
public async UniTask<T> LoadAssetAsync<T>(string key, CancellationToken ct = default)
{
lock (_lock)
{
if (_loadedAssets.TryGetValue(key, out var existingHandle))
{
return (T)existingHandle.Result;
}
}
var handle = Addressables.LoadAssetAsync<T>(key);
try
{
var result = await handle.ToUniTask(cancellationToken: ct);
lock (_lock)
{
_loadedAssets[key] = handle;
}
return result;
}
catch
{
if (handle.IsValid())
Addressables.Release(handle);
throw;
}
}
public async UniTask ReleaseAll(bool immediate = false)
{
List<AsyncOperationHandle> handles;
lock (_lock)
{
handles = _loadedAssets.Values.ToList();
_loadedAssets.Clear();
}
foreach (var handle in handles)
{
if (handle.IsValid())
Addressables.Release(handle);
}
if (!immediate)
await UniTask.Yield(); // Allow cleanup
}
public void Dispose()
{
ReleaseAll(true).Forget();
}
}
```
## Scene Loading
### Load Scene Additively
```csharp
public async UniTask<SceneInstance> LoadSceneAsync(
string sceneAddress,
LoadSceneMode mode = LoadSceneMode.Additive,
CancellationToken ct = default)
{
var handle = Addressables.LoadSceneAsync(sceneAddress, mode);
return await handle.ToUniTask(cancellationToken: ct);
}
// Unload scene
public async UniTask UnloadSceneAsync(SceneInstance scene)
{
var handle = Addressables.UnloadSceneAsync(scene);
await handle.ToUniTask();
}
```
### Scene with Activation Control
```csharp
public async UniTask<SceneInstance> LoadSceneWithDelayedActivation(string address)
{
var handle = Addressables.LoadSceneAsync(
address,
LoadSceneMode.Additive,
activateOnLoad: false // Don't activate immediately
);
var scene = await handle.ToUniTask();
// Do preparation work...
await PrepareSceneAsync();
// Now activate
await scene.ActivateAsync().ToUniTask();
return scene;
}
```
## Remote Content Delivery
### Check for Updates
```csharp
public async UniTask<bool> CheckForUpdatesAsync()
{
var checkHandle = Addressables.CheckForCatalogUpdates();
var catalogs = await checkHandle.ToUniTask();
Addressables.Release(checkHandle);
return catalogs != null && catalogs.Count > 0;
}
```
### Update Catalogs
```csharp
public async UniTask UpdateCatalogsAsync(IProgress<float> progress = null)
{
var checkHandle = Addressables.CheckForCatalogUpdates();
var catalogs = await checkHandle.ToUniTask();
if (catalogs != null && catalogs.Count > 0)
{
var updateHandle = Addressables.UpdateCatalogs(catalogs);
await updateHandle.ToUniTask();
Addressables.Release(updateHandle);
}
Addressables.Release(checkHandle);
}
```
### Download Content
```csharp
public async UniTask DownloadContentAsync(
string label,
IProgress<float> progress,
CancellationToken ct)
{
// Get download size
var sizeHandle = Addressables.GetDownloadSizeAsync(label);
var totalSize = await sizeHandle.ToUniTask();
Addressables.Release(sizeHandle);
if (totalSize <= 0)
{
Debug.Log("All content already downloaded");
return;
}
Debug.Log($"Downloading {totalSize / 1_000_000f:F2} MB");
// Download
var downloadHandle = Addressables.DownloadDependenciesAsync(label);
// Report progress
while (!downloadHandle.IsDone)
{
progress?.Report(downloadHandle.PercentComplete);
if (ct.IsCancellationRequested)
{
Addressables.Release(downloadHandle);
throw new OperationCanceledException(ct);
}
await UniTask.Yield();
}
if (downloadHandle.Status != AsyncOperationStatus.Succeeded)
{
var error = downloadHandle.OperationException?.Message ?? "Unknown error";
Addressables.Release(downloadHandle);
throw new Exception($"Download failed: {error}");
}
Addressables.Release(downloadHandle);
}
```
### Clear Cache
```csharp
public async UniTask ClearCacheAsync()
{
// Clear all cached bundles
Caching.ClearCache();
// Re-initialize Addressables
await Addressables.InitializeAsync().ToUniTask();
}
public bool ClearCacheForKey(string key)
{
var locator = Addressables.ResourceLocators.FirstOrDefault();
if (locator != null && locator.Locate(key, typeof(object), out var locations))
{
foreach (var location in locations)
{
Caching.ClearAllCachedVersions(location.InternalId);
}
return true;
}
return false;
}
```
## Progress Reporting
### Download Progress
```csharp
public struct DownloadProgress
{
public long TotalBytes;
public long DownloadedBytes;
public float Percent;
public string CurrentFile;
}
public async UniTask DownloadWithDetailedProgress(
string label,
IProgress<DownloadProgress> progress,
CancellationToken ct)
{
var sizeHandle = Addressables.GetDownloadSizeAsync(label);
var totalBytes = await sizeHandle.ToUniTask();
Addressables.Release(sizeHandle);
var downloadHandle = Addressables.DownloadDependenciesAsync(label);
while (!downloadHandle.IsDone)
{
ct.ThrowIfCancellationRequested();
var status = downloadHandle.GetDownloadStatus();
progress?.Report(new DownloadProgress
{
TotalBytes = totalBytes,
DownloadedBytes = status.DownloadedBytes,
Percent = status.Percent,
CurrentFile = status.DownloadedBytes.ToString()
});
await UniTask.Yield();
}
Addressables.Release(downloadHandle);
}
```
## Memory Management
### Preloading Assets
```csharp
public class AssetPreloader
{
private readonly List<AsyncOperationHandle> _preloadedHandles = new();
public async UniTask PreloadAsync(IEnumerable<string> addresses, CancellationToken ct)
{
var tasks = addresses.Select(async addr =>
{
var handle = Addressables.LoadAssetAsync<object>(addr);
await handle.ToUniTask(cancellationToken: ct);
_preloadedHandles.Add(handle);
});
await UniTask.WhenAll(tasks);
}
public void ReleaseAll()
{
foreach (var handle in _preloadedHandles)
{
if (handle.IsValid())
Addressables.Release(handle);
}
_preloadedHandles.Clear();
}
}
```
### Memory Budget Tracking
```csharp
public class AddressableMemoryTracker
{
public long GetEstimatedMemoryUsage()
{
long total = 0;
// This is approximate - actual tracking requires custom implementation
foreach (var locator in Addressables.ResourceLocators)
{
// Track loaded bundles
}
return total;
}
public void LogLoadedAssets()
{
// Use Addressables Event Viewer in Editor
// For runtime, implement custom tracking
}
}
```
## Error Handling
### Robust Loading Pattern
```csharp
public async UniTask<T> LoadWithRetryAsync<T>(
string address,
int maxRetries = 3,
CancellationToken ct = default)
{
Exception lastException = null;
for (int attempt = 0; attempt < maxRetries; attempt++)
{
try
{
var handle = Addressables.LoadAssetAsync<T>(address);
return await handle.ToUniTask(cancellationToken: ct);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
lastException = ex;
Debug.LogWarning($"Load attempt {attempt + 1} failed: {ex.Message}");
if (attempt < maxRetries - 1)
{
await UniTask.Delay(1000 * (attempt + 1), cancellationToken: ct);
}
}
}
throw new Exception($"Failed to load {address} after {maxRetries} attempts", lastException);
}
```
### Handle Invalid Operations
```csharp
public void SafeRelease<T>(ref AsyncOperationHandle<T> handle)
{
if (handle.IsValid())
{
try
{
Addressables.Release(handle);
}
catch (Exception ex)
{
Debug.LogWarning($"Error releasing handle: {ex.Message}");
}
finally
{
handle = default;
}
}
}
```
## Best Practices
1. **Always release loaded assets** when done
2. **Use labels** for grouping related assets
3. **Preload critical assets** during loading screens
4. **Check download size** before downloading
5. **Handle cancellation** properly
6. **Use typed AssetReferences** in inspector
7. **Profile memory** with Addressables Event Viewer
8. **Test remote loading** early in development
9. **Implement retry logic** for network operations
10. **Clear cache** when updating content significantly
## Troubleshooting
| Issue | Solution |
|-------|----------|
| Asset not found | Check address spelling, rebuild addressables |
| Memory leak | Ensure all handles are released |
| Slow loading | Use preloading, check bundle sizes |
| Download fails | Check network, implement retry |
| Duplicate loading | Track loaded assets, use singleton loader |
| Cache issues | Clear cache, check catalog version |
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!