导入插件

注解

本教程假设您已经知道如何制作通用插件。 如有疑问,请参阅 制作插件 页面。 这也假设您熟悉Godot的导入系统。

简介

An import plugin is a special type of editor tool that allows custom resources to be imported by Godot and be treated as first-class resources. The editor itself comes bundled with a lot of import plugins to handle the common resources like PNG images, Collada and glTF models, Ogg Vorbis sounds, and many more.

本教程将向您展示如何创建一个简单的导入插件,以将自定义文本文件作为材质资源加载。 此文本文件将包含由逗号分隔的三个数值,它表示颜色的三个通道,并且生成的颜色将用作导入材质的反照率(主颜色)。在此示例中,它将包含纯蓝色(红色0,绿色0和满蓝色):

  1. 0,0,255

配置

首先,我们需要一个通用插件来处理导入插件的初始化和销毁。 让我们先添加 plugin.cfg 文件:

  1. [plugin]
  2. name="Silly Material Importer"
  3. description="Imports a 3D Material from an external text file."
  4. author="Yours Truly"
  5. version="1.0"
  6. script="material_import.gd"

然后我们需要 material_import.gd 文件来在需要时添加和删除导入插件:

  1. # material_import.gd
  2. tool
  3. extends EditorPlugin
  4. var import_plugin
  5. func _enter_tree():
  6. import_plugin = preload("import_plugin.gd").new()
  7. add_import_plugin(import_plugin)
  8. func _exit_tree():
  9. remove_import_plugin(import_plugin)
  10. import_plugin = null

When this plugin is activated, it will create a new instance of the import plugin (which we’ll soon make) and add it to the editor using the add_import_plugin() method. We store a reference to it in a class member import_plugin so we can refer to it later when removing it. The remove_import_plugin() method is called when the plugin is deactivated to clean up the memory and let the editor know the import plugin isn’t available anymore.

Note that the import plugin is a reference type, so it doesn’t need to be explicitly released from memory with the free() function. It will be released automatically by the engine when it goes out of scope.

EditorImportPlugin类

The main character of the show is the EditorImportPlugin class. It is responsible for implementing the methods that are called by Godot when it needs to know how to deal with files.

让我们开始编写我们的插件,一个方法:

  1. # import_plugin.gd
  2. tool
  3. extends EditorImportPlugin
  4. func get_importer_name():
  5. return "demos.sillymaterial"

The first method is the get_importer_name(). This is a unique name for your plugin that is used by Godot to know which import was used in a certain file. When the files needs to be reimported, the editor will know which plugin to call.

  1. func get_visible_name():
  2. return "Silly Material"

The get_visible_name() method is responsible for returning the name of the type it imports and it will be shown to the user in the Import dock.

You should choose this name as a continuation to “Import as”, e.g. “Import as Silly Material”. You can name it whatever you want but we recommend a descriptive name for your plugin.

  1. func get_recognized_extensions():
  2. return ["mtxt"]

Godot’s import system detects file types by their extension. In the get_recognized_extensions() method you return an array of strings to represent each extension that this plugin can understand. If an extension is recognized by more than one plugin, the user can select which one to use when importing the files.

小技巧

许多插件可能会使用像 .json.txt 这样的常见扩展。 此外,项目中可能存在仅作为游戏数据的文件,不应导入。 导入时必须小心以验证数据。 永远不要指望文件格式正确。

  1. func get_save_extension():
  2. return "material"

The imported files are saved in the .import folder at the project’s root. Their extension should match the type of resource you are importing, but since Godot can’t tell what you’ll use (because there might be multiple valid extensions for the same resource), you need to declare what will be used in the import.

由于我们正在导入材质,因此我们将对此类资源类型使用特殊扩展。 如果要导入场景,可以使用 scn 。 通用资源可以使用 res 扩展名。 但是,引擎不会以任何方式强制执行此操作。

  1. func get_resource_type():
  2. return "SpatialMaterial"

导入的资源具有特定类型,因此编辑器可以知道它属于哪个属性槽。 这允许从FileSystem停靠点拖放到Inspector中的属性。

在我们的示例中,它是 SpatialMaterial,可以应用于3D对象。

注解

如果需要从同一扩展中导入不同类型,则必须创建多个导入插件。 您可以在另一个文件上抽象导入代码,以避免在这方面出现重复。

选项和预设

您的插件可以提供不同的选项,以允许用户控制资源的导入方式。 如果一组选定的选项很常见,您还可以创建不同的预设以使用户更容易。 下图显示了选项在编辑器中的显示方式:

../../../_images/import_plugin_options.png

由于可能有许多预设并且它们用数字标识,因此使用枚举是一个很好的做法,因此您可以使用名称来引用它们。

  1. tool
  2. extends EditorImportPlugin
  3. enum Presets { DEFAULT }
  4. ...

既然定义了枚举,让我们继续看一下导入插件的方法:

  1. func get_preset_count():
  2. return Presets.size()

The get_preset_count() method returns the amount of presets that this plugins defines. We only have one preset now, but we can make this method future-proof by returning the size of our Presets enumeration.

  1. func get_preset_name(preset):
  2. match preset:
  3. Presets.DEFAULT:
  4. return "Default"
  5. _:
  6. return "Unknown"

Here we have the get_preset_name() method, which gives names to the presets as they will be presented to the user, so be sure to use short and clear names.

我们可以在这里使用 match 语句来使代码更加结构化。 这样,将来很容易添加新的预设。 我们使用catch all模式来返回一些东西。 虽然Godot不会要求超出您定义的预设计数的预设,但最好是安全起见。

如果您只有一个预设,则可以直接返回其名称,但如果您这样做,则在添加更多预设时必须小心。

  1. func get_import_options(preset):
  2. match preset:
  3. Presets.DEFAULT:
  4. return [{
  5. "name": "use_red_anyway",
  6. "default_value": false
  7. }]
  8. _:
  9. return []

This is the method which defines the available options. get_import_options() returns an array of dictionaries, and each dictionary contains a few keys that are checked to customize the option as its shown to the user. The following table shows the possible keys:

类型描述
name字符串选项的名称。 显示时,下划线变为空格,首字母大写。
default_value任何类型此预设的选项的默认值。
property_hint枚举值One of the PropertyHint values to use as hint.
hint_string字符串属性的提示文本。 与您在GDScript中的 export 语句中添加相同。
usage枚举值One of the PropertyUsageFlags values to define the usage.

``name``和``default_value``键是 强制 ,其余是可选的。

请注意, get_import_options 方法接收预设编号,因此您可以为每个不同的预设(尤其是默认值)配置选项。 在这个示例中,我们使用 match 语句,但是如果您有很多选项并且预设只改变了您可能想要首先创建选项数组的值,然后根据预设更改它。

警告

即使您没有定义预设(通过使 get_preset_count 返回零),也会调用 get_import_options 方法。 您必须返回一个数组,即使它是空的,否则您可能会得到错误。

  1. func get_option_visibility(option, options):
  2. return true

For the get_option_visibility() method, we simply return true because all of our options (i.e. the single one we defined) are visible all the time.

如果只有在使用某个值设置了另一个选项时才需要使某个选项可见,则可以在此方法中添加逻辑。

import 方法

The heavy part of the process, responsible for converting the files into resources, is covered by the import() method. Our sample code is a bit long, so let’s split in a few parts:

  1. func import(source_file, save_path, options, r_platform_variants, r_gen_files):
  2. var file = File.new()
  3. var err = file.open(source_file, File.READ)
  4. if err != OK:
  5. return err
  6. var line = file.get_line()
  7. file.close()

The first part of our import method opens and reads the source file. We use the File class to do that, passing the source_file parameter which is provided by the editor.

如果打开文件时出错,我们将其返回以让编辑器知道导入不成功。

  1. var channels = line.split(",")
  2. if channels.size() != 3:
  3. return ERR_PARSE_ERROR
  4. var color
  5. if options.use_red_anyway:
  6. color = Color8(255, 0, 0)
  7. else:
  8. color = Color8(int(channels[0]), int(channels[1]), int(channels[2]))

此代码获取之前读取的文件行,并将其拆分为以逗号分隔的片段。 如果有多于或少于三个值,则认为该文件无效并报告错误。

Then it creates a new Color variable and sets its values according to the input file. If the use_red_anyway option is enabled, then it sets the color as a pure red instead.

  1. var material = SpatialMaterial.new()
  2. material.albedo_color = color

This part makes a new SpatialMaterial that is the imported resource. We create a new instance of it and then set its albedo color as the value we got before.

  1. return ResourceSaver.save("%s.%s" % [save_path, get_save_extension()], material)

This is the last part and quite an important one, because here we save the made resource to the disk. The path of the saved file is generated and informed by the editor via the save_path parameter. Note that this comes without the extension, so we add it using string formatting. For this we call the get_save_extension method that we defined earlier, so we can be sure that they won’t get out of sync.

We also return the result from the ResourceSaver.save() method, so if there’s an error in this step, the editor will know about it.

平台变体和生成的文件

您可能已经注意到我们的插件忽略了 import 方法的两个参数。 那些是* return arguments *(因此在它们名称的开头是 r ),这意味着编辑器会在调用您的import方法之后读取它们。 它们都是可以填充信息的数组。

The r_platform_variants argument is used if you need to import the resource differently depending on the target platform. While it’s called platform variants, it is based on the presence of feature tags, so even the same platform can have multiple variants depending on the setup.

要导入平台变体,需要在扩展名之前使用feature标记保存它,然后将标记推送到 r_platform_variants 数组,以便编辑可以知道您做了。

For example, let’s say we save a different material for a mobile platform. We would need to do something like the following:

  1. r_platform_variants.push_back("mobile")
  2. return ResourceSaver.save("%s.%s.%s" % [save_path, "mobile", get_save_extension()], mobile_material)

``r_gen_files``参数用于在导入过程中生成并需要保留的额外文件。 编辑器将查看它以了解依赖关系并确保不会无意中删除额外文件。

这也是一个数组,应该填充您保存的文件的完整路径。 例如,让我们为下一个传递创建另一个材质并将其保存在不同的文件中:

  1. var next_pass = SpatialMaterial.new()
  2. next_pass.albedo_color = color.inverted()
  3. var next_pass_path = "%s.next_pass.%s" % [save_path, get_save_extension()]
  4. err = ResourceSaver.save(next_pass_path, next_pass)
  5. if err != OK:
  6. return err
  7. r_gen_files.push_back(next_pass_path)

试试这个插件

这是理论上的,但是现在导入插件已经完成了,让我们来测试一下。 确保您创建了示例文件(包含介绍部分中描述的内容)并将其另存为 test.mtxt 。 然后在“项目设置”中激活插件。

如果一切顺利,导入插件将添加到编辑器中并扫描文件系统,使自定义资源显示在FileSystem基座上。 如果选择它并聚焦导入面板,则可以看到选择该选项的唯一选项。

在场景中创建一个MeshInstance节点,并为其Mesh属性设置一个新的SphereMesh。 在属性面板中展开“材质”部分,然后将文件从“文件系统”面板拖动到材质属性。 对象将在视区中使用导入材质的蓝色进行更新。

../../../_images/import_plugin_trying.png

转到导入面板,启用“强制使用红色”选项,然后单击“重新导入”。 这将更新导入的材质,并应该自动更新显示红色的视图。

就是这样! 您的第一个导入插件已经完成! 现在获得创意并为您自己心爱的格式制作插件。 这对于以自定义格式编写数据然后在Godot中使用它就像它们是本机资源一样非常有用。 这显示了导入系统如何强大和可扩展。