部分文档翻译内容
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
.
在使用 UnityWebRequest
或 WWW.LoadFromCacheOrDownload
之后, 确保下载代码调用了 Dispose
.
Loading Assets From AssetBundles
- LoadAsset (LoadAssetAsync)
- LoadAllAssets (LoadAllAssetsAsync)
- 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)
并且保证对象没有冗余. 两种常用的做法,
- 切场景或加载时卸载资源
- 独立维护对象的引用计数, 当 bundle 内所有对象都没有引用时卸载.
如果必须用 AssetBundle.Unload(false)
, 那么对于独立的对象可以用一下两个方法卸载,
- 清除代码和场景中所有无用对象的引用, 再调用
Resources.UnloadUnusedAssets
- 使用 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.