Unity Asset一生
选自Unity官方直播 高川
[知乎]([Unity]Asset简介 - 知乎 (zhihu.com))
Scene
场景相同的物体,最好是用Prefab,这样场景的描述文件要小得多,prefab指向的都是同一个内存,占用小
Meta
AssetBundle
- 结构 总的来说,一个AB包括2个部分:
包头
和数据段
包头:包含有关AssetBundle 的信息,比如标识符、压缩类型和内容清单(manifest:manifest是一个用对象名字做key的查找表),每个条目都提供一个字节索引,该索引指示在AssetBundle的数据段中可以找到给定对象的位置。
在大多数平台,这个查找表是用平衡搜索树实现的。(除了Windows和OSX派生的平台(包括IOS平台)是用红黑树实现)。因此,构建这个清单的时间消耗会随着ab内的资源数量的增加而线性增加。数据段:通过序列化AB内的资源的原生数据。
- 如果用LZMA压缩,将压缩所有序列化资产的完整字节数组;
- 如果是LZ4压缩,每个资源都是分开压缩的;
- 如果没有压缩,就都保存原生字节流。
Scene
,它是一个单独的AssetBundle,因为它和其他的Asset的处理方式是不一样的,所以Asset和Scene是不能打到一起的,要分开打。加载一个AssetBundle的时候
,它的头会立刻加载进内存,这个也是我们在Profiler里面经常看到的SerializedFile
。剩下的内容,也就是Bundle里面的Asset,它是按需加载
的。也就是说如果我们不去加载这个Asset,它是不会从包体里被加载到内存中的。但是有一个例外,就是默认的LZMA的压缩,这种压缩格式用一个数据流代表整个AssetBundle,因此要读取里面任意一个Asset的时候需要解压整个数据流。打包参数 DisableWriteTypeTree
用来做兼容,如果unity版本不同用它可以达到兼容效果,使文件变得更小,加载起来也更快一点
可以看到关闭之后一个简单的cube的AB包小了很多
在Profiler中,AB的头也会缩小
方案:在打Release包的时候打包参数添加DisableWriteTypeTree,能缩小包体和内存大小
编辑器的时候不能添加该参数,否则会报错
打包策略
- 大小尽量在1-10m之类,大小加载的都是头文件,造成头重脚轻的情况,太大对下载可能有问题
AssetBundle的识别
当我们前后两次打出AssetBundle的时候,如何判断哪些AssetBundle是有差异的,哪些AssetBundle是没有发生变化的呢?
很多人会通过计算两次打出来AssetBundle的md5来判断是否发生变化,实际上这种方式是不推荐的
。因为在Unity打包的过程中,有一些因素是不稳定的,可能导致你两次打包之后的AssetBundle,虽然你里面的东西没有变,但是打出来的Binary不是严格一致的,从而md5也是不一样的。所以不建议算打出来之后的AssetBundle。那怎么算呢?我们可以算Library里的文件的md5,或者是原文件以及对应的meta文件的md5,用这些算出来的hash做为AssetBundle的变化依据是可以的
Asset的卸载
1.UnloadUnusedAssets
它可以卸载掉那些没用的Asset,把它从内存中清除掉。它也是个Operation,它和加载一样,也是归PreloadManager处理的,它必须独成的,不能并行。因为Unity在一次Load Operation开始的阶段就已经确定了哪些Asset要被Load,所以在Load的过程中又发生了Unload这样的操作,那就会导致有些确定了使用且已经被Load的Asset被卸载掉了,就会导致最后的出错。
所以Unity现在的设计是一个同步的过程,所以这个过程会造成卡顿
。Unity在切换Scene的时候会自动调用一次UnloadUnusedAssets
,如果是通过Scene来管理的话就没太大的必要关心造成的卡顿了。如果不是,那就需要自己找些合适的时机去调用一下。
2.AssetBundle.Unload
它又分true和false,但是无论哪一个都和上面的不一样,它不是一个Operation,也就是不归PreloadManager管。它会遍历当前加载过的东西,然后去把它删掉。
如果是true那就是把AssetBundle和它加载出来的Asset全都一起干掉。这个在不合适的时机就有可能发生Runtime的错误。如果是false,那么只是把AssetBundle给丢掉,Asset是不会被扔掉的。那么当你第二次去加载同一个AssetBundle的时候,在内存中就会有两份Asset,因为当AssetBundle被卸载的时候,它和对应的Asset的关系就被切割掉了。所以AssetBundle不知道之前的Asset是不是还在内存中,是不是从自己这加载出来的。所以使用AssetBundle.Unload就很考验游戏的规划。
Unity为什么不做成Reference?因为Unity内部对于这些Asset实际上是没有Reference的,很多时候是通过遍历去查找,实际上不存在大家想象的ReferenceCount,它和C#其实是不太一样的。