Unity3D AssetBundle
文章目录
Unity 资源热更新需要把资源打包为 AssetBundle 形式,老方法需要在程序中自动配置打包策略,然后程序管理资源的加载和卸载,最新的 Addressable Asset System 可以进行自动打包。
AssetBundle 打包策略
统计所有用到的资源路径,比如各种表里的图片配,Prefab,ShaderVariant文件等,并获得所有依赖文件。把所有资源进行归类,meta,dll,cs脚本无需打包。
- 所有.shader文件一个包
- 所有.shadervariants一个包
- 每个soundbank的.bytes文件为一个包
- 每个角色的技能配置为一个包
- 所有新手引导配置为一个包
- 所有lua文件为一个包,并且统计所有lua文件
- 其余的资源每个最小子目录一个包
初始的start场景在打包函数中自动包含进去。遍历所有资源,每个资源加入自己所属的包中。检查所有资源,确保.unity场景文件单独一个包,每个场景都是大量prefab组合的。然后用Unity的AssetBundleBuild构建打包配置项,进行自动打包。打包完成会得到一个资源manifest文件,遍历其中所有资源,构建资源名,文件大小,md5的json信息文件,在资源加载时查找使用。
AssetBundle 加载卸载
通过资源路径 url 发起加载资源请求,统一返回一个加载状态,同时开启一个实际资源加载的 Coroutine,并传入一个资源加载完成的 Callback。资源加载由特定的管理器提供加载函数,所以实际加载中调用的函数会因为资源类型有所不同,比如分为 UI,Lua 和其它资源。在实际加载函数中,分为异步和同步,同步模式直接加载,异步模式会把资源添加到加载队列中逐个加载。加载时,根据资源 url 和之前打包的信息,查找到资源所在的 bundlePath,获得 bundlePath 所有的依赖 bundle 资源,逐个加载这些资源直到完成。所有依赖的 bundle 加载完成时,检查当前 url 资源是否还需要,即每个资源 url 加载时都会创建一个全局唯一的包含 refCount 的 loader 对象,加载时增加,释放时减小,通过判断 refCount == 0 可以知道资源是否还需要。如果不需要了,则把所有依赖 bundle 进行 Release。如果确实需要加载,同步的调用 assetBundle.LoadAsset(assetName) 加载,异步的调用 assetBundle.LoadAssetAsync(assetName)进行异步加载,加载完成后进行回调通知。
AssetBundle 更新
- 加载原始包中的config配置文件,Android 在 jar:file//{Application.dataPath}!/assets/config.json,从中获取版本号,服务器域名等
- 确定一个资源更新目录updPath,一般在 {Application.persistentDataPath}/upd/
- 从updPath中获取config.json,如果没有则创建upd目录,并把原始包中的config复制过来。如果updPath中已存在config文件,则直接读取配置
- 以完整包为例,开始执行补丁检查。如果包内版本高于updPath中的补丁版本,一般是直接安装了新版本覆盖造成的,需要强制初始化,即把原始包内配置复制到updPath中,否则开始检查补丁更新
- 根据config配置得到一个更新检查地址updateUrl,参数为渠道号,版本,一个根据版本信息和密钥得到的md5。更新服务器会返回需要更新的所有资源列表信息
- 如果是最新版,直接开始游戏。如果是大版本强制更新,则弹出下载提示框。如果是补丁更新,则把要下载的所有文件加入列表中,弹出提示让玩家确定下载这些文件
- 确定要更新补丁后,开一个线程专门下载补丁文件。下载文件A时,先检查是否已经存在。然后检查临时文件A.temp是否存在,如果存在则得到文件上次下载位置,否则新建临时文件
- 用HttpWebRequest下载时可以指定下载位置,循环从Stream中读数据到临时文件FileStream,最后下载完成。检查文件md5,不对则重新下载。
- 全部下载完毕后,文件名由A.temp变为A,然后解压缩所有下载的文件
- 解压完毕后,继续进行版本更新检查,没有更新了就进入游戏
AssetBundle 手动打包测试
使用 Unity5.6 创建 AssetBundle 依然要考虑依赖关系,避免资源重复的问题仍然比较麻烦,本文并没有解决这个问题,只是初步学习创建和加载,在 Android 平台测试通过。
首先是 AssetBundle 的创建问题,主要思路是资源不要放在 Resources 目录下,而是在 Assets 下创建一个固定的目录,然后给这个目录下所有资源自动设置 AssetBundle 的名字,然后自动打包即可。比如这个目录叫做 BundleResources,然后把要动态加载的资源都放在这个目录下,资源的名字由路径和自身的名字组成,那些不直接加载的资源放在其他目录。之前一直考虑哪些资源该单独拿出来打包,即哪些共同的资源应该放入打包目录,这个问题没找到什么好办法,觉得是共享的就放入打包目录即可。目前的方法是,可以做一个编辑器工具来检查出共享的文件,然后再放到这个目录。
然后是加载,我这里只实现了同步加载的方式。主要思路是,游戏开始先加载总的 Manifest 文件,这个文件里有所有的资源依赖关系。加载某个 AssetBundle 时,先通过总的 Manifest 文件来查找依赖的其他 AssetBundle,然后递归加载所有需要的 AssetBundle。最后,从目标 AssetBundle 中加载所需的资源,然后卸载 AssetBundle。如果在编辑器下,会直接调用编辑器代码来加载资源。
WWW和LoadFromFile都可以加载,但是之前的StreamingAssets中文件只能用WWW来读取,之后的Unity做了改进,用LoadFromFile也可以加载,并且LoadFromFile加载LZ4格式的AssetBundle只加载文件头信息,使用的内存更少。
第一次进游戏时,会先从 StreamingAssets 目录解压 AssetBundle 压缩文件到 persistentDataPath 目录,在打包时会自动创建该压缩文件。压缩和解压缩使用了一个插件代码,目前该插件只支持 IL2cpp 模式的打包,mono 打包运行时会无法解压,所以打包 Android 需要先设置 IL2cpp。
这里只粘贴部分加载代码,其他的可以看Unity工程。
|  |  | 
加载完成后,还要考虑何时卸载,避免一些内存占用问题。注意,如果用Unload(false)之后再次加载该对象,内存中容易出现两份相同的资源。或者加载后,选择在何时的时候用 Unload(true) 来卸载。
文章作者 huijian142857
上次更新 2020-09-02