Unity中的Asset
本文主要讲解unity中的资源以及其存在方式序列化等等,内容皆来自下面文章和个人的一点总结
Unity 5.x AssetBundle零冗余解决方案 | 侑虎科技
资源导入
相关资源导入之后untiy会将资源数据转换成其认可的数据,记录再AssetDatabase
里面,也就是Library中
Library文件夹的内容
Source Assets 和 Artifacts
Unity在 Library 文件夹 中维护了两个数据库文件,它们统称为 资产数据库(Asset Database)。这两个数据库分别记录了有关 Source Assets 的信息,以及 Artifacts 的信息(即导入结果的相关数据)。
Source Asset 数据库(Source Asset Database)
Source Asset 数据库包含 Source Assets 的元信息。Unity 使用这些信息来判断文件是否发生了修改,从而决定是否需要重新导入文件。这些信息包括以下内容:
- 文件的最后修改日期
- 文件内容的哈希值
- 全局唯一标识符(GUID)
- 其他元信息
Artifact 数据库(Artifact Database)
Artifacts 是导入过程的结果。Artifact 数据库记录了每个 Source Asset 的导入结果信息。每个 Artifact 包含以下内容:
- 导入的依赖关系信息
- Artifact 的元信息
- Artifact 文件的列表
- 注意事项
数据库文件位于项目的 Library 文件夹 中,因此应将其排除在版本控制系统之外。这些文件的具体路径如下:- Source Asset 数据库:
Library\SourceAssetDB
- Artifact 数据库:
Library\ArtifactDB
注:对应数据库的大小区分也很大
除了上面提到的两个数据库,Library文件夹还存储 Unity 引擎生成的其它缓存
Scene
场景相同的物体,最好是用Prefab,这样场景的描述文件要小得多,prefab指向的都是同一个内存,占用小
Meta、FileID、GUID、InstanceID
Meta文件
Meta文件在资源导入的时候就自动产生,其最重要的就是有个GUID,保证资源的唯一性,unity根据这个guid来找到资源
(如果要保持资源的正确引用,在拷贝资源的时候连同meta)
除了guid这个,它还包含了资源的导入设置,例如图片的meta里面就记录了图片的TextureImporter设置
FileID
FileID其实叫LocalID
更好,它表示这个本身Object的标识ID
,我们分为两种讨论
资源的FileID
对于同一类型资源他们的FileID是一样的,例如Texture都是280000,Script都是11500000,这些都是Unity预定义的(具体值得定义没查到相关文档)
对于图集(一个贴图下有很多子精灵)这种,主贴图还是280000,子图精灵的fileID是系统生成的
Object的FileID
Object的FileID,这里的Object值得是(Prefab,Scene,或者模型,场景中的GameObject),他们的特征很明显,都是一个对象上有多个component,这个时候的FileID就是记录组件的id,看个prefab的例子
这个Prefab就两个组件Transform和MonoScript,此时这个gameobject就记录两个fileID分别指向对应的组件id
这里说明一下 !u!114 &8880607289944865690
这个格式
- !u! 不管,应该是代表unity
- 接着的id是unity内置的classid 例如114下面肯定就是自定义mono脚本
- 后面的就是这个组件的fileID
通过这些完整组成了一个prefab(其他也类似)
然后我们再看下这个Mono脚本
1 | public class NewMonoBehaviour1 : MonoBehaviour |
这里texture2d1和texture2d2他们的guid不同代表他们指向了不同的图片文件
sprite1和sprite2他们的guid相同但是fileID不同,代表是一个图集下的两个子精灵
InstanceID
Instance ID 是 Unity 在运行时为已加载到内存中的对象分配的临时唯一标识,只在当前运行会话中有效。
Unity 在编辑器或游戏运行时将资源(如 GameObject、组件等)加载到内存后,这些对象都需要一个唯一编号便于引擎管理与访问。Instance ID 仅在进程存活期间有效,一旦编辑器重启或游戏停止,这些 ID 即失效,不同于固定存储于项目中的 GUID 和 Local ID。
记住,InstanceID不能用来做持久化数据,他是变化的
总结与对比
属性 | GUID | Local ID | Instance ID |
---|---|---|---|
唯一性 | 全局唯一 | 文件内唯一 | 运行时唯一 |
存储范围 | 项目范围 | 单个资源文件中 | 内存中 |
存储位置 | .meta 文件 |
场景或 Prefab 文件 | 不存储 |
用途 | 跨文件引用资源 | 文件内资源定位 | 内存中引用 |
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#其实是不太一样的。