Work in progress

The content of this page was not yet updated for Godot 4.6 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 и многое другое.

В этом руководстве показано, как создать плагин импорта для загрузки пользовательского текстового файла в качестве ресурса материала. Этот текстовый файл будет содержать три числовых значения, разделённых запятой, которые представляют три канала цвета, а полученный цвет будет использоваться в качестве альбедо (основного цвета) импортируемого материала. В этом примере он содержит чистый синий цвет (без красного, без зелёного и полностью синий):

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 class. Он отвечает за реализацию методов, вызываемых Godot, когда ему требуется информация о том, как работать с файлами.

Давайте начнем кодировать наш плагин, по одному методу за раз:

# import_plugin.gd
@tool
extends EditorImportPlugin


func _get_importer_name():
    return "demos.sillymaterial"

Первый метод — это _get_importer_name(). Это уникальное имя вашего плагина, которое Godot использует, чтобы определить, какой импорт был использован в определённом файле. При необходимости повторного импорта файлов редактор будет знать, какой плагин вызывать.

func _get_visible_name():
    return "Silly Material"

Метод _get_visible_name() отвечает за возврат имени импортируемого им типа, и оно будет показано пользователю в доке импорта.

Вам следует выбрать это имя как продолжение "Import as", например, «Импортировать как Silly Material». Вы можете назвать его как угодно, но мы рекомендуем использовать описательное имя для вашего плагина.

func _get_recognized_extensions():
    return ["mtxt"]

Система импорта Godot определяет типы файлов по их расширению. В методе _get_recognized_extensions() возвращается массив строк, представляющих каждое расширение, которое распознаёт этот плагин. Если расширение распознаётся несколькими плагинами, пользователь может выбрать, какой из них использовать при импорте файлов.

Совет

Распространённые расширения, такие как .json и .txt, могут использоваться многими плагинами. Кроме того, в проекте могут быть файлы, которые содержат только данные для игры и не подлежат импорту. При импорте необходимо соблюдать осторожность и проверять данные. Не рассчитывайте на корректность файла.

func _get_save_extension():
    return "material"

Импортированные файлы сохраняются в папке .import в корне проекта. Их расширение должно соответствовать типу импортируемого ресурса, но поскольку Godot не может определить, какой тип файла вы будете использовать (поскольку для одного и того же ресурса может быть несколько допустимых расширений), вам необходимо указать, какой тип файла будет использоваться при импорте.

Поскольку мы импортируем материал, мы будем использовать специальное расширение для таких типов ресурсов. При импорте сцены можно использовать scn. Универсальные ресурсы могут использовать расширение res. Однако движок никак не навязывает это.

func _get_resource_type():
    return "StandardMaterial3D"

Импортированный ресурс имеет определённый тип, поэтому редактор может определить, к какому слоту свойств он принадлежит. Это позволяет перетаскивать его из дока файловой системы в свойство в инспекторе.

В нашем случае это StandardMaterial3D, который можно применять к 3D-объектам.

Примечание

Если вам нужно импортировать разные типы данных из одного расширения, необходимо создать несколько плагинов импорта. Вы можете абстрагировать код импорта в отдельный файл, чтобы избежать дублирования.

Опции и предустановки

Ваш плагин может предоставлять различные параметры, позволяющие пользователю управлять импортом ресурса. Если набор выбранных параметров является общим, вы также можете создать различные предустановки, чтобы упростить работу пользователя. На следующем изображении показано, как эти параметры будут выглядеть в редакторе:

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

Поскольку предустановок может быть много и они идентифицируются по номеру, хорошей практикой является использование перечисления, чтобы можно было ссылаться на них по именам.

@tool
extends EditorImportPlugin


enum Presets { DEFAULT }


...

Теперь, когда перечисление определено, давайте продолжим рассмотрение методов плагина импорта:

func _get_preset_count():
    return Presets.size()

Метод _get_preset_count() возвращает количество предустановок, определяемых этим плагином. Сейчас у нас только одна предустановка, но мы можем сделать этот метод перспективным, возвращая размер перечисления Presets.

func _get_preset_name(preset_index):
    match preset_index:
        Presets.DEFAULT:
            return "Default"
        _:
            return "Unknown"

Здесь у нас есть метод _get_preset_name(), который дает имена предустановкам, которые будут представлены пользователю, поэтому обязательно используйте короткие и понятные имена.

Мы можем использовать оператор 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 []

Этот метод определяет доступные параметры. _get_import_options() возвращает массив словарей, каждый из которых содержит несколько ключей, проверяемых для настройки параметра, отображаемого пользователю. В следующей таблице представлены возможные ключи:

Ключ

Тип

Описание

name

Строка

Название параметра. При отображении подчёркивания заменяются пробелами, а первые буквы становятся заглавными.

default_value

Любой

Значение параметра по умолчанию для данной настройки.

property_hint

Значение Перечисления (Enum)

Одно из значений PropertyHint для использования в качестве подсказки.

hint_string

Строка

Текст подсказки свойства. Тот же, что вы бы добавили в оператор export в GDScript.

usage

Значение Перечисления (Enum)

Одно из значений PropertyUsageFlags для определения использования.

Ключи name и default_value являются обязательными, остальные — необязательными.

Обратите внимание, что метод _get_import_options получает номер предустановки, поэтому вы можете настроить параметры для каждой отдельной предустановки (особенно значение по умолчанию). В этом примере мы используем оператор match, но если у вас много параметров, а предустановки изменяют только значение, вы можете сначала создать массив параметров, а затем изменить его на основе предустановки.

Предупреждение

Метод _get_import_options вызывается, даже если вы не определили предустановки (заставив _get_preset_count возвращать ноль). Вам необходимо возвращать массив, даже если он пустой, иначе возможны ошибки.

func _get_option_visibility(path, option_name, options):
    return true

Для метода _get_option_visibility() мы просто возвращаем true, потому что все наши параметры (т. е. единственный, который мы определили) видны все время.

Если вам нужно сделать определенную опцию видимой только в том случае, если для другой опции установлено определенное значение, вы можете добавить логику в этот метод.

Метод import

Самая тяжёлая часть процесса, отвечающая за преобразование файлов в ресурсы, выполняется методом _import(). Наш пример кода довольно длинный, поэтому давайте разделим его на несколько частей:

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()

Первая часть нашего метода импорта открывает и считывает исходный файл. Для этого мы используем класс FileAccess, передавая параметр source_file, предоставляемый редактором.

Если при открытии файла возникла ошибка, мы возвращаем ее, чтобы сообщить редактору, что импорт не был успешным.

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

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

Этот код берёт строку файла, которую он читал ранее, и разбивает её на части, разделённые запятой. Если количество значений больше или меньше трёх, файл считается недействительным и выдаёт ошибку.

Затем он создаёт новую переменную Color и устанавливает её значения в соответствии с входным файлом. Если включена опция use_red_anyway, то цвет устанавливается как чисто красный.

var material = StandardMaterial3D.new()
material.albedo_color = color

Эта часть создаёт новый StandardMaterial3D, представляющий собой импортированный ресурс. Мы создаём его новый экземпляр и устанавливаем его цвет альбедо равным значению, полученному ранее.

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

Это последняя и весьма важная часть, поскольку здесь мы сохраняем созданный ресурс на диск. Путь к сохранённому файлу генерируется и указывается редактором через параметр save_path. Обратите внимание, что он идёт без расширения, поэтому мы добавляем его с помощью string formatting. Для этого мы вызываем метод _get_save_extension, который мы определили ранее, чтобы гарантировать отсутствие рассинхронизации.

Мы также возвращаем результат из метода ResourceSaver.save(), поэтому, если на этом этапе возникнет ошибка, редактор узнает об этом.

Варианты платформы и сгенерированные файлы

Вы могли заметить, что наш плагин проигнорировал два аргумента метода import. Это возвращаемые аргументы (отсюда и r в начале их названия), что означает, что редактор будет читать их после вызова вашего метода импорта. Оба аргумента представляют собой массивы, которые можно заполнить информацией.

Аргумент r_platform_variants используется, если вам нужно импортировать ресурс по-разному в зависимости от целевой платформы. Хотя это называется platform вариантами, он основан на наличии feature tags, поэтому даже для одной платформы может быть несколько вариантов в зависимости от настроек.

Чтобы импортировать вариант платформы, вам необходимо сохранить его с тегом функции перед расширением, а затем поместить тег в массив r_platform_variants, чтобы редактор мог это узнать.

Например, если мы сохраняем другой материал для мобильной платформы, нам нужно сделать что-то вроде следующего:

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

Аргумент r_gen_files предназначен для дополнительных файлов, создаваемых в процессе импорта и требующих сохранения. Редактор проверит его, чтобы определить зависимости и убедиться, что дополнительный файл не будет случайно удалён.

Это тоже массив, который следует заполнить полными путями к сохраняемым файлам. В качестве примера давайте создадим ещё один материал для следующего прохода и сохраним его в другом файле:

var next_pass = StandardMaterial3D.new()
next_pass.albedo_color = color.inverted()
var next_pass_path = "%s.next_pass.%s" % [save_path, _get_save_extension()]

err = ResourceSaver.save(next_pass, next_pass_path)
if err != OK:
    return err
r_gen_files.push_back(next_pass_path)

Пробуем плагин

Это было теоретическим предположением, но теперь, когда плагин импорта готов, давайте его протестируем. Убедитесь, что вы создали файл-пример (с содержимым, описанным во введении), и сохраните его как test.mtxt. Затем активируйте плагин в настройках проекта.

Если всё пройдёт успешно, плагин импорта будет добавлен в редактор, а файловая система будет просканирована, после чего пользовательский ресурс появится в доке FileSystem. Если выбрать его и сделать фокус на доке Import, вы увидите там единственный пункт для выбора.

Создайте в сцене узел MeshInstance3D и для его свойства Mesh настройте новый SphereMesh. Разверните раздел Material в Inspector и перетащите файл из дока FileSystem в свойство материала. Объект обновится в области просмотра, приняв синий цвет импортированного материала.

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

Перейдите в раздел Import, включите опцию "Use Red Anyway" и нажмите "Reimport". Это обновит импортированный материал и автоматически обновит представление, отображающее красный цвет.

Вот и всё! Ваш первый плагин импорта готов! Теперь проявите креативность и создайте плагины для ваших любимых форматов. Это может быть очень полезно для записи данных в пользовательском формате и последующего использования их в Godot как нативных ресурсов. Это демонстрирует, насколько мощна и расширяема система импорта.