167 lines
12 KiB
Markdown
167 lines
12 KiB
Markdown
# [【三思Unity】深入浅出资源管理之:Asset Import Pipeline v2(上)](https://www.cnblogs.com/hearthstone/p/13358025.html "发布于 2020-07-21 23:12")
|
||
|
||
每个游戏都是由种类繁多的资源构成,例如:网格、材质、纹理、着色器、动画、音频等。导入并管理这些资源文件,是游戏引擎必备的能力。资源管理包含两部分:离线管理和运行时管理。
|
||
|
||
在 Unity 2019 LTS 中,对应的解决方案分别是 Asset Import Pipeline v2(下文简称 AIP v2) 和 Addressable Asset System。本文主要讲解 AIP v2 部分, 包含 v2 相对于 v1 的改进之处,和完整的资源导入流程。
|
||
|
||
关键词:Asset Import Pipeline v2,Unity Accelerator,Asset Revisions,Asset Database
|
||
|
||
# 1. Asset Import Pipeline v2
|
||
|
||
资源导入是 Unity 中最耗时的工作流程之一,是 AIP v1 的遗留问题。针对其中最耗时的两个阶段——新资源的导入和平台切换,AIP v2 分别提供了对应的解决方案。
|
||
|
||
## 1.1 新资源的导入
|
||
|
||
第一次拉取项目时,项目中没有 Library 文件夹,这意味着 Assets 文件夹和 Packages 文件夹中的所有资源都需要经过 AIP 的导入处理,这步非常耗时。相较而言,直接拉取导入结果,耗时就要少几个数量级。
|
||
|
||
### Unity Accelerator
|
||
|
||
AIP v2 给出的方案是 Unity Accelerator,一种基于 Unity Collaborate 的局域网代理和缓存服务。原理是:团队中有一人导入完资源,导入结果被自动缓存到 Accelerator,团队里的其他成员在导入相同版本的资源时,Unity 首先检测 Accelerator 上是否已经有对应的导入结果,如果有就下载。因为是本地网络,带宽不是问题,所以在资源较多的项目中,下载时间要远少于导入时间。
|
||
|
||
在 Unity 给出的测试中,一个大小100MB、数量12000个的项目资源,Untiy 的导入时间为6分钟,Accelerator 的导入时间为30秒,节省了90%以上的资源导入时间。然而实际项目中,12000个资源的空间占用一般要大于100MB,因此该测试仅供参考。
|
||
|
||
Unity Accelerator 基于 Unity Teams Advaned,按团队人数收费。目前提供30天免费试用,25GB云存储,3人团队的价格是60元/月,每增加一名团队成员的价格是48元/月。
|
||
|
||
## 1.2 平台切换
|
||
|
||
在 AIP v1 中,Library 文件夹中缓存的导入结果以资源的GUID作为文件名。这天然的导致了一个资源只存在一个导入结果,因此每次切换平台都需要重新导入所有资源。这步非常耗时,在大型项目中甚至需要数个小时。
|
||
|
||
在 AIP v2 出现前,临时的解决方案是每个平台分别对应一个项目。缺点是要占用多份项目的硬盘空间,以及每个项目需要单独拉取更新。
|
||
|
||
### Asset Revisions
|
||
|
||
AIP v2 给出的解决方案是:不使用资源的GUID作为导入结果的文件名,而是使用资源所有依赖的哈希值作为文件名,而平台正是资源的依赖之一(资源依赖的说明见下文4.3)。于是每个资源因为依赖的不同,而拥有多个版本的导入结果,它们被称为资源的修订版(Asset Revisions)。因此 Asset Database 中可以同时缓存多个平台的导入结果,当切换至已经有导入结果的平台时,就不需要再经历耗时的导入处理。
|
||
|
||
AIP v2 同样存在占用硬盘空间大的缺点,但是相较于每个平台分别对应一个项目,占用空间要小很多。对此 AIP v2 也做了优化处理:只保存每个资源仅平台依赖不同的修订版,其他依赖导致的不同修订版,在 Unity 每次重启时会被删除。
|
||
|
||
# 2. 源文件 Source File
|
||
|
||
Source File 是指由 Unity 之外的软件创建并导出的资源文件。常见的有:图片(png、jpg、psd)、音频(wav、ogg、mp3)、视频(avi、wmv、mp4)、模型(fbx、obj、c4d、blend)、字体(ttf、otf、ttc)、着色器(cg、hlsl)、文本文件(txt、xml、json、csv)、代码文件(dll、jar、cpp、c、mm、dylib)等。
|
||
|
||
仅仅将 Source File 放入项目文件夹中,并不代表它成为了项目的一部分。只有经过 AIP 的导入处理,Soure File 转变成 Asset, 才能为项目所用。
|
||
|
||
导入处理将 Source File 转换成 Unity 能够处理的格式。这些格式往往硬件友好,能够让CPU、图形、音频等硬件立即处理。例如:Unity 在运行时不会直接使用 .png 格式的图片,而是使用经过导入处理后的某种具体格式的 texture 文件。
|
||
|
||
Pipeline 确保了导入结果的确定性,即:相同依赖的 Sourece File 经过导入处理,在任意时刻、任意设备得到的 Asset 是相同的。
|
||
|
||
# 3. 资源数据库 Asset Database
|
||
|
||
Asset Database 包含了两层含义。一层指 Source Files 经过 AIP 导入处理后的数据文件集合,即项目 Library 文件夹中的内容,又称为 Asset Cache。Unity 在项目运行和打包时,使用的正是这里面的数据文件。
|
||
|
||
另一层指 Unity 封装的 AssetDatabase 类。该类提供了对于 AIP 最重要的一个方法 Refresh,以及一系列方便安全的项目文件操作方法,例如:Contains、CreateAsset、CreateFolder、RenameAsset、CopyAsset、MoveAsset、DeleteAsset、LoadAssetAtPath等。
|
||
|
||
# 4. 刷新 Asset Database
|
||
|
||
将 Source File 放入项目 Assets 文件夹中时,或通过 Package Manager 导入 Package 时,AIP 检测到新资源,并执行导入处理。这一步的本质是触发了 AssetDatabase 的 Refresh 方法。
|
||
|
||
以下三种情况会触发 AssetDatabase 的 Refresh 方法:
|
||
|
||
1. Unity Editor 重新获得焦点(如果开启了 Auto-Refresh)。
|
||
2. 点击 Assets > Refresh 菜单。
|
||
3. 在 C# 代码中直接调用 Refresh 方法;或通过调用 CreateAsset、ImportAsset 等方法,间接触发 Refresh 方法。
|
||
|
||
AssetDatabase 的 Refresh 方法将按顺序执行以下步骤:
|
||
|
||
1. 寻找变更的 Assets,并更新 source Asset Database(Library 文件夹中)
|
||
2. 导入并编译代码相关文件
|
||
3. 重载脚本域
|
||
4. 导入非代码文件
|
||
5. 热重载
|
||
6. 更新 Artifact Database(Library 文件夹中)
|
||
|
||
接下来将对上述步骤做较详细介绍,其中某些步骤会被拆分为几个小步骤。
|
||
|
||
## 4.1 寻找变更的 Assets
|
||
|
||
当触发了 AssetDatabase.Refresh() 方法后,Unity 开始扫描项目中 Assets 文件夹和 Packages 文件夹中的每个资源。与上次扫描相比,如果资源出现了新增、变更或删除,则将它添加到一个资源变更列表中。
|
||
|
||
## 4.2 更新 source Asset Database
|
||
|
||
计算资源变更列表中的每个资源的哈希值,并且根据它们的GUID,更新 source Asset Database,并从列表中删除对应资源。
|
||
|
||
## 4.3 依赖追踪(Dependency tracking)
|
||
|
||
资源的依赖(Dependencies)是指所有能够影响导入结果的数据。资源的 Source File 是最原始的依赖,资源的导入设置、目标平台等也都是依赖。
|
||
|
||
**资源变更的本质,是资源依赖的变更**。AIP 追踪了每个资源的所有依赖。资源的任意依赖发生变更,都将引起资源的重新导入,生成新的导入结果。
|
||
|
||
**资源变更的结果,是不同版本的资源缓存**。AIP 维护了项目所有资源的导入结果,这些导入结果缓存在 Library 文件夹中,又叫做资源缓存(Asset Cache)。同一个资源的不同导入结果,是不同版本的资源缓存,又称为资源的修订版(Asset Revisions)。
|
||
|
||
## 4.4 导入并编译代码相关文件
|
||
|
||
Unity 从资源变更列表中收集与代码相关的文件,递送给脚本编译管线(script compilation pipeline)。然后编译器根据脚本文件和 assembly definition 文件生成程序集(Assemblies)。
|
||
|
||
## 4.5 重载脚本域(Reload the domain)
|
||
|
||
域重载(Domain Reloading)处理会完全重置脚本的状态,包括重置所有静态字段和静态事件句柄。任意脚本的变更,都会重载脚本域。为了确保自定义导入器(Scripted Importer)起作用,这步将重启 AssetDatabase.Refresh() 方法。
|
||
|
||
## 4.6 导入非代码资源
|
||
|
||
AIP 基于资源文件名的扩展名识别文件类型,再基于文件类型使用对应的导入器(Asset Importer)处理资源。例如:TextureImporter 负责导入 jpg、png 和 psd 等图片资源。导入器分为两种:原生导入器(Native Importers)和自定义导入器(Scripted Importers)。
|
||
|
||
### 4.6.1 原生导入器(Native Importers)
|
||
|
||
Unity 内置了很多原生导入器,支持大多数资源类型,例如:图片、音频、视频、模型、材质、着色器、字体、文本文件、代码文件等。
|
||
|
||
### 4.6.2 自定义导入器(Scripted Importers)
|
||
|
||
对于新的资源类型,我们可以编写自己的导入器。对于 Unity 已经提供了导入器的资源类型,也可以使用自定义导入器覆盖原生的导入处理。
|
||
|
||
部分 Unity 内置的导入器也属于自定义导入器,因此这些导入器的处理,发生在自定义导入器的处理阶段。例如:StyleSheetImporter(uss 文件)、UIElementsViewImporter(uxml 文件)。
|
||
|
||
## 4.7 预处理(Preprocess)和后处理(Postprocess)
|
||
|
||
在导入阶段,有许多回调方法,可用于资源导入的预处理和后处理。
|
||
|
||
预处理方法有:
|
||
|
||
- OnpreprocessAsset
|
||
- OnpreprocessAnimation
|
||
- OnpreprocessAudio
|
||
- OnpreprocessModel
|
||
- OnpreprocessSpeedTree
|
||
- OnpreprocessTexture
|
||
|
||
后处理方法有:
|
||
|
||
- OnAssignMaterialModel
|
||
- OnPostprocessAnimation
|
||
- OnPostprocessAssetbundleNameChanged
|
||
- OnPostprocessAudio
|
||
- OnPostprocessCubemap
|
||
- OnPostprocessGameobjectWithAnimatedUserProties
|
||
- OnPostprocessWithUserProperties
|
||
- OnPostprocessMaterial
|
||
- OnPostprocessMeshHierarchy
|
||
- OnPostprocessModel
|
||
- OnPostprocessSpeedTree
|
||
- OnPostprocessSprites
|
||
- OnPostprocessTexture
|
||
- OnPostprocessAllAssets
|
||
|
||
## 4.8 重启 AssetDatabase 的 Refresh 处理
|
||
|
||
在上文中提到,重载脚本域将重启 AssetDatabase.Refresh() 方法。除此以外,还有其他情况会重启 AssetData base 的 Refresh 处理。例如:
|
||
|
||
- 如果某个资源导入失败
|
||
- 如果资源在 Refresh 中的导入阶段被更改
|
||
- 如果资源在导入时生成了其他资源
|
||
- 如果在预处理/后处理回调方法中强制重新导入某个文件,例如:在 OnPostProcessAllAssets 方法中使用 AssetDatabase.ForceReserializeAssets 方法或 AssetImport.SaveAndReimport 方法。注意避免无限重导入。
|
||
- 如果在编译完脚本后,某个程序集需要重新加载。
|
||
- 如果以“Text only”保存某个资源,但是该资源必须被序列化为二进制格式,就会触发 Refresh 处理。例如:带有地形的场景必须被序列化为二进制格式,因为文本格式太笨重。
|
||
|
||
## 4.9 热重载 Hot reloading
|
||
|
||
热重载是指在 Editor 不重启的情况下,导入并应用脚本和资源的变更。热重载在 Editor 的 Play Mode 和非 Play Mode 都有可能发生。
|
||
|
||
当你更改并保存一个脚本文件时,Unity 首先保存所有已加载脚本的序列化变量,等脚本加载完成再恢复这些数据。在热重载完成后,所有非序列化的数据都将丢失。
|
||
|
||
## 4.10 更新 Artifact Database
|
||
|
||
这是 Refrech 的最后一步,Library 文件夹中的 Artifact Database 被更新,导入结果被保存在硬盘上。
|
||
|
||
# 引用参考
|
||
|
||
[1] [The new Asset Import Pipeline: Solid foundation for speeding up asset imports](https://blogs.unity3d.com/cn/2019/10/31/the-new-asset-import-pipeline-solid-foundation-for-speeding-up-asset-imports/)
|
||
[2] [The Asset Database](https://docs.unity3d.com/Manual/AssetDatabase.html)
|
||
[3] [Refreshing the Asset Database](https://docs.unity3d.com/Manual/AssetDatabaseRefreshing.html) |