使用 AssetDatabase 类可自定义资源管线,并创建工具以使用自己的脚本访问、加载、创建和操作资源,从而扩展编辑器的工作方式。它是一个 Editor 类,因此其功能在运行时在独立构建中不可用。
AssetDatabase 类具有大量方法,可以让您完全按照 Unity 编辑器本身的用法访问资源并执行操作。您可以创建、导入、删除、复制、移动、加载和保存资源,以及搜索资源数据库。
这意味着您可以使用 Unity 的编辑器脚本和编辑器窗口自定义来创建任何内容,比如简单的调整、强大的工具和自定义,还有项目的资源工作流程。
如需简单的示例,请参阅 AssetDatabase.ForceReserializeAssets 方法文档。该文档介绍了如何将菜单项添加到编辑器,从而更好地控制将项目升级到 Unity 新版本时,应该如何升级某些资源捆绑包。
如需可用方法的完整列表以及每种方法的介绍文档,请参阅 AssetDatabase 脚本 API 页面。
从脚本的角度来看,Unity 认为的“资源”与项目窗口中显示的内容略有不同。放置在项目的 **Assets ** 文件夹中的文件是资源的源文件,但在概念上与 Unity 编辑器使用的资源对象不同。Unity 在导入资源文件时,会处理这些文件并生成导入的结果:派生自 UnityEngine.Object 的序列化 C# 对象。从脚本的角度来看,在 Unity 编辑器中编写脚本时,可以访问的资源是这些导入的结果。
例如,可能以二进制文件(例如 JPEG 或 PNG 图像文件)开头的资源将转换为类型为 UnityEngine.Object 特殊化的 C# 对象。如果是 JPEG 或 PNG 文件,则它们会被转换为 Texture 类的序列化实例,该类又继承自 UnityEngine.Object。然后,序列化的对象数据作为工件存储在 Library 文件夹中。因此,使用脚本访问纹理资源时,访问的不是原始 JPEG 或 PNG 文件,而是导入原始图像文件时生成的 C# 纹理对象的序列化版本。导入过程中 Unity 创建的 .meta 文件,保存在原始资源文件旁边,其中包含资源的导入设置,还包含 GUID 以允许 Unity 连接原始资源文件与资源数据库中的工件。
Unity 本身创建的某些类型的资源文件(例如 .prefab、.scene、.asset 和 .mat)已经在其源文件中包含序列化的数据,因此 Unity 生成和缓存的工件文件与源文件非常相似。这些文件的源文件(例如项目的 Assets 文件夹中的 .mat 材质文件)是人工可读的(前提是资源序列化模式 (Asset Serialization Mode) 设置为强制文本 (Force Text),这是默认设置)。这与从外部来源(例如纹理或音频)导入的二进制资源文件不同,后者的文件通常人工不可读。
资源文件可以包含多个序列化对象,为了使用 AssetDatabase 方法编写脚本,可以将每个对象视为“资源”。例如,.prefab 资源文件可以包含附加了多个组件的序列化游戏对象。其中每个组件也会序列化为资源文件中的对象,因此使用 AssetDatabase 方法访问预制件资源的内容时,资源文件中的组件对象将被视为子资源(下文将更详细地说明)。
导入过程中生成的序列化对象称为工件,Unity 将其存储在资源数据库的导入工件缓存中,位于项目的 Library 文件夹中。工件被视为缓存的数据,因为 Unity 始终可以使用导入器设置和保存在项目中的项目设置从源资源重新生成工件。
您可以使用 Import Activity 窗口检查为项目中的资源生成的工件,窗口中将显示 Unity 生成的具体缓存工件文件以及其他有用信息,例如导入发生的时间以及花费的时间。
每个工件文件名都是一个没有文件扩展名的唯一哈希(GUID)。Unity 将这些文件分成多个子文件夹,每个子文件夹的名称都与工件文件名的前两个字符匹配。
这些工件文件包含二进制数据,并非设计为人工可读文件。虽然了解这些文件包含资源数据库使用的数据很有用,但在使用 Unity 时无需直接查看、编辑或使用这些文件。相反,AssetDatabase 类提供了在编辑器中使用资源所需的方法。
由于 Unity 可以在同一个资源文件中存储多个序列化对象,因此 Unity 在任何资源文件中都有主资源的概念。Unity 创建包含单个资源(例如材质)的资源文件时,主资源始终是该单个资源。对于包含多个序列化资源对象的其他类型的资源,除非 SetMainObject 方法另有指定,否则主资源始终是添加到文件的第一个资源。
有时可以在编辑器的项目窗口中看到子资源(如果这些子资源属于特定类型)。例如,在项目窗口中查看包含“Space Frigate”模型的此 FBX 资源文件时,其视图已展开以显示其具有材质和网格作为子资源。
资源还可以具有不会像这样在项目窗口中显示的子资源类型。例如,上面的“Space Frigate”资源文件实际上包含项目窗口中显示的两个以上的子资源。使用 AssetDatabase 方法访问资源文件时,可以看到资源的真实数量,如下脚本所示:
using UnityEngine;
using UnityEditor;
public class Example : MonoBehaviour
{
[MenuItem("AssetDatabase/InspectAssets")]
private static void InspectAssets()
{
Object[] data = AssetDatabase.LoadAllAssetsAtPath("Assets/Space Frigate.fbx");
Debug.Log(data.Length + " Assets");
foreach (Object o in data)
{
Debug.Log(o);
}
}
}
在这种情况下,输出将显示此文件的导入序列化版本包含六个资源:
6 Assets
Space Frigate (UnityEngine.GameObject)
space_frigate_0 (UnityEngine.Material)
space_frigate_0 (UnityEngine.Mesh)
Space Frigate (UnityEngine.Transform)
Space Frigate (UnityEngine.MeshRenderer)
Space Frigate (UnityEngine.MeshFilter)
这是因为游戏对象、材质、网格数据本身以及 Unity 在导入过程中自动添加到游戏对象中的每个组件(变换组件、MeshFilter 和 MeshRenderer)都算作单独的序列化对象。因此,它们是资源文件的子资源,就资源数据库 API 而言,它们都是单独的资源。
如果要使用 AssetDatabase 类编写脚本,请务必了解 Unity 导入过程的顺序如何影响脚本,否则可能会取得意外结果。顺序如下所示:
脚本始终在所有其他常规资源之前导入和编译,因为编辑器需要知道项目中是否存在自定义资源后期处理器或脚本化导入器。这可确保编辑器在导入其余非脚本资源时使用任何新的或更改后的导入器或后处理器。
InitializeOnLoad 回调通常用于在项目启动时或脚本更改时运行一些代码。如上列表所示,此回调在 Unity 重新加载域后运行,但在开始导入资源之前运行。这意味着,如果使用 [InitializeOnLoad] 回调来访问资源,则会在当前资源导入周期完成之前执行代码。特别是以下情况:
对于首次导入的资源,AssetDatabase.LoadAssetAtPath、AssetDatabase.FindAssets、Shader.Find、Resources.Load 等方法将返回 null,因为这些资源尚未导入。
对于至少已导入一次的资源,AssetDatabase.LoadAssetAtPath、AssetDatabase.FindAssets、Shader.Find、Resources.Load 等方法将在重新加载域之前修改资源的旧版本(过时)时返回,因为域重新加载发生在常规资源导入阶段之前。
在编写脚本化导入器、资源预处理器和资源后处理器时,不应使代码假设其他特定资源已根据任何特定顺序导入。导入时,Unity 会按类型将资源分组到队列中,虽然类型是按预定义顺序导入的,但除非使用 ScriptedImporter.GatherDependenciesFromSourceFile,否则相同类型队列中的资源将以任意顺序导入。使用 GatherDependenciesFromSourceFile 还会在资源之间产生依赖关系,因此如果一个资源被修改,则另一个依赖该资源的资源将被重新导入。