跳到正文
cccskills
免费在 GitHub 公开

tools-unity-addressables

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

中文简介正在准备中。当前显示原文说明。

查看安装方法

包含的文件(1)

  • SKILL.md13.7 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

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

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

adopt

免费

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.

中文简介正在准备中。当前显示原文说明。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

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.

中文简介正在准备中。当前显示原文说明。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

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.

中文简介正在准备中。当前显示原文说明。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

art-bible

免费

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.

中文简介正在准备中。当前显示原文说明。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

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

中文简介正在准备中。当前显示原文说明。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

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.

中文简介正在准备中。当前显示原文说明。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

查看 IdoCohen560 的全部技能

报告此技能的问题