Unity Term Book
进阶技术

Addressables

Addressables 是 Unity 的高级资源管理系统,按地址(key)异步加载 Asset,支持 DLC、CDN 流式分发,并在运行时自动管理内存引用计数。适合需要热更新、分包下载或大世界流式加载的团队,能显著减少 Resources.Load 硬加载带来的包体膨胀、依赖混乱与内存峰值压力。

想象一下...

Resources.Load() 像亲自走进仓库——必须知道精确路径,还得干等。Addressables 是快递上门:你报品名(address),服务在本地找或从 CDN 下载。你拿到像运单号一样的 handle——游戏不阻塞;到达时回调触发。释放 handle 就是让服务回收内存。

概念详解

Addressable Asset System 解决 Resources 文件夹的局限 (无法单独卸载、路径硬编码、无 CDN)以及原始 AssetBundle 的痛点(API 复杂、依赖需手管)。 Addressables 建立在 AssetBundle 之上,但自动化大部分工作。

每个资源标记为 Addressable 并赋予 Address(字符串 key,例如 “prefabs/hero_sword”)。资源进入 Groups——每组编译成一个 AssetBundle。组可以是 Local(进包)或 Remote(上传 CDN)。 Labels 让你按标签一次加载多个资源。

主要 API: Addressables.LoadAssetAsync<T>(key) 返回 AsyncOperationHandle<T> ——可 await,或订阅 .Completed。 重要:用完后调用 Addressables.Release(handle) ——否则资源永不卸载(内存泄漏)。

InstantiateAsync 合并加载 + Instantiate:卸载用 Addressables.ReleaseInstance(go) 而非 Destroy。Addressables 还有 play mode scripts:Fast Mode (不烘 bundle——迭代快)、Virtual Mode(模拟 bundle)、Packed Play Mode(真实 bundle)。

异步加载流程

代码调用
→

Addressables.LoadAssetAsync("heroes/knight")

Check Cache
→

已经在内存中?

Hit: instant
从源加载
→

本地 bundle 或 CDN 下载

Deserialize
→

解压并在内存中创建对象

Callback
→

handle.Completed → result available

Release()
→

RefCount-- → 0 → 从内存卸载

动手步骤

1

安装 Addressables

Window → Package Manager → 搜索 "Addressables" → Install。Window → Asset Management → Addressables → Groups 打开 Groups 窗口。

2

将资源标为 Addressable

在 Project 中选中 → Inspector → 勾选 "Addressable" → 设置 address(例如 "heroes/knight")。

3

配置 Groups(Local 与 Remote)

Addressables Groups 窗口:Create New Group → 设置 Build Path(Local 或 CDN 的 Custom Remote Path)。把资源拖进组。

4

构建内容

Addressables Groups → Build → New Build → Default Build Script。Play 或正式出包前先 Build。

5

正确 Load 与 Release

用 address 调用 LoadAssetAsync 或 InstantiateAsync。保留 handle。用完:Release(handle) 或 ReleaseInstance(go)。

交互模拟器

模拟异步加载流程——观察 Reference Count 与 Memory Pool。

按下 Load 开始...

Loaded

0

Total Refs

0

Memory ~

0 MB

代码示例

基础

异步加载 prefab 并 Instantiate——使用 await(C# async/await)。

using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using System.Threading.Tasks;

public class AssetLoader : MonoBehaviour
{
  AsyncOperationHandle<GameObject> knightHandle;

  async void Start()
  {
      // Async load — does not block the main thread
      knightHandle = Addressables.LoadAssetAsync<GameObject>("heroes/knight");
      await knightHandle.Task; // Or .Completed callback

      if (knightHandle.Status == AsyncOperationStatus.Succeeded)
      {
          Instantiate(knightHandle.Result, transform.position, Quaternion.identity);
      }
  }

  void OnDestroy()
  {
      // REQUIRED: Release to avoid a memory leak
      if (knightHandle.IsValid())
          Addressables.Release(knightHandle);
  }
}

// Or InstantiateAsync (auto-managed lifecycle)
async void SpawnEnemy(string address, Vector3 pos)
{
  var handle = Addressables.InstantiateAsync(address, pos, Quaternion.identity);
  await handle.Task;
  // When the enemy dies: Addressables.ReleaseInstance(go) instead of Destroy
}

代码示例

进阶

按 Label 一次加载多个资源,并在后台预加载下一场景。

using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using System.Collections.Generic;
using System.Threading.Tasks;

public class AssetManager : MonoBehaviour
{
  readonly List<AsyncOperationHandle> handles = new();

  // Load every asset labeled "ui_icons" at once
  async Task LoadUIIcons()
  {
      var handle = Addressables.LoadAssetsAsync<Sprite>(
          "ui_icons",
          sprite => { Debug.Log($"Loaded: {sprite.name}"); }
      );
      await handle.Task;
      handles.Add(handle);
  }

  // Preload the next scene in the background while the player is in-game
  AsyncOperationHandle<UnityEngine.ResourceManagement.ResourceProviders.SceneInstance> sceneHandle;

  public async void PreloadNextScene(string sceneAddress)
  {
      sceneHandle = Addressables.LoadSceneAsync(sceneAddress,
          UnityEngine.SceneManagement.LoadSceneMode.Additive,
          activateOnLoad: false); // Load but do not activate yet
      await sceneHandle.Task;
      Debug.Log("Scene preloaded, waiting for activation");
  }

  public async void ActivatePreloadedScene()
  {
      await sceneHandle.Result.ActivateAsync();
  }

  void OnDestroy()
  {
      foreach (var h in handles)
          Addressables.Release(h);
  }
}

📌 快速记忆

  • ▸LoadAssetAsync = 非阻塞加载;返回 Handle
  • ▸用完必须 Release(handle)——避免泄漏
  • ▸InstantiateAsync + ReleaseInstance 替代 Instantiate/Destroy
  • ▸Label = 按组一次加载多个资源
  • ▸Fast Mode:开发(快);Packed Mode:真实 bundle 测试

⚠️ 常见错误

  • ❌ 从不 Release handle → 严重内存泄漏

    资源留在 RAM;引用计数永不归 0

    ✅ 始终在 OnDestroy 中 Release,并始终保留 handle

  • ❌ Exception: Attempting to use an invalid operation handle

    Release 后再用 handle,或双重释放

    ✅ Release 前检查 handle.IsValid();之后将 handle 置空