1.AB包压缩的三种方式
①No Compression:不压缩包,包体最大,但是加载是最快的
②LZMA:包体压缩到最小,但是解压速度最慢,加载的时候会加载整个包
③LZ4:包体压缩中等,速度较快,速度跟No Compression差不多(推荐使用)
下面测试一下三者的加载速度
【图1】
图1中的model为41.6MB,依照3个选项分别打成3个包,如图2,图3,图4,图5
【图2】
【图3】
【图4】
【图5】
下面对每个包加载2次,Time单位为毫秒
【图6】
图6为No Compression,加载速度是最快的
【图7】
图7为LZMA,加载速度最慢
【图8】
图8为LZ4,加载速度较快,跟NoCompression速度差不多
注意:AB包只能加载一次,重复加载会报错。要使用Unload卸载加载过的包,如果未加载时调用Unload不会报错
//卸载全部包,bool值为是否清除加载过的资源,true表示清除,但是游戏对象不会被清除,所以会剩下一堆空物体,或者造成材质丢失
AssetBundle.UnloadAllAssetBundles(true);
AssetBundle bundle = AssetBundle.LoadFromFile(path);
bundle.Unload(true); //卸载单个包,参数同上
2.AB包加载依赖顺序
【图9】
图9中,models包依赖了texture包,在加载的时候,我们需要优先并且手动加载依赖包,才能使得资源加载正确。
加载依赖包的操作:先加载主包,加载包里的依赖文件,通过依赖文件查询要加载包的依赖关系。
if (Input.GetKeyDown(KeyCode.K))
{
Debug.Log("使用LZ4包");
//加载models包,并且实例化包里的unitychan(GameObject类型)
AssetBundle b = Load(Application.dataPath + "/../AssetBundles/PCBuild1/models", "unitychan");
}
if(Input.GetKeyDown(KeyCode.J))
{
Debug.Log("加载依赖包");
//加载主包
AssetBundle main = Load(Application.dataPath + "/../AssetBundles/PCBuild1/PCBuild1");
//加载描述文件
AssetBundleManifest manifest = main.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
//获取models包的所有依赖关系
string[] depend = manifest.GetAllDependencies("models");
foreach (string str in depend)
{
//加载models的依赖包
Debug.Log("depend==" + str);
Load(Application.dataPath + "/../AssetBundles/PCBuild1/" + str);
}
}
上述代码中,如果先按下J加载依赖包,再按下K加载models包,则模型正常,如图10
【图10】
如果先加载了models包,后面才加载依赖包,则贴图丢失,如图11
【图11】
有一点注意的是,我一开始想通过models包直接加载描述文件,获取依赖包,但是发现加载不了,可能只有主包才能获取描述文件
Debug.Log("使用LZ4包");
AssetBundle b = Load(Application.dataPath + "/../AssetBundles/PCBuild1/models", "unitychan");
AssetBundleManifest manifest = b.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
Debug.Log("manifest==" + manifest); //manifest为null
3.AB包资源的自动复制
下面用一个简单的例子解释下依赖包的产生
【图12】
【图13】
如图12,图13,Cube有一个blue材质,Sphere有一个red材质。把empty对象打成一个名为empty的包,blue材质打成名为blue_material的包,red材质不打包。通过打包工具就可以看到一些blue和red不一样的地方了
【图14】
图14,我们发现empty包里有一个red材质,没有blue材质,原因如下:
red材质没有指定包名,所以unity会认为你的red是不需要打依赖包的,自动复制这个资源到empty包中;
blue材质手动打了blue_material包,则不会复制资源到empty包中。
【图15】
如图15,我们只加载empty包,可以看到blue材质丢失了,而red材质是正常加载的,这就说明red已经被自动复制进empty包中了。
4.为什么要打依赖包,什么时候要打依赖包?
还是red材质未设置包名的情况:我们假设不止empty包依赖了材质red,还有其他很多包都依赖了材质red,那么所有依赖red材质的包都会复制一份red材质到他们自己的包里去。这就使得资源浪费,同个资源不应该重复出现在其他地方。
要解决这个办法就是跟我们上面说的一样,将red材质单独打一份包,这样就只会存在一份资源,不会浪费。
结论:依赖包是为了避免资源的浪费;当存在多个包引用同一个资源时,这个资源需要单独打依赖包。
5.加载依赖包的正确方式
错误操作:
private AssetBundle Load(string path, string assetName = "")
{
Debug.Log("开始加载资源");
AssetBundle bundle = AssetBundle.LoadFromFile(path);
//加载依赖包,这里的manifest已经提前加载了
string bundleName = path.Substring(path.LastIndexOf('/') + 1);
string[] depends = manifest.GetAllDependencies(bundleName);
foreach(string depend in depends)
{
Debug.Log("自动加载依赖包==" + depend);
AssetBundle.LoadFromFile(Application.dataPath + "/../AssetBundles/PCBuild1/" + depend);
}
if(!string.IsNullOrEmpty(assetName))
{
GameObject instance = bundle.LoadAsset<GameObject>(assetName);
Instantiate(instance);
}
return bundle;
}
这个加载函数会查询要加载的包的依赖,如果并且将他依赖的包都加载一遍(注意这里如果是同时加载的话,那么先加载依赖还是先加载包,都没有问题,不会造成资源引用丢失)。这段代码是有一个问题的,如果有2个包都依赖了同个包,那么这个依赖包会被重复加载。
【图16】
图16中,有empty包和empty2包,这两个包都依赖了blue_material包,所以他加载了2次blue_material包。解决办法就是在加载依赖包前先检查一遍,如下代码。
正确操作:
private AssetBundle Load(string path, string assetName = "")
{
Debug.Log("开始加载资源--" + path);
AssetBundle bundle = AssetBundle.LoadFromFile(path);
//加载依赖包,这里的manifest已经提前加载了
string bundleName = path.Substring(path.LastIndexOf('/') + 1);
string[] depends = manifest.GetAllDependencies(bundleName);
foreach(string depend in depends)
{
//新增代码
bool isLoad = false;
IEnumerable<AssetBundle> bundlesEnumerable = AssetBundle.GetAllLoadedAssetBundles();
foreach(AssetBundle loaded in bundlesEnumerable)
{
if(string.Equals(loaded.name, depend))
{
Debug.Log("已经加载过--" + depend);
isLoad = true;
}
}
if(isLoad)
{
continue;
}
Debug.Log("自动加载依赖包==" + depend);
AssetBundle.LoadFromFile(Application.dataPath + "/../AssetBundles/PCBuild1/" + depend);
}
if(!string.IsNullOrEmpty(assetName))
{
GameObject instance = bundle.LoadAsset<GameObject>(assetName);
Instantiate(instance);
}
return bundle;
}
图【17】
但是这种办法会嵌套遍历,所以更好的办法是用字典把加载过的包名存起来,直接查找字典就行。