Work in progress

The content of this page was not yet updated for Godot 4.2 and may be outdated. If you know how to improve this page or you can confirm that it's up to date, feel free to open a pull request.

导入插件

备注

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

前言

导入插件是一种特殊的编辑器工具, 它允许Godot导入自定义资源, 并将其作为一级资源对待. 编辑器本身捆绑了很多导入插件来处理常见的资源, 如PNG图片, Collada和glTF模型, Ogg Vorbis声音等等.

This tutorial shows how to create an import plugin to load a custom text file as a material resource. This text file will contain three numeric values separated by comma, which represents the three channels of a color, and the resulting color will be used as the albedo (main color) of the imported material. In this example it contains the pure blue color (zero red, zero green, and full blue):

0,0,255

配置

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

[plugin]

name="Silly Material Importer"
description="Imports a 3D Material from an external text file."
author="Yours Truly"
version="1.0"
script="material_import.gd"

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

# material_import.gd
@tool
extends EditorPlugin


var import_plugin


func _enter_tree():
    import_plugin = preload("import_plugin.gd").new()
    add_import_plugin(import_plugin)


func _exit_tree():
    remove_import_plugin(import_plugin)
    import_plugin = null

当这个插件被激活时, 它将创建一个新的导入插件实例(我们很快就会制作), 并使用 add_import_plugin() 方法将其加入编辑器. 我们在类成员 import_plugin' 中存储它的引用, 这样我们就可以在以后删除它时引用它. remove_import_plugin() 方法在插件停用时被调用, 以清理内存并让编辑器知道导入插件不再可用.

注意, 导入插件是一个引用类型, 所以它不需要明确地用 free() 函数从内存中释放. 当它超出范围时, 将被引擎自动释放.

EditorImportPlugin 类

这个展示的主角是 EditorImportPlugin 类. 它负责实现Godot需要知道如何处理文件时调用的方法.

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

# import_plugin.gd
@tool
extends EditorImportPlugin


func _get_importer_name():
    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.

func _get_visible_name():
    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.

你选择的名字应该可以接到“导入为”后面,例如“导入为 Silly Material”。你可以随心所欲地命名,但我们建议为你的插件起一个描述性的名字。

func _get_recognized_extensions():
    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 这样的常见扩展. 此外, 项目中可能存在仅作为游戏数据的文件, 不应导入. 导入时必须小心以验证数据. 永远不要指望文件格式正确.

func _get_save_extension():
    return "material"

导入的文件被保存在项目根部的 .import 文件夹中. 它们的扩展名应与你要导入的资源类型相匹配, 但由于Godot不能告诉你将使用什么(因为同一资源可能有多个有效的扩展名), 你需要声明将在导入时使用的内容.

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

func _get_resource_type():
    return "StandardMaterial3D"

导入的资源具有特定类型,编辑器可以据此知道它属于哪个属性槽。这样就能够将其从文件系统面板拖放到检查器的属性之中。

In our case it's a StandardMaterial3D, which can be applied to 3D objects.

备注

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

选项和预设

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

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

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

@tool
extends EditorImportPlugin


enum Presets { DEFAULT }


...

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

func _get_preset_count():
    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.

func _get_preset_name(preset_index):
    match preset_index:
        Presets.DEFAULT:
            return "Default"
        _:
            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不会要求超出你定义的预设计数的预设, 但最好是安全起见.

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

func _get_import_options(path, preset_index):
    match preset_index:
        Presets.DEFAULT:
            return [{
                       "name": "use_red_anyway",
                       "default_value": false
                    }]
        _:
            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

枚举值

PropertyHint 中的一个值, 作为提示使用.

hint_string

字符串

属性的提示文本. 与你在GDScript中的 export 语句中添加相同.

usage

枚举值

PropertyUsageFlags 中的一个值来定义用途.

namedefault_value 键是 强制 , 其余是可选的.

Note that the _get_import_options method receives the preset number, so you can configure the options for each different preset (especially the default value). In this example we use the match statement, but if you have lots of options and the presets only change the value you may want to create the array of options first and then change it based on the preset.

警告

The _get_import_options method is called even if you don't define presets (by making _get_preset_count return zero). You have to return an array even it's empty, otherwise you can get errors.

func _get_option_visibility(path, option_name, options):
    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:

func _import(source_file, save_path, options, r_platform_variants, r_gen_files):
    var file = FileAccess.open(source_file, FileAccess.READ)
    if file == null:
        return FileAccess.get_open_error()

    var line = file.get_line()

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

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

var channels = line.split(",")
if channels.size() != 3:
    return ERR_PARSE_ERROR

var color
if options.use_red_anyway:
    color = Color8(255, 0, 0)
else:
    color = Color8(int(channels[0]), int(channels[1]), int(channels[2]))
</