بنقرة واحدة
tools-unity-addressables
// Unity Addressables patterns for asset loading, memory management, reference counting, and remote content delivery.
// Unity Addressables patterns for asset loading, memory management, reference counting, and remote content delivery.
Mobile-specific Unity optimization patterns for memory, battery, thermal, and performance.
Unity animation patterns including Animancer, state machines, and performance optimization.
Behavior Designer patterns for AI behavior trees including task creation, shared variables, conditionals, and debugging.
Unity Cinemachine camera system patterns including virtual cameras, blending, and state-driven cameras.
FlowCanvas visual scripting patterns for abilities, custom nodes, and graph execution.
Gameplay Ability System patterns for abilities, effects, attributes, and tags including recursion safety and lifecycle management.
| name | tools-unity-addressables |
| description | Unity Addressables patterns for asset loading, memory management, reference counting, and remote content delivery. |
Addressables provides a system for loading assets by address, with automatic dependency management, memory tracking, and remote content delivery support.
// In inspector - reference to addressable asset
[SerializeField] private AssetReference _prefabReference;
[SerializeField] private AssetReferenceT<AudioClip> _audioReference;
[SerializeField] private AssetReferenceSprite _spriteReference;
[SerializeField] private AssetReferenceGameObject _gameObjectReference;
// 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();
// Load from serialized reference
var handle = _prefabReference.LoadAssetAsync<GameObject>();
var prefab = await handle.ToUniTask();
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 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);
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);
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);
}
// 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)
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();
}
}
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();
}
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;
}
public async UniTask<bool> CheckForUpdatesAsync()
{
var checkHandle = Addressables.CheckForCatalogUpdates();
var catalogs = await checkHandle.ToUniTask();
Addressables.Release(checkHandle);
return catalogs != null && catalogs.Count > 0;
}
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);
}
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);
}
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;
}
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);
}
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();
}
}
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
}
}
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);
}
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;
}
}
}
| 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 |