本文主要讲解unity中的资源以及其存在方式序列化等等,内容皆来自下面文章和个人的一点总结

官方直播 Unity Asset的一生

【Unity】Asset简介 - 知乎

Unity 5.x AssetBundle零冗余解决方案 | 侑虎科技

Unity引擎资源管理机制介绍 - 海澜个人技术笔记


资源导入

相关资源导入之后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)
  • 其他元信息

file

Artifact 数据库(Artifact Database)

Artifacts 是导入过程的结果。Artifact 数据库记录了每个 Source Asset 的导入结果信息。每个 Artifact 包含以下内容:

  • 导入的依赖关系信息
  • Artifact 的元信息
  • Artifact 文件的列表

file

  • 注意事项
    数据库文件位于项目的 Library 文件夹 中,因此应将其排除在版本控制系统之外。这些文件的具体路径如下:
  • Source Asset 数据库Library\SourceAssetDB
  • Artifact 数据库Library\ArtifactDB

注:对应数据库的大小区分也很大

除了上面提到的两个数据库,Library文件夹还存储 Unity 引擎生成的其它缓存

file
file

Scene

场景相同的物体,最好是用Prefab,这样场景的描述文件要小得多,prefab指向的都是同一个内存,占用小


Meta、FileID、GUID、InstanceID

Meta文件

Meta文件在资源导入的时候就自动产生,其最重要的就是有个GUID,保证资源的唯一性,unity根据这个guid来找到资源

(如果要保持资源的正确引用,在拷贝资源的时候连同meta)

除了guid这个,它还包含了资源的导入设置,例如图片的meta里面就记录了图片的TextureImporter设置

FileID

FileID其实叫LocalID更好,它表示这个本身Object的标识ID,我们分为两种讨论

资源的FileID

image-20250214175509850

对于同一类型资源他们的FileID是一样的,例如Texture都是280000,Script都是11500000,这些都是Unity预定义的(具体值得定义没查到相关文档)

对于图集(一个贴图下有很多子精灵)这种,主贴图还是280000,子图精灵的fileID是系统生成的

Object的FileID

Object的FileID,这里的Object值得是(Prefab,Scene,或者模型,场景中的GameObject),他们的特征很明显,都是一个对象上有多个component,这个时候的FileID就是记录组件的id,看个prefab的例子

image-20250214182658063

image-20250214183144678

这个Prefab就两个组件Transform和MonoScript,此时这个gameobject就记录两个fileID分别指向对应的组件id

这里说明一下 !u!114 &8880607289944865690这个格式

  • !u! 不管,应该是代表unity
  • 接着的id是unity内置的classid 例如114下面肯定就是自定义mono脚本
  • 后面的就是这个组件的fileID

通过这些完整组成了一个prefab(其他也类似)

然后我们再看下这个Mono脚本

1
2
3
4
5
6
7
8
9
10
11
public class NewMonoBehaviour1 : MonoBehaviour
{
public Texture2D texture2D1;
public Texture2D texture2D2;
public Sprite sprite1;
public Sprite sprite2;
public TextAsset textAsset1;
public TextAsset textAsset2;
public GameObject gameObject1;

}

这里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

  1. 结构 总的来说,一个AB包括2个部分:包头数据段

包头:包含有关AssetBundle 的信息,比如标识符、压缩类型和内容清单(manifest:manifest是一个用对象名字做key的查找表),每个条目都提供一个字节索引,该索引指示在AssetBundle的数据段中可以找到给定对象的位置。
在大多数平台,这个查找表是用平衡搜索树实现的。(除了Windows和OSX派生的平台(包括IOS平台)是用红黑树实现)。因此,构建这个清单的时间消耗会随着ab内的资源数量的增加而线性增加。

数据段:通过序列化AB内的资源的原生数据。
- 如果用LZMA压缩,将压缩所有序列化资产的完整字节数组;
- 如果是LZ4压缩,每个资源都是分开压缩的;
- 如果没有压缩,就都保存原生字节流。

  1. Scene,它是一个单独的AssetBundle,因为它和其他的Asset的处理方式是不一样的,所以Asset和Scene是不能打到一起的,要分开打。

  2. 加载一个AssetBundle的时候,它的头会立刻加载进内存,这个也是我们在Profiler里面经常看到的SerializedFile。剩下的内容,也就是Bundle里面的Asset,它是按需加载的。也就是说如果我们不去加载这个Asset,它是不会从包体里被加载到内存中的。但是有一个例外,就是默认的LZMA的压缩,这种压缩格式用一个数据流代表整个AssetBundle,因此要读取里面任意一个Asset的时候需要解压整个数据流。

  3. 打包参数 DisableWriteTypeTree

    用来做兼容,如果unity版本不同用它可以达到兼容效果,使文件变得更小,加载起来也更快一点

    没有设置DisableWriteTypeTree

可以看到关闭之后一个简单的cube的AB包小了很多

在Profiler中,AB的头也会缩小

没有设置DisableWriteTypeTree

方案:在打Release包的时候打包参数添加DisableWriteTypeTree,能缩小包体和内存大小

编辑器的时候不能添加该参数,否则会报错

  1. 打包策略
  • 大小尽量在1-10m之类,大小加载的都是头文件,造成头重脚轻的情况,太大对下载可能有问题
  1. 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#其实是不太一样的。

[https://docs.unity3d.com/Manual/ClassIDReference.html]: