Unity AssetBundle 杂记

跟風遠走 提交于 2019-12-02 15:49:42

部分文档翻译内容

AssetBundle fundamentals

Overview

AssetBundle 系统负责资产动态加载, 不能更新代码.

AssetBundle Layout

包含两部分: 数据段.

头 包括 identifier, compression type and a manifest. Manifest 是一个以 Object 名字作为键值的查找表. 大部分平台上是 平衡搜索树 实现的, Window 和 OSX 的衍生平台(iOS)上是红黑树.

数据段是资产的序列化数据. LZMA 对所有序列化后的资产一起压缩, LZ4 对资产分开独立压缩.

Unity 5.3 之前, 不能对一个 bundle 内的数据独立压缩, 要读去必须对整个 bundle 全部解压.

Loading AssetBundles

AssetBundle.LoadFromMemory(Async)

不推荐

AssetBundle.LoadFromFile(Async)

最高效的加载未压缩或者使用 LZ4 压缩的 bundle.

在桌面, 主机和手机平台上, LoadFromFile 都只会加载 AssetBundle 头, 数据段部分仍然在磁盘上. AssetBundle 里的对象使用方法 AssetBundle.Load 按需加载. 在 Editor 中该接口会把 AssetBundle 都加载到内存中. 如果是在Editor 时 profile, 该接口会导致内存峰值. 在解决这些问题之前应该先在实际设备上再测试.

AssetBundleDownloadHandler

UnityWebRequest 如何处理下载的数据以及减少不必要的内存使用.

LZMA压缩的 bundles 在下载的过程中会被解压并且用 LZ4 压缩后缓存. 可以通过设置 Caching.CompressionEnabled 更改.

当下载完成后, Download Handler 的属性 assetBundle 可以直接被使用, 类似于对下载后的 bundle 调用了一次 AssetBundle.LoadFromFile.

Recommendations

AssetBundle.LoadFromFile 是效率最高的.

对于 需要下载的工程, 5.3 以上的版本, 尽量使用 UnityWebRequest, 5.2 及以后的版本用 WWW.LoadFromCacheOrDownload.

在使用 UnityWebRequestWWW.LoadFromCacheOrDownload 之后, 确保下载代码调用了 Dispose.

Loading Assets From AssetBundles

  1. LoadAsset (LoadAssetAsync)
  2. LoadAllAssets (LoadAllAssetsAsync)
  3. LoadAssetWithSubAssets (LoadAssetWithSubAssetsAsync)

所有同步的接口都比异步的接口要快.

Low-Level loading details

UnityEngine.Object 的加载不是在主线程, 对象的数据是在 worker 线程上读取的. 任何不是线程敏感的部分(脚本, 渲染) 都会放到 worker 线程上处理.

From Unity 5.3 onward, Object loading has been parallelized. Multiple Objects are deserialized, processed and integrated on worker threads. When an Object finishes loading, its Awake callback will be invoked and the Object will become available to the rest of the Unity Engine during the next frame.

从 5.3 到现在, 对象的加载都是并行化的. 多个对象序列化, 处理都是在 worker 线程上. 当对象结束加载后, Awake 回调会被执行, 对象在下一帧能够在主线程使用.

同步到接口 AssetBundle.Load 会暂停主线程知道对象加载完毕, 这里会做分时处理, 通过 Application.backgroundLoadingPriority 处理,

  • ThreadPriority.High: Maximum 50 milliseconds per frame
  • ThreadPriority.Normal: Maximum 10 milliseconds per frame
  • ThreadPriority.BelowNormal: Maximum 4 milliseconds per frame
  • ThreadPriority.Low: Maximum 2 milliseconds per frame

AssetBundle Dependencies

依赖的产生是因为对象之间存在引用关系, 加载 bundle 之前必须加载其依赖的所有 bundle.

AssetBundle Usage Patterns

Managing loading Assets

内存敏感的环境, 要控制好加载的对象数量和大小. bundle 在什么时候 load 和 unload 很重要. 对 bundle 不合时宜的卸载会导致对象在内存中的冗余, 还会招致一些不确定的问题, 比如场景内对象引用的贴图或材质丢失.

最重要的是理解 AssetBundle.Unload(unloadAllLoadedObjects) 接口的行为. 这个接口调用后会卸载 bundle 头. 参数 unloadAllLoadedObjects 会决定是否销毁所有从这个 bundle Instantiate 出来的对象, bundle 同样也会马上被卸载掉, 即使他现在还在场景中被使用.

下面假设材质 M 是中 bundle AB 中 LoadAsset 出来的.

如果调用 AssetBundle.Unload(true), 那么材质 M 会从场景中移除, 销毁并卸载. 如果 AssetBundle.Unload(false), bundle 的头信息会被卸载, 但是 M 仍然在场景中, 这里仅仅打破了材质 M 和 bundle 之间的链接, 如果 bundle 再次被 load, 那该对象又会在场景中产生一个新的的拷贝.

如果再次加载 AB, 头部信息会重新加载(也就会产生一个新的拷贝), 但是 M 不是从这个新加载出来的 AB 中创建出来的, 因此 AB 和旧的未被销毁的材质 M 之间没有任何联系.

如果再次调用 AssetBundle.LoadAsset 会创建一个新的材质 M1, 如下图,

对于大部分项目, 上述不满足需求. 大部分项目用 AssetBundle.Unload(true) 并且保证对象没有冗余. 两种常用的做法,

  1. 切场景或加载时卸载资源
  2. 独立维护对象的引用计数, 当 bundle 内所有对象都没有引用时卸载.

如果必须用 AssetBundle.Unload(false), 那么对于独立的对象可以用一下两个方法卸载,

  1. 清除代码和场景中所有无用对象的引用, 再调用 Resources.UnloadUnusedAssets
  2. 使用 non-additively 的方式加载场景, 会自动销毁所有的对象并且自动调用 Resources.UnloadUnusedAssets

If a project has well-defined points where the user can be made to wait for Objects to load and unload, such as in between game modes or levels, these points should be used to unload as many Objects as necessary and to load new Objects.

The simplest way to do this is to package discrete chunks of a project into scenes, and then build those scenes into AssetBundles, along with all of their dependencies. The application can then enter a "loading" scene, fully unload the AssetBundle containing the old scene, and then load the AssetBundle containing the new scene.

While this is the simplest flow, some projects require more complex AssetBundle management. As every project is different, there is no universal AssetBundle design pattern.
When deciding how to group Objects into AssetBundles, it is generally best to start by bundling Objects into AssetBundles if they must be loaded or updated at the same time. For example, consider a role-playing game. Individual maps and cutscenes can be grouped into AssetBundles by scene, but some Objects will be needed in most scenes. AssetBundles could be built to provide portraits, the in-game UI, and different character models and textures. These latter Objects and Assets could then be grouped into a second set of AssetBundles that are loaded at startup and remain loaded for the lifetime of the app.

Another problem can arise if Unity must reload an Object from its AssetBundle after the AssetBundle has been unloaded. In this case, the reload will fail and the Object will appear in the Unity Editor's hierarchy as a (Missing) Object.

This primarily occurs when Unity loses and regains control over its graphics context, such as when a mobile app is suspended or the user locks their PC. In this case, Unity must re-upload textures and shaders to the GPU. If the source AssetBundle for these assets is unavailable, the application will render Objects in the scene as magenta.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!