首先放源码链接,addressable source code.
Addressable(以下简称AA),是Unity最新推出的资源管理方案,它与之前的AssetBundle(以下简称AB)不是替代关系, 而是建立在AssetBundle提供的基础功能上面的一套更加封装和易用的系统。
在之前使用AB的时候,由于AB提供的接口非常的基础和底层,这虽然有很高的自由度,但更常见的情况就是资源系统的 的每一处细节都要开发人员自己去实现,事无巨细,非常麻烦,bug也多。这也导致涌现出了很多资源管理框架。
后来Unity也推出了Addressable,在写下这篇文章的时候,最新版本是1.20.5,大概每两个月左右一个版本, 还处于活跃开发阶段,会修复很多问题或者增加一些功能。
按照Unity的描述 AA可以”Simplify your content management with Addressables”。实际上也是这样, AA为开发者封装了很多细节,包括依赖管理,引用计数,打包,下载,更新等等。如果没有很特殊需求, 按照AA的教程,很快就能搭起一套可用的完全体资源管理系统。
AA可以在PackageManager中直接安装。建议可以直接转最新的版本,因为所谓的稳定版本其实也不是那么稳定, 我曾在项目中使用稳定版本,结果一个API调用会在真机上产生竞态条件卡死,导致不得不使用魔改过的版本, 六个月后的新版本中Unity的团队才修复了这个问题。
本文不是一个教程,如果从0开始学习可以移步官方文档
下面是几个在实际使用中遇到的坑点。
      Addressables.CheckForCatalogUpdates()  
          private ResourceLocationMap GetCatalogMap(string catalogPath) {
          try {
              var txt = File.ReadAllText(catalogPath);
              // 构造最新资源的Catalog的实例
              var h = JsonUtility.FromJson<ContentCatalogData>(txt);
              var locator = h.CreateLocator();
              return locator;
          } catch (Exception e) {
              Debug.LogError(e.Message);
              return null;
          }
      }
      private long CalcDownLoadSize() {
          var locator = GetCatalogMap(pathToRemoteCatalogFile);
          if (locator == null) {
              return 0;
          }
          long size = 0;
          foreach (var kv in locator.Locations) {
              var key = kv.Key as string;
              if (key == null || !key.EndsWith(".bundle")) {
                  continue;
              }
              foreach (var location in kv.Value) {
                  var sizeData = location.Data as ILocationSizeData;
                  if (sizeData != null) {
                      // 与本地的ResourceManager对比下载量
                      size += sizeData.ComputeSize(location, Addressables.ResourceManager);
                  }
              }
          }
          return size;
      }
    Addressables.ResourceManager.InternalIdTransformFunc
            private string Init() {
          Addressables.ResourceManager.InternalIdTransformFunc = TransFunc;
      }
      private string TransFunc(IResourceLocation location) {
          var path = location.InternalId;
          if (location.Data is AssetBundleRequestOptions) {
              // 尝试读取本地资源
              ...
              return path;
          }
          // 没有读取到本地资源,返回原始地址
          return location.InternalId; 
      }
  
  // 手动载入catalog
  public async Task<bool> InitCatalogAsync() {
      var catalogPath = GetLocalCatalogPath();
      foreach (var locator in Addressables.ResourceLocators) {
          // 如果已经装载,不用再重新载入
          if (locator.LocatorId == catalogPath) {
              return true;
          }
      }
      if (mLoadOp.IsValid()) {
          return true;
      }
      var loadOp = Addressables.LoadContentCatalogAsync(catalogPath, false);
      mLoadOp = loadOp;
      await loadOp.Task;
      if (loadOp.Status != AsyncOperationStatus.Succeeded) {
          return false;
      }
      return true;
  }
  // 要更新手动载入的catalog,必须先卸载已经载入的catalog
  public void UnloadCatalog() {
      if (mLoadOp.IsDone && mLoadOp.IsValid()) {
          Addressables.Release(mLoadOp);
      }
  }
  分组不变,只能在已经存在的分组里增删资源,如果每次都新建分组,即使同名并且资源相同 那么仍然会导致每次打包出的资源guid变化,因为AA分组在每次新建时是取了一个新的分组guid, 后面打包的资源的会依据这个guid做一次hash得到资源guid。
schema不变,每个分组的schema只能用已经存在的,如果schema改变,那么guid也会改变。 并且如果直接使用 AddressableAssetGroup.RemoveSchema的话,会造成schema的资源被删除。 所以如果要清除schema的话,AddressableAssetGroup.Schemas.Clear,这样可以保证不会删除资源。
要将生成好的分组,schema都提交到版本管理,这样每次切到某个版本做资源构建的时候才 不会发生guid变化,