【Unity优化】资源管理系列03:AssetBundle 基本原理
定义:将一个或多个文件,按照一种归档格式,存在一个文件中,Unity可以索引并序列化其中的文件
操作:加载/卸载AB自身、加载卸载AB中某个资源
作用:在游戏安装后,继续分发和更新非代码资源
1)减小游戏包体
2)减小运行时内存压力
3)针对不同设备/平台,加载不同资源
AB结构
1)数据头:identifier、压缩类型、manifest
manifest:查找表,key是对象名,value中包含一个字节索引,定位数据段中对象位置
2)数据段:资源系列化后的原始数据
三种压缩类型
1)LZMA:所有资源的序列化数据,被压缩进同一个字节数组
2)LZ4:每个资源的序列化数据,单独被压缩为字节数据
3)不压缩:原始字节数据
压缩类型对比
1)LZMA:
优点:原始AB小
缺点:想要加载其中一个资源,需要解压整个AB;运行时占用内存大
2)LZ4(推荐):
优点:想要加载其中一个资源,只需要解压相关数据块;运行时占用内存小
缺点:原始AB大
加载AB
1)AssetBundle.LoadFromMemory
说明:不推荐,两倍AB内存占用,三倍资源内存占用
2)AssetBundle.LoadFromFile
说明:推荐,只加载数据头,之后按需加载资源
补充:在Editor模式下,异步方法会加载整个AB,会出现内存峰值;真机中没有
3)UnityWebRequest 的 DownloadHandlerAssetBundle
① 可以使用 UnityWebRequest.GetAssetBundle,也可使用 DownloadHandler
② DownloadHandler 使用工作线程,流式下载,存入固定大小buffer,然后传给临时存储或 AB cache。这些操作都有原生代码实现,不会产生堆分配。且不会额外保存下载字节数据的拷贝,减少内存占用。
③ 如果AB是LZMA压缩格式,在下载的时候会被解压缩,并以LZ4格式缓存。
④ AB下载完成后,通过 DownloadHandler 的 assetBundle 获取AB。
⑤ 如果AB之前已被加载,且在缓存中,则会立即返回,此时和 LoadFromFile 功能类似
4)WWW.LoadFromCacheOrDownload
说明:Unity5.6之前使用,已废弃
5)建议
① LoadFromFile,建议在任何可用的场景使用,无论是加载速度、硬盘占用、运行时内存占用,该API都是最高效的。
② UnityWebRequest,需要下载AB时使用;记得调用 Dispose 或 Using。
③ 如果自研下载器,需要适配 LoadFromFile 方法。
加载AB中资源
1)三种API
① LoadAsset(Async)
② LoadAllAssets(Async)
③ LoadAssetWithSubAssets(Async)
2)同步与异步对比
① 同步方法:至少比异步快一帧;缺点是在资源加载完毕前,会阻塞主线程
② 异步方法:可以在一帧内加载多个资源,不阻塞主线程
3)LoadAllAssets(Async)
适用场景:从一个AB中加载大部分资源,且这些资源相互独立。
优点:比多次调用 LoadAsset 快
AB策略:如果一个AB中,超过66%的资源需要同时加载,则建议将这部分分离出来单独打包
4)LoadAssetWithSubAssets(Async)
适用场景:当需要加载一个由多个小资源组合而成的大资源时
举例:FBX模型及其动画、精灵图集及其精灵
5)资源加载的一些细节
① 从存储中加载对象数据,发生在工作线程上。
② 多个工作线程,使得多个对象可以同时被反序列化、处理、整合。
③ 当一个对象被加载完毕,Awake方法被调用,从下一帧开始,该对象可被引擎使用。
④ 对象加载在一帧中的时间不超过 time-slice,该值由 ThreadPriority 决定。
⑤ 发起异步调用,和对象被引擎可用,这两步间至少需要一帧,因此相同条件下,同步方法要比异步方法快一帧。
6)AB依赖
① AB间的相互依赖可以通过 AssetDatabase 和 AssetBundleManifest 获取
② 在加载一个对象前,要先加载它依赖的所有AB。Unity不会帮忙自动加载,要自己实现
7)AssetBundle manifests
① 被包含在一个以父文件夹命名的AB中
② 包含了AB中对象之间的引用信息
③ manifest AB 和其他 AB 一样,可以被加载、缓存、卸载
④ 使用 GetAllAssetBundles 方法获取所有包含 AB 列表
⑤ 使用 GetAllDependencies 方法返回该AB的所有依赖
⑥ 使用 GetDirectDependencies 方法返回该AB的直接依赖
⑦ 上面两个方法都会分配 string 数组,不要在性能敏感处使用
8)建议
① 在性能敏感的代码之前,尽可能预加载需要的资源