# [【三思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)