前言
为了实习,紧急学习一下AB包的相关知识。魂类项目在做了。
学习视频链接: 【唐老狮】Unity热更新之AssetBundle,文章中的部分图片来自该视频教程。
AB包的概念及作用
做游戏需要用到资源,AB包就是Unity针对不同平台(安卓,IOS,PC)的统一资源压缩包。AB包能存储除了C#脚本之外的几乎所有Unity资产(模型,贴图,预制体,材质,音效)。
AB包相对于Resources文件夹这一套资源管理方案,拥有更好的资源管理及资源读取办法,能够压缩资源并且压缩方式可以自定义,减少初始包的大小(可以理解为下载COD时候能分关卡下载),以及拥有对资源进行热更新的能力,可以说Unity进行热更新的基础就是使用AB包。
注:热更新指软件能够直接将新的资源以及代码注入已经安装好的程序中来达到更新的效果。基本规则如下图所示。
官方打包工具及AB包的生成
AB包可以通过对Unity编辑器的开发来实现自定义打包工具,也可以使用Unity官方提供的打包工具Asset Bundle Browser。
据说现在使用Unity开发项目的公司已经都把打包工具开发好了。因为早期的时候,没有官方打包工具。
对于官方打包工具,在Package Manager里面可以下载并安装。
如果是老版本Unity(2017),可以到Github中找到Unity官方发布的Asset Bundle包。
从Window菜单栏中打开AssetBundle Browser,我们可以看到三个页签,配置(Configure)页签,打包(Build)页签,和检查(Inspect页签)。
创建或导入一些资源,选中资源(可以批量选择),在Inspector面板的最下面会看到有一个AssetBundle。点击第一个按钮选项(第二个按钮选项是拓展名,暂时不用管),弹出的菜单栏中点击new即可将资源放到到新建的AB包配置方案中。
AB包中的资源可以在AssetBundle Browser的Configure中看到。
注意,AB包中没办法放C#脚本,而如果说要实现热更新的话需要用到lua语言。
在设置好AB包的资源配置之后,在Build页面中设置AB包的打包方案,包括
- Build Target:AB包的适用目标平台
- Output Path:输出的位置
- Clear Folders:每次打包时是否要清空输出文件夹
- Copy to StreamingAssets:是否将AB包复制到StreamingAssets文件夹,StreamingAssets文件夹会随着项目的Build一起Build,并且不会和Resources一样进行压缩加密。
- Compression:高级设置(Advanced Settings)中的压缩方式
- Exclude Type Information:设置在资源包中不包含资源的类型信息
- Force Rebuild:重新打包时重新构造包(与Clear Folders不同,不会删除不再存在于Configure中的包)
- Ignore Type Tree Change:忽略类型树的更改
- Append Hash:将文件哈希值附加到包名上(没啥用)
- Strict Mode:严格模式,如果打包时报错,则打包直接失败。
- Dry Run Build:运行时构建
每一个AB包打包出来都只对应一个平台,如果需要换平台则要重新打包。
对于压缩选项,可以选择不压缩,LZMA和LZ4,其中LZMA是一种能将文件尽可能压缩,但是后期解压速度较慢的压缩方法,而且由于这个方法一次性会将所有的资源解压出来,所以内存占用也会更大,相比之下LZ4虽然解压率比不上,但是却是用什么资源就解压什么资源,内存占用量更小。
设置完成以后就可以点击Build生成AB包文件,生成的AB包不会随着项目的Build包含在内,但如果勾选了Copy to StreamingAssets还会在项目Assets文件夹中创建一个StreamingAssets文件并将刚刚生成的AB包拷贝到里面。
可以看到,我们生成的AB包除了包本身,还有一个同名manifest(清单?)文件,以及一个PC包,PC包是这个AB包的主包,存储了包与包之间的依赖关系信息。
最后一个是Inspect页,可以用来检查AB包的相关信息。
AB包资源加载
当我们生成了我们的AB包之后,我们就可以在脚本中实现包中资源的加载。

要加载包中的资源,我们首先要加载相应的AB包。这里展示的是从文件中加载AB包(同步加载),从我们拷贝到的StreamingAssets文件夹中加载。
// 1.加载AB包
AssetBundle ab =
AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + "prefabs");
加载包以后,就可以加载包中的资源了。注意:不通过泛型只使用名字加载时默认加载类型为Object,可能遇到相同名字不同类型的对象,所以建议使用泛型或指定类型。
// 2.加载AB包中的资源
GameObject cube = ab.LoadAsset<GameObject>("Cube");
可以将刚刚加载到的对象实例化到场景中。
Instantiate(cube);
注意事项:
- 同一个AB包不能被加载两次,Ab包也不存在两个同名AB包,同名AB包肯定是同一个AB包。
AssetBundle ab = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + "prefabs");
AssetBundle ab2 = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + "prefabs");
接下来展示AB包的异步加载,异步加载会在资源加载的同时依然运行帧,异步加载使用协程来实现。
public Image img;
// Start is called before the first frame update
void Start()
{
// 异步加载
StartCoroutine(LoadABRes("texture", "2019-02-05 (1)"));
}
IEnumerator LoadABRes(string ABName, string resName)
{
// 加载AB包
AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/" + ABName);
// 等异步加载加载完成
yield return abcr;
// 加载资源
AssetBundleRequest abr = abcr.assetBundle.LoadAssetAsync<Sprite>(resName);
yield return abr;
img.sprite = abr.asset as Sprite;
}
卸载AB包
这里说的卸载不是指删除电脑硬盘里的文件,而是指卸载脚本加载过的AB包。
下面展示的是卸载所有的AB包。
// 卸载所有加载的AB包 如果参数为true会把通过AB包加载的资源也卸载了
AssetBundle.UnloadAllAssetBundles(true);
对于这个方法的参数,一般都使用false。
如果要卸载的是单个AB包,可通过单个AB包调用UnLoad方法。其中的参数和UnloadAllAssetBundles的参数功能是一样的。
ab.Unload(true);
AB包依赖与主包
对于包中资源用到的其它资源(比如说某个模型使用了某个材质),即使这个其它资源没有被我们手动放到包中,AB包也会把它自动包括进去。
但是如果某个包中的资源使用到了其它包中的资源,其它包中的资源不会自动包括到这个包中,而且如果只加载这个包,那么其它包中的资源也无法被使用到。
此时,要不我们将其它包中的资源再包括到这个包中,要不就要加载这个包依赖的AB包。
AssetBundle ab = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + "prefabs");
AssetBundle ab2 = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + "materials");
其实,在正常开发中,我们不会自己去记录某个包依赖那些包,而是通过主包获取依赖信息。
主包是和我们的输出路径文件夹名同名的包。
// 加载主包
AssetBundle abMain = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + "PC");
// 加载主包中的固定文件 AssetBundleManifest
AssetBundleManifest abm = abMain.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
// 从固定文件中得到依赖信息
string[] strs = abm.GetAllDependencies("prefabs");
通过以上代码,可以得到主包的固定文件中所包含的依赖信息。
作为测试我们将prefabs的依赖文件名打印出来。
foreach (var name in strs)
{
AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + name);
}
要加载的话,一个foreach就能将所有的依赖包加载出来。
这样加载的一个缺点就是,这样并不会区分具体是哪一个资源依赖哪一些包,而是会加载所有资源依赖的所有包。