☰
Unity 构建序列化:level0、sharedassets、resources.assets
2026/10/18 1:56:55 网站建设 项目流程

一、构建产物总览

以 Windows Standalone 为例,MyGame_Data/目录大致如下(Android 位于assets/bin/Data/):

MyGame_Data/ ├── globalgamemanagers # 全局管理器(PlayerSettings、TagManager、QualitySettings……) ├── globalgamemanagers.assets # 全局管理器引用的资源(Always Included Shaders、Splash、MonoScript 等) ├── level0 # Build Settings 中第 0 个场景的场景对象 ├── level1 ├── ... ├── sharedassets0.assets # level0 首次引用的资源对象 ├── sharedassets0.assets.resS # 对应的大块二进制数据(纹理像素、网格顶点等) ├── sharedassets0.resource # 音视频数据(AudioClip、VideoClip) ├── sharedassets1.assets ├── ... ├── resources.assets # Resources 文件夹中的资源(及其依赖) ├── resources.assets.resS ├── resources.resource ├── Resources/ │ ├── unity default resources # 内置资源(默认字体、内置 Mesh 等) │ └── unity_builtin_extra # 内置 Shader、Default-Material 等 └── Managed/ # 托管程序集(Mono 后端)或 il2cpp_data

这些文件(除.resS/.resource外)都是同一种格式:SerializedFile。区别只在于"装了哪些对象"。


二、三类文件各装什么

1. levelN —— 场景本身

对应 Build Settings 中第 N 个场景,只包含属于场景的对象:

  • 场景中所有GameObject、Transform以及各类Component(MonoBehaviour、MeshRenderer、Collider……)
  • 场景级设置:RenderSettings、LightmapSettings、NavMeshSettings、OcclusionCullingSettings
  • 场景实例化的 Prefab 会被展开成普通 GameObject 写入(Player 中不存在"Prefab 实例"概念)

不包含:Texture、Mesh、Material、AudioClip、AnimationClip 等资源对象——它们只以引用形式(PPtr)出现在 level 文件中。

2. sharedassetsN.assets —— 场景依赖的资源

规则可以概括为:按场景构建顺序,资源放进第一个引用它的场景所对应的 sharedassets 文件中,后续场景复用,不重复存储。

Scene0 引用: TexA, MatA, MeshA → sharedassets0: TexA, MatA, MeshA Scene1 引用: TexA, TexB → sharedassets1: TexB (TexA 引用 sharedassets0) Scene2 引用: MatA, MeshC → sharedassets2: MeshC (MatA 引用 sharedassets0)

因此:

  • 加载level2时,运行时需要同时访问sharedassets2和sharedassets0(通过外部引用表)。
  • level0 的依赖越多,sharedassets0 越大,首场景加载越慢——这是"用一个极简启动场景做 level0"的直接原因。

3. resources.assets —— Resources 文件夹

  • 所有位于任意Resources/目录下的资源无条件打入(不管有没有被引用)。
  • 这些资源的依赖(如 Prefab 引用的 Material、Texture)也会被收集进来;若某依赖已在其他序列化文件中,则可能以跨文件引用的方式指向它(具体分配策略随 Unity 版本略有差异,建议用下文的 BuildReport 实际核对)。
  • Resources 的"路径 → 对象"索引表保存在globalgamemanagers的ResourceManager中,启动时会构建查找结构,Resources 中对象越多,启动开销越大。这也是官方不推荐大量使用 Resources 的主要原因之一。

4. .resS 与 .resource —— 流式数据

Unity 5.x 之后,大块数据从 SerializedFile 中剥离:

文件内容说明
.assets.resS纹理像素、网格顶点/索引等对象内只存StreamingInfo { offset, size, path }
.resourceAudioClip、VideoClip 数据支持流式读取音频

好处是:SerializedFile 本体小、反序列化快;大数据可异步/直接读取到 GPU 上传缓冲,Texture Streaming 也基于此机制。


三、SerializedFile 二进制结构

┌───────────────────────────────┐ │ Header │ metadataSize, fileSize, version, │ │ dataOffset, endianness ├───────────────────────────────┤ │ Metadata │ │ ├─ Unity 版本字符串 │ 如 "2022.3.20f1" │ ├─ 目标平台 (BuildTarget) │ │ ├─ enableTypeTree 标志 │ │ ├─ Types[] │ classID、是否被裁剪、脚本类型索引、 │ │ │ 类型哈希、(可选) TypeTree 节点 │ ├─ Objects[] │ PathID(int64)、byteStart、byteSize、typeIndex │ ├─ ScriptTypes[] │ 引用的脚本对象 │ ├─ Externals[] │ 外部文件表:GUID、type、pathName │ └─ RefTypes[] / UserInfo │ (SerializeReference 等,新版本) ├───────────────────────────────┤ │ Object Data │ 每个对象按 Transfer 顺序二进制写入, │ │ 对象间按 8/16 字节对齐 └───────────────────────────────┘

关键点

① 跨文件引用:PPtr

任何对UnityEngine.Object的引用在磁盘上都是:

PPtr { int m_FileID; int64 m_PathID; }
  • m_FileID == 0:同一文件内的对象
  • m_FileID == n:Externals[n-1]指向的文件(如sharedassets0.assets、unity default resources)
  • m_PathID:目标文件中对象的唯一标识(Player 构建时通常会重新编号,与编辑器 YAML 中的 fileID 不同)

所以 level1 里的 MeshRenderer 指向 sharedassets0 里的 Material,就是一个FileID=1, PathID=xx的 PPtr。

② TypeTree

  • TypeTree 描述字段布局(名字、类型、大小、对齐),用于版本兼容的反序列化。
  • Player 构建默认不写 TypeTree(引擎与数据版本完全一致,直接按编译期布局读取,省空间、更快)。
  • AssetBundle 默认写入 TypeTree(因为可能被不同版本的 Player 加载),可用BuildAssetBundleOptions.DisableWriteTypeTree关闭。

③ 对象数据写入

引擎使用StreamedBinaryWrite调用每个类型的Transfer():

  • 基本类型按小端直接写入,bool/byte后按 4 字节对齐
  • 数组/字符串:先写int32长度再写元素,结束后 4 字节对齐
  • MonoBehaviour(classID 114):先写引擎字段(m_GameObject、m_Enabled、m_Script等),再按 C# 序列化规则写用户字段

四、构建流程中的序列化步骤

逻辑上,BuildPipeline.BuildPlayer的流程大致是:

1. 编译脚本(Player 版本,去掉 UNITY_EDITOR 代码) 2. 按 Build Settings 顺序处理每个场景: a. 打开场景,执行 IProcessSceneWithReport / OnProcessScene 回调 b. 剥离 EditorOnly 标签对象、HideFlags.DontSaveInBuild 对象 c. 计算依赖闭包 d. 场景对象 → levelN 未分配过的依赖资源 → sharedassetsN(大数据 → .resS / .resource) 3. 收集 Resources 文件夹 → resources.assets 4. 全局管理器 → globalgamemanagers;其引用资源 → globalgamemanagers.assets 5. Shader 变体编译、纹理平台格式转换(结果缓存于 Library) 6. 可选:LZ4 压缩打包为 data.unity3d(Standalone), 或合并为 .data(WebGL)、放入 APK/AAB(Android)

注意:资源在写入时已是平台格式(如 ASTC、ETC2、BC7),Material 只保留目标平台需要的 keyword 和 Shader 变体引用。


五、如何查看"谁进了哪个文件"

方法 1:BuildReport API(最权威)

usingUnityEditor;usingUnityEditor.Build.Reporting;usingUnityEngine;usingSystem.Linq;publicstaticclassBuildContentDumper{[MenuItem("Tools/Build And Dump Content")]publicstaticvoidBuildAndDump(){varoptions=newBuildPlayerOptions{scenes=EditorBuildSettings.scenes.Where(s=>s.enabled).Select(s=>s.path).ToArray(),locationPathName="Build/MyGame.exe",target=BuildTarget.StandaloneWindows64,options=BuildOptions.None};BuildReportreport=BuildPipeline.BuildPlayer(options);foreach(PackedAssetspackedinreport.packedAssets){Debug.Log($"====={packed.shortPath}({packed.overhead}bytes overhead) =====");foreach(varginpacked.contents.GroupBy(c=>c.sourceAssetPath).OrderByDescending(g=>g.Sum(c=>(long)c.packedSize))){longsize=g.Sum(c=>(long)c.packedSize);Debug.Log($"{size/1024f:F1}KB{g.Key}");}}}}

packedAssets会精确列出level0、sharedassets0.assets、resources.assets等每个文件中包含的源资源及大小。

方法 2:Editor.log

构建完成后在 Editor.log 中搜索Build Report,可看到按类别统计的大小和最大资源列表。

方法 3:解析构建产物

  • AssetStudio / AssetRipper:可视化浏览 SerializedFile 中的对象。
  • UnityPy(Python):
importUnityPy env=UnityPy.load("MyGame_Data/sharedassets0.assets")forobjinenv.objects:print(obj.path_id,obj.type.name,obj.byte_size)

注意:由于 Player 构建不含 TypeTree,第三方工具需要匹配对应 Unity 版本的类型结构才能完整解析。


六、实践建议

问题原因对策
首场景加载慢level0 依赖多,sharedassets0 庞大level0 用极简启动场景,再异步加载主场景
启动时间长Resources 索引在启动时构建减少 Resources 使用,改用 Addressables / AssetBundle
包体臃肿Resources 中的资源无条件打包清理 Resources 中无用资源
资源"重复"同一资源在 Player 和 AssetBundle 中各存一份Player 与 Bundle 之间不会去重,需规划好资源归属
意外包含编辑器资源场景中残留调试对象使用 EditorOnly 标签或IProcessSceneWithReport剥离
内置资源冗余引用 Default-Material、内置 Shader用自定义资源替代,避免依赖 unity_builtin_extra

七、小结

  • levelN:第 N 个场景的场景对象(GameObject/Component/场景设置)。
  • sharedassetsN.assets:按场景顺序"首次引用即归属"的资源,跨场景通过 PPtr 共享,不重复存储。
  • resources.assets:Resources 文件夹中的全部资源,无条件打包,并影响启动时间。
  • .resS / .resource:从对象中剥离的大块数据,支持流式读取。
  • 所有文件共享 SerializedFile 格式,通过Externals 表 + PPtr(FileID, PathID)构成跨文件引用网络。

理解这套机制后,就能有针对性地优化首场景加载、启动时间和包体大小,也更容易判断何时应迁移到 Addressables/AssetBundle 体系。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询