赞
踩
AssetBundle可以理解为一种Unity特殊的压缩包。它的主要作用是,压缩大小,保证资源依赖完整。
以上两个特性延伸出来两个主要的应用场景和使用目的。热更新(因为体积更小,又能简单保证资源的完整性),性能(内存)优化。
简述流程为
注: 这段的介绍最初来自Siki学院的课件,但是深究之后发现是Unity
Manual的翻译版本,若要追溯请看UnityManual2017。
关于BuildPipeline.BuildAssetBundles(string outputPath, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform);的第二个参数,打包选项设置,简述如下。
BuildAssetBundleOptions.None:使用LZMA算法进行整体压缩,压缩的包更小,但是加载时间更长。而且使用前要对Bundle进行整体解压,并且会整个Bundle加载到内存中去生成内存镜像,解压后的Bundle会使用LZ4压缩。 Unity官方推荐的使用场景是,如果对资源下载大小的要求比较高,可以使用该压缩格式,因为下载后会自动解压成LZ4格式存放。
LZMA是流压缩(stream-based)。流压缩(LZMA)在处理整个数据块时使用同一个字典,它提供了最大可能的压缩率但只支持顺序读取。
BuildAssetBundleOptions.UncompressedAssetBundle:不压缩包体大小,读取自然更快。原汁原味,即快又大,但是这样还不如用resources呢,更快,又不一定更大。
BuildAssetBundleOptions.ChunkBasedCompression:最推荐使用得压缩格式。该选项会使用分段压缩方式,压缩算法为LZ4压缩,虽然压缩率没有LZMA高,但是我们可以在加载指定资源解压而不用解压全部资源。况且,LZMA也会解压成LZ4再进行使用呀。
LZ4是块压缩(chunk-based),块压缩(LZ4)指的是原始数据被分成大小相同的子块并单独压缩。如果你想要实时解压/随机读取开销小,则应该使用这种。
BuildAssetBundleOptions.CollectDependencies:使用该选项时,打包会包含所有依赖资源。也就是我们常说的依赖打包。同样也非常推荐使用。
附全部压缩选项介绍:https://blog.csdn.net/AnYuanLzh/article/details/81485762
另外要注意,打包选项是按位与操作,也就是多个选项可以合并使用,一般情况下,大家最推荐的是ChunkBasedCompression|CollectDependencies的组合使用。既能进行大小压缩,又能获得完整引用的资源。
所谓依赖打包,即Bundle打包时选择CollectDependencies时,会将Bundle依赖的所有资源打包到Bundle中,以供后续正确的加载全部需要的资源。
依赖打包的资源冗余。 简而言之,若两个不同的AssetBundle中的资源,引用了统一个未标记AssetBundle标签的资源,那么该资源将会分别被拷贝到这两个AssetBundle中去,造成资源冗余。(这里推荐用代码动态添加Bundle标签,即提前遍历所有需要打Bundle包的资源,先为他们加上Bundle标签,防止出现冗余问题。)
AsseBundle资源之间的相互引用关系:
AsseBundle资源依赖加载方式:
加载一个Bundle中的资源前,要先加载该Bundle和所有被该Bundle引用的AssetBundle,这里官方特别提出了,AssetBundle之间的加载顺序对引用没有影响,但是要保证使用资源前(LoadAsset)正确的加载了所有的需要的Bundle。
(不过测试中确实有Bundle都加载出来,但是资源还是会丢失的情况,这种特殊处理就好:等待一帧,提前手动引用,检查Shader的设置等):
AssetBundle循环依赖问题(即A->B,B->A的问题):
其实这个问题很好解决,所有加载进来的Bundle,都用字典(Dictionary)存起来,加载新的Bundle之前判断该Bundle有没有被加载过就好了。
至于网上说的那些要控制Bundle加载先后顺序的问题纯属谬论。官方明确说明,无需关心加载顺序,只要保证在使用资源前,将所有被引用的Bundle都被正确的加载过一次就好了。
这种问题多出现在UGUI中,因为UGUI的图集在工程中是散图,且未打图集Tag的图片会被打包到一个默认的图集中去。
1 这种情况下如果多个Bundle引用了同一个图片,那么该默认图集就会被重复打到两个Bundle中去(这里特意说明默认图集,是因为这种默认图集更容易出问题且不易察觉,非默认图集也是同样的情况)。
2 另外,如果同一个图集Tag的Sprite被指定了不同的Bundle标签,也会出现上述问题。
所以我们要注意的是,一个AssetBundle可以包含多个图集,但一个图集的资源只能存在于一个AssetBundle中,否则会造成非常严重的资源冗余。
参考链接 :
官方对依赖的解释(注意2019.4文档中加了一个案例解释,但是原理解释没变)https://docs.unity3d.com/Manual/AssetBundles-Dependencies.html
打包依赖分析 https://zhuanlan.zhihu.com/p/34137188AssetBundle
图集Atlas与AB包https://www.jianshu.com/p/0d18ac565563
关于AssetBundle的分配方案,这里大概总结一下网上提出的解决方案,并会简单注明其优势和引用出处。最后再说下,目前字项目中的解决方案。
首先是Unity官方的几种方案:
注: 这个最常用,逻辑实体的优势是,一般情况下一个逻辑实体的加载和卸载时机是一致的,而且按照同一个逻辑实体缓存和卸载,逻辑上更容易理解,且不易出现逻辑BUG。
注: 这个一般作为备用方案,无特殊之处。
注:这个也思路比较重要,但是一般具体方案还是按照上述逻辑实体分组设计。若项目管理比较严格的,会应该会两者相辅使用,比如某关卡的背景图,背景音效分为一组,关卡里面的每个角色分为一组。
其他诸位前辈总结的方案:
注:之前小包更新时有这方面的要求,这个具体看需求吧,如果按照逻辑实体分类导致每次更新包容量都太大,可以考虑对逻辑实体再划分。
注:这个强烈推荐,甚至该方案应该作为最基础的一种设计思路,能减少资源冗余,减少内存加载占用,同时减少频繁更新包的大小。
注:这个比较实用,主要思路是同时加载一个大的的资源会比同时加载数量较多的小资源快(考虑到IO的切换)。但也比较少遇到,需要按需设计。
常见的加载API(只总结异步的)。Ps:别忘了异步需要启动协程。
AssetBundleCreateRequest request = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path));
yield return request;
AssetBundle ab = request.assetBundle;
AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(path);
WWW www = WWW.LoadFromCacheOrDownload(path, 1);
yield return www;
if( !string.IsNullOrEmpty(www.error) )
{
Debug.Log(www.error);
yield break;
}
AssetBundle ab = www.assetBundle;
//1、使用UnityWebRequest.GetAssetBundle(路径)【服务器 / 本地都可以】 去获取到网页请求
UnityWebRequest request = UnityWebRequest.GetAssetBundle(path);
//2、等待这个请求进行发送完
yield return request.SendWebRequest();
//3、发送完请求之后,就要从DownloadHandlerAssetBundle进行获取一个request,得到出来的是一个AssetBundle类对象
AssetBundle ab = DownloadHandlerAssetBundle.GetContent(request);
商业项目中,多需要对资源进行加密。而我遇到的加密方式,基本是对资源进行字节加密,这必然会影响unity对assetbundle的识别,所以加载时也一般只能先使用IO加载到内存中解密,然后再从内存中加载Bundle。(这种方式硬伤很大,参考LoadFromMemoryAsync注释)
(这位大神解释的很清楚,这里不再整理笔记)https://blog.csdn.net/bloodshadow/article/details/52923492
注意,这张图片只看卸载关系即可。紫色部分的bundle镜像内存,在5.4后已经修改为加载headr方式,不再直接拷贝内存镜像。
bundle卸载分为强制卸载(传入true)和弱卸载(参考链接https://www.jianshu.com/p/93d4617f9942)。
注意:如果是依赖打包要考虑引用关系,最好是做个引用计数,以防止出现资源丢失
首先该方案旨在解决的问题(或者优势)是,
以下是文件结构简图
Bundle中的资源按照文件类型分文件夹,他们将由代码按文件夹路径打上Bundle标签
每个命名为Image的文件夹,将由代码遍历其内所有图片,如果该图片无图集Tag,则将该图片打成按照文件夹路径打图集Tag。如果有图集Tag则跳过该图片。
被Prefab直接引用的资源放到Scatter里面并按照文件类型分文件夹,图片也要命名为Image以便自动打图集Tag。
被多个模块引用的资源,将放到CommonModule中去(也就是功能模块思路)。
最后说缺点
其他需要注意的问题
将shader放入GraphicsSettings->Always Included Shaders中后,打包时会将相应的shader抽离,运行时加载时会自动加载其依赖的shader。同时也意味着,如果修改了Always Included Shaders或在一个新建项目中使用该Bundle,会出现shader丢失的问题。注意检查即可。
未找到具体原因,估计是spine 编辑器平台支持不好,改用prefab加载即可。
来自https://blog.csdn.net/lodypig/article/details/51863683
AssetBundleFileHead : 记录了版本、是否压缩等主要描述信息。
AssetFileHeader :包含一个文件列表,记录了每个资源的name,offset,length等。
Asset1 : 第一个资源本身,内部结构如下
AssetHeader :包含了TypeTree大小、文件大小、format等。
所谓增量打包,就是只打包有修改的部分,以减少打包时长。 参考链接https://www.jianshu.com/p/68e66a51f6a8
使用Resources.Load的时候在第一次Instantiate之前,相应的Asset对象还没有被创建,直到第一次Instantiate时才会真正去读取文件创建这些Assets。它的目的是实现一种OnDemand的使用方式,到该资源真正使用时才会去创建这些资源。
而使用AssetBundle.Load方法时,会直接将资源文件读取出来创建这些Assets,因此第一次Instantiate的代价会相对较小。
上述区别可以帮助我们解释为什么发射第一发子弹时有明显的卡顿现象的出现。
参考链接 https://blog.csdn.net/wotingdaonile/article/details/80111164
参考链接https://blog.csdn.net/sinat_28962939/article/details/89396577
打包
BuildPipeline.BuildPlayer(BuildPlayerOptions options )
options 在options中填入需要打包的场景路径,和打包选项设置为BuildAdditionalStreamedScenes
加载
SceneManager.LoadSceneAsync ("Test")
先将Bundle加载到内存中,然后直接调用SceneManager的场景加载接口即可,只要Bundle在内存中,SceneManager就可以自动从内存中加载到对应的场景,无需在特殊指向这个Bundle。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。