近期项目正好在处理AB包的问题,趁这个机会,自己重新学习并梳理一下AB包相关知识,供以后回顾。
1、什么是AssetBundle?
AsetBundle是unity提供的一种资源打包方式,可以通过LZMA和LZ4压缩方式将模型、贴图、预设体、音频文件甚至整个场景压缩成压缩包(当然也可以不压缩,后面会提及)。至于它的后缀名是什么完全不用在意,可以自己定。AssetBundle(后面简称AB包)自身会保存依赖关系,如AB包A中的材质可以引用AB包B中的纹理。因为AB包通过LZMA或LZ4压缩算法来压缩的,所以可以减少包的大小,使其能更快的进行网络传输。而且如果我们把一些下载内容放在AB包里,那么就可以减少安装包的大小。
AB包是Unity动态下载资源的通用解。
2、unity提供的几种主要的资源加载方式对比(AssetBundle的优点)
(1)、Resources//内嵌资源,使用Resources.Load可以加载任意资源,但是不能动态修改,而且需要把所有资源全部打入安装包。
(2)、StreamingAssets//随包资源,使用IO或者WWW.Load,WWW.Load可以加载任意资源,而IO仅限bytes和text,该文件夹下所有资源也全部打入安装包。
(3)、WWW从网络下载并加载。
(4)、WWW从网络加载AB包。
其中(1)(2)并不具备热更新的效果且两个文件下所有资源都会被打入安装包,而(3)(4)都是从网络加载,他们之间的区别就是(3)不具备缓存功能,容易导致让用户反复浪费流量,不可取。而(4)本来就是unity给我们准备的关于热更新的解决方案,AB包给我们提供了一个版本号来做缓存比对,可以有效解决资源热更新。
3、打包
网上关于如何打包介绍很多,这里不多做赘述,最核心的也就一个API:
BuildPipeline.BuildAssetBundles ("AssetBundles", BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows64);
第一个参数是打包路径。
第二个参数是build的选项,常用的有三种:(1)BuildAssetBundleOptions.None:默认使用LZMA压缩算法进行压缩,这个压缩算法压缩出来的包小,当然相应的解压的时间会更长。在我们使用之前要整体解压,一但被解压之后就会用LZ4算法重新压缩。(2)BuildAssetBundleOptions.ChunkBasedCompression:使用LZ4压缩算法进行压缩,压缩出来的包会比用LZMA压缩的要大一些,解压快,解压速度可以和不压缩相媲美,而且可以指定资源解压。(3)BuildAssetBundleOptions.UncompressedAssetBundle:不压缩,包最大,解压速度最快。
第三个参数就是选择build出来的AB包所使用的的平台,根据所选平台的不同,AB包只能在该平台使用。因此IOS和Android需要分开打包。
打包自然是根据AssetBundleName来分组的,而打包整个过程最让人头疼的也就莫过于该如何分组了,要是没分好很容易出现重复打包,而如何才能给用户最小的资源更新量是永远没有最优解的,下面列举一些常用打包分组的小策略:
(1)按照资源类型分组,如:声音资源一起打包,材质资源一起打包,shader一起打包,prefab一起打包等。
(2)按照逻辑实体分组,如:一个UI界面或者所有UI界面一起打包,一个角色或者所有角色一起打包,所有场景共享的东西一起打包等。
(3)按照使用打包,也就是同时加载的资源放在一个包里,如:同一时间内用到的资源一起打包,同一场景一起打包,同一关卡一起打包。
(4)把经常更新的和不经常更新的分开打包。
打包过程中还有一个比较特殊的打包方式就是依赖打包。依赖打包简单地说就是把多个资源所依赖的同一资源单独打包,unity会自动判断依赖关系,但是并不会自动加载具有依赖关系的资源包,所以我们在使用资源之前应先加载依赖资源的包,避免出现材质丢失的情况。而资源之间的依赖关系则可以在我们打包后生成的manifest中查看。另外,我们在打包的时候不建议把AB包拆的太碎,你想如果加载一个模型需要加载十几次依赖资源,那么加载过程中就需要发送这么多的WWW或者http请求,还是有点危险的。关于依赖资源包的加载直接上代码:
AssetBundle manifestAB = AssetBundle.LoadFromFile ("AssetBundles/AssetBundles"); AssetBundleManifest manifest = manifestAB.LoadAsset<AssetBundleManifest> ("AssetBundleManiFest"); // 加载依赖包 string[] strs = manifest.GetAllDependencies ("cube.unity3d"); foreach (string name in strs) { Debug.Log (name); AssetBundle.LoadFromFile ("Assetbundles/" + name); }
4、manifest文件与版本号的简单介绍
既然提到manifest文件,那这里就做一个简单介绍。manifest是unity打AB包时自动生成的一个清单,里面最重要的三个信息是CRC校验码、Assets、Dependencies。其中CRC校验码是用来校验文件是否完整,Assets记录的是资源路径,Dependencies指定的则是该资源所依赖的文件。
而在做资源热更新的过程中版本号是必不可少的,资源版本号是自己设定的,可以自己写版本号的生成方法,也可以用MD5值作为版本号,我们需要根据版本号的对比来确定资源包是否需要更新。资源热更新可分为如下五个步骤:
(1)通过请求服务器获取到服务器的MD5码配置文件;
(2)获取本地的MD5码配置文件;
(3)逐个对比每个文件的MD5码;
(4)统计MD5码不一致的文件列表;
(5)从服务器下载更新文件列表中的文件。
那么问题来了,该如何生成相应的MD5码呢?生成步骤如下:
(1)读取文件流;
(2)读取文件流中的字节数据;
(3)通过MD5接口生成MD5码;
(4)将步骤3获得的Hash字节数组转换成字符串数组。
关键代码如下:
public static string getFileHash(string filePath) { try { FileStream fs = new FileStream(filePath, FileMode.Open); int len = (int)fs.Length; byte[] data = new byte[len]; fs.Read(data, 0, len); fs.Close(); MD5 md5 = new MD5CryptoServiceProvider(); byte[] result = md5.ComputeHash(data); string fileMD5 = ""; foreach (byte b in result) { fileMD5 += Convert.ToString(b, 16); } return fileMD5; } catch (FileNotFoundException e) { Console.WriteLine(e.Message); return ""; } }
调用的时候通过填写制定文件的完整目录,即可获得对应文件的MD5码:
string md5 = getFileHash("E:\\MyPro\\cubetest.unity3d");
将所有文件的MD5码都写到一个文本.txt文件中,此即为热更新文件的配置文件,每次上传新版本到服务器时,将这份文件也存放到服务器中。
5、AB包的加载
关于AB包的加载,unity官方提供了四个API,分别是:
(1)AssetBundle.LoadFromMemoryAsync:从内存中异步加载
(2)AssetBundle.LoadFromFile:本地加载
(3)WWW.LoadFromCacheOrDownLoad:即可从远程服务器下载AB包,又可以从本地加载。从远程加载AB包后自动缓存
(4)UnityWebRequest:5.3版本后新出的API,unity新增的一种网络请求方式,目的就是为了取代(3)中加载方式,因为WWW在IOS上加载资源偶尔会出现卡死现象。
因为加载并没有什么特别需要注意的地方,而且网上资料很多,这里只贴第四种加载方式的代码:
IEnumerator GetText() { UnityWebRequest request = UnityWebRequest.Get("http://example.com"); // // UnityWebRequest request = new UnityWebRequest("http://example.com"); // // request.method = UnityWebRequest.kHttpVerbGET; // yield return request.Send(); // if (request.isError) { Debug.Log(request.error); } else { if (request.responseCode == 200) { // string text = request.downloadHandler.text; // byte [] results = request.downloadHandler.data; } } }