Skip to content
cccskills
FreePublished on GitHub

tools-unity-addressables

Unity Addressables patterns for asset loading, memory management, reference counting, and remote content delivery.

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md13.7 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

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

// 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)

// 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

// Load from serialized reference
var handle = _prefabReference.LoadAssetAsync<GameObject>();
var prefab = await handle.ToUniTask();

Loading Patterns

Basic Asset Loading

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

// 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

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

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

// 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

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

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

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

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

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

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

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

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

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

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

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

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

IssueSolution
Asset not foundCheck address spelling, rebuild addressables
Memory leakEnsure all handles are released
Slow loadingUse preloading, check bundle sizes
Download failsCheck network, implement retry
Duplicate loadingTrack loaded assets, use singleton loader
Cache issuesClear cache, check catalog version

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

adopt

Free

Brownfield onboarding — audits existing project artifacts for template format compliance (not just existence), classifies gaps by impact, and produces a numbered migration plan. Run this when joining an in-progress project or upgrading from an older template version. Distinct from /project-stage-detect (which checks what exists) — this checks whether what exists will actually work with the template's skills.

The English summary is being prepared. Showing the original description.

IdoCohen560/claude-unity-game-studio22Updated Jul 8, 2026

Creates an Architecture Decision Record (ADR) documenting a significant technical decision, its context, alternatives considered, and consequences. Every major technical choice should have an ADR.

The English summary is being prepared. Showing the original description.

IdoCohen560/claude-unity-game-studio22Updated Jul 8, 2026

Validates completeness and consistency of the project architecture against all GDDs. Builds a traceability matrix mapping every GDD technical requirement to ADRs, identifies coverage gaps, detects cross-ADR conflicts, verifies engine compatibility consistency across all decisions, and produces a PASS/CONCERNS/FAIL verdict. The architecture equivalent of /design-review.

The English summary is being prepared. Showing the original description.

IdoCohen560/claude-unity-game-studio22Updated Jul 8, 2026

Guided, section-by-section Art Bible authoring. Creates the visual identity specification that gates all asset production. Run after /brainstorm is approved and before /map-systems or any GDD authoring begins.

The English summary is being prepared. Showing the original description.

IdoCohen560/claude-unity-game-studio22Updated Jul 8, 2026

Audits game assets for compliance with naming conventions, file size budgets, format standards, and pipeline requirements. Identifies orphaned assets, missing references, and standard violations.

The English summary is being prepared. Showing the original description.

IdoCohen560/claude-unity-game-studio22Updated Jul 8, 2026

Generate per-asset visual specifications and AI generation prompts from GDDs, level docs, or character profiles. Produces structured spec files and updates the master asset manifest. Run after art bible and GDD/level design are approved, before production begins.

The English summary is being prepared. Showing the original description.

IdoCohen560/claude-unity-game-studio22Updated Jul 8, 2026

See all skills by IdoCohen560

Report a problem with this skill