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.

Importieren von Plugins

Bemerkung

In diesem Tutorial wird davon ausgegangen, dass Sie bereits wissen, wie man generische Plugins erstellt. Im Zweifelsfall lesen Sie bitte die Seite Erstellen von Plugins. Dies setzt auch voraus, dass Sie mit dem Importsystem von Godot vertraut sind.

Einführung

Ein Import-Plugin ist eine spezielle Art von Editor-Tool, mit dem benutzerdefinierte Ressourcen von Godot importiert und als First-Class-Ressourcen behandelt werden können. Der Editor selbst wird mit vielen Import-Plugins geliefert, die gängige Ressourcen wie PNG Bilder, Collada und glTF Modelle, Ogg Vorbis Sounds und vieles mehr verarbeiten.

Dieses Tutorial zeigt Ihnen, wie Sie ein einfaches Import-Plugin erstellen, um eine benutzerdefinierte Textdatei als Materialressource zu laden. Diese Textdatei enthält drei durch Komma getrennte numerische Werte zur Darstellung der drei Kanäle einer Farbe. Die resultierende Farbe wird als Albedo (Hauptfarbe) des importierten Materials verwendet. In diesem Beispiel enthält es die reine blaue Farbe (null Rot, null Grün und volles Blau):

0,0,255

Konfiguration

Zuerst brauchen wir ein generisches Plugin, das die Initialisierung und Zerstörung unseres Import-Plugins übernimmt. Fügen wir zuerst die Datei plugin.cfg hinzu:

[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"

Dann brauchen wir die Datei material_import.gd, um das Import-Plugin bei Bedarf hinzuzufügen und zu entfernen:

# 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

Wenn dieses Plugin aktiviert wird, erzeugt es eine neue Instanz des Import-Plugins (das wir bald erstellen werden) und fügt es dem Editor mit der Methode add_import_plugin() hinzu. Wir speichern eine Referenz darauf in einem Klassen-Member import_plugin, damit wir später beim Entfernen darauf verweisen können. Die Methode remove_import_plugin() wird aufgerufen, wenn das Plugin deaktiviert wird, um den Speicher aufzuräumen und den Editor wissen zu lassen, dass das Import-Plugin nicht mehr verfügbar ist.

Beachten Sie, dass das Import-Plugin ein Referenztyp ist, so dass es nicht explizit mit der Funktion free() aus dem Speicher freigegeben werden muss. Es wird automatisch von der Engine freigegeben, wenn es den Gültigkeitsbereich verlässt.

Die EditorImportPlugin-Klasse

Der Hauptcharakter der Darbietung ist die EditorImportPlugin-Klasse. Sie ist für die Implementierung der Methoden verantwortlich, die von Godot aufgerufen werden, wenn es wissen muss, wie es mit Dateien umgehen soll.

Beginnen wir mit der Programmierung unseres Plugins, eine Methode nach der anderen:

# import_plugin.gd
@tool
extends EditorImportPlugin


func _get_importer_name():
    return "demos.sillymaterial"

Die erste Methode ist die _get_importer_name(). Dies ist ein eindeutiger Name für Ihr Plugin, der von Godot verwendet wird, um zu wissen, welcher Import in einer bestimmten Datei verwendet wurde. Wenn die Dateien erneut importiert werden müssen, weiß der Editor, welches Plugin er aufrufen muss.

func _get_visible_name():
    return "Silly Material"

Die Methode _get_visible_name() ist für die Rückgabe des Namens des importierten Typs verantwortlich, der dem Benutzer im Import-Dock angezeigt wird.

Sie sollten diesen Namen als Fortsetzung von "Importieren als" wählen, z. B. "Importieren als Quatsch-Material ". Sie können es benennen, wie Sie wollen, aber wir empfehlen einen beschreibenden Namen für Ihr Plugin.

func _get_recognized_extensions():
    return ["mtxt"]

Das Importsystem von Godot erkennt Dateitypen anhand ihrer Erweiterung. In der Methode _get_recognized_extensions() geben Sie ein Array von Strings zurück, um jede Erweiterung darzustellen, die dieses Plugin verstehen kann. Wenn eine Erweiterung von mehr als einem Plugin erkannt wird, kann der Benutzer auswählen, welches beim Importieren der Dateien verwendet werden soll.

Tipp

Gängige Erweiterungen wie .json und .txt werden möglicherweise von vielen Plugins verwendet. Außerdem könnte es Dateien im Projekt geben, die nur Daten für das Spiel sind und nicht importiert werden sollten. Sie müssen beim Importieren vorsichtig sein, um die Daten zu validieren. Erwarten Sie niemals, dass die Datei wohlgeformt ist.

func _get_save_extension():
    return "material"

Die importierten Dateien werden im Ordner .import im Stammverzeichnis des Projekts gespeichert. Ihre Erweiterung sollte mit dem Typ der Ressource übereinstimmen, die Sie importieren, aber da Godot nicht sagen kann, was Sie verwenden wollen (weil es mehrere gültige Erweiterungen für dieselbe Ressource geben könnte), müssen Sie angeben, was beim Import verwendet wird.

Da wir ein Material importieren, werden wir die spezielle Erweiterung für solche Ressourcentypen verwenden. Wenn Sie eine Szene importieren, können Sie scn verwenden. Generische Ressourcen können die Erweiterung res verwenden. Dies wird jedoch von der Engine in keiner Weise erzwungen.

func _get_resource_type():
    return "StandardMaterial3D"

Die importierte Ressource hat einen bestimmten Typ, damit der Editor weiß, zu welchem Property-Slot sie gehört. Dies ermöglicht die Verwendung von Drag&Drop aus dem Dateisystem-Panel auf eine Property im Inspector.

In unserem Fall handelt es sich um ein StandardMaterial3D, das auf 3D-Objekte angewendet werden kann.

Bemerkung

Wenn Sie verschiedene Typen aus derselben Erweiterung importieren, müssen Sie mehrere Import-Plugins erstellen. Sie können den Import-Code auf eine andere Datei abstrahieren, um diesbezüglich Doppelarbeit zu vermeiden.

Optionen und Vorgaben

Ihr Plugin kann verschiedene Optionen bereitstellen, mit denen der Benutzer steuern kann, wie die Ressource importiert werden soll. Wenn eine Menge ausgewählter Optionen üblich ist, können Sie auch verschiedene Vorgaben erstellen, um es dem Benutzer zu erleichtern. Das folgende Bild zeigt, wie die Optionen im Editor angezeigt werden:

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

Da es viele Vorgaben geben kann und diese mit einer Nummer identifiziert werden, ist es eine gute Praxis, ein Enum zu verwenden, damit Sie über Namen auf sie verweisen können.

@tool
extends EditorImportPlugin


enum Presets { DEFAULT }


...

Da das Enum nun definiert ist, lassen Sie uns die Methoden eines Import-Plugins weiter betrachten:

func _get_preset_count():
    return Presets.size()

Die Methode _get_preset_count() gibt die Anzahl der Vorgaben zurück, die dieses Plugin definiert. Wir haben jetzt nur eine Vorgabe, aber wir können diese Methode zukunftssicher machen, indem wir die Größe unseres Vorgaben-Enums zurückgeben.

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

Hier haben wir die Methode _get_preset_name(), die den Vorgaben Namen gibt, wie sie dem Benutzer präsentiert werden, also achten Sie darauf, kurze und klare Namen zu verwenden.

Wir können hier die match-Anweisung verwenden, um den Code besser zu strukturieren. Auf diese Weise ist es einfach, in Zukunft neue Vorgaben hinzuzufügen. Wir verwenden das Catch-All-Schema, um auch etwas zurückzugeben. Obwohl Godot nicht nach Vorgaben jenseits der von Ihnen definierten Vorgaben-Anzahl fragen wird, ist es immer besser, auf Nummer sicher zu gehen.

Wenn Sie nur eine Vorgabe haben, können Sie den Namen einfach direkt zurückgeben. Wenn Sie dies jedoch tun, müssen Sie beim Hinzufügen mehrerer Vorgaben vorsichtig sein.

func _get_import_options(path, preset_index):
    match preset_index:
        Presets.DEFAULT:
            return [{
                       "name": "use_red_anyway",
                       "default_value": false
                    }]
        _:
            return []

Dies ist die Methode, in der die vorhandenen Optionen definiert werden. _get_import_options() gibt ein Array von Dictionarys zurück, und jedes Dictionary enthält ein paar Keys, die überprüft werden, um die Option, die dem Benutzer angezeigt wird, anzupassen. Die folgende Tabelle zeigt die möglichen Keys:

Key

Typ

Beschreibung

name

String

Der Name der Option. Unterstriche werden zu Leerzeichen und Anfangsbuchstaben werden groß geschrieben.

default_value

beliebig

Der Default-Wert der Option für diese Vorgabe.

property_hint

Enum-Wert

Einer der PropertyHint-Werte, der als Hint verwendet werden soll.

hint_string

String

Der Hint-Text der Property. Das gleiche, wie Sie es in der Anweisung export in GDScript hinzufügen würden.

usage

Enum-Wert

Einer der PropertyUsageFlags-Werte, um die Verwendung zu definieren.

Die Keys name und default_value müssen angegeben werden, der Rest ist optional.

Beachten Sie, dass die Methode _get_import_options die Nummer der Vorgabe erhält, so dass Sie die Optionen für jede einzelne Vorgabe konfigurieren können (insbesondere den Default-Wert). In diesem Beispiel verwenden wir die match-Anweisung, aber wenn Sie viele Optionen haben und die Vorgaben nur den Wert ändern, möchten Sie vielleicht zuerst das Array der Optionen erstellen und es dann basierend auf der Vorgabe ändern.

Warnung

Die Methode _get_import_options wird auch dann aufgerufen, wenn Sie keine Vergaben definiert haben (indem Sie _get_preset_count auf Null setzen). Sie müssen ein Array zurückgeben, auch wenn es leer ist, sonst können Sie Fehler erhalten.

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

Für die Methode _get_option_visibility() geben wir einfach true zurück, weil alle unsere Optionen (d.h. die einzige, die wir definiert haben) immer sichtbar sind.

Wenn Sie eine bestimmte Option nur sichtbar machen müssen, wenn eine andere mit einem bestimmten Wert belegt ist, können Sie die Logik in dieser Methode hinzufügen.

Die import-Methode

Der Hauptteil des Prozesses, der für die Umwandlung der Dateien in Ressourcen verantwortlich ist, wird von der Methode _import() abgedeckt. Unser Beispielcode ist ein bisschen lang, also teilen wir ihn in ein paar Teile auf:

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

Der erste Teil unserer Importmethode öffnet und liest die Quelldatei. Dazu verwenden wir die Klasse FileAccess und übergeben den Parameter source_file, der vom Editor bereitgestellt wird.

Wenn beim Öffnen der Datei ein Fehler auftritt, geben wir diesen zurück, um dem Editor mitzuteilen, dass der Import nicht erfolgreich war.

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

Dieser Code nimmt die Zeile der Datei, die er zuvor gelesen hat, und teilt sie in Teile auf, die durch ein Komma getrennt sind. Wenn mehr oder weniger als die drei Werte vorhanden sind, betrachtet er die Datei als ungültig und meldet einen Fehler.

Dann erzeugt er eine neue Variable Color und setzt ihre Werte entsprechend der Eingabedatei. Wenn die Option use_red_anyway aktiviert ist, dann setzt er die Farbe stattdessen als reines Rot.

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

Dieser Teil erstellt ein neues StandardMaterial3D, das die importierte Ressource ist. Wir erstellen eine neue Instanz davon und setzen dann seine Albedo-Farbe als den Wert, den wir zuvor erhalten haben.

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

Dies ist der letzte Teil und ein ziemlich wichtiger, denn hier wird die erstellte Ressource auf der Festplatte gespeichert. Der Pfad der gespeicherten Datei wird generiert und vom Editor über den Parameter save_path mitgeteilt. Beachten Sie, dass dieser Parameter ohne Erweiterung bereitgestellt wird, also fügen wir ihn mit String-Formatierung hinzu. Dazu rufen wir die Methode _get_save_extension auf, die wir zuvor definiert haben, so dass wir sicher sein können, dass sie nicht aus dem Takt geraten.

Wir geben auch das Ergebnis der Methode ResourceSaver.save() zurück, so dass der Editor weiß, wenn in diesem Schritt ein Fehler auftritt.

Plattformvarianten und generierte Dateien

Sie haben vielleicht bemerkt, dass unser Plugin zwei Argumente der Methode import ignoriert hat. Diese sind Rückgabe-Argumente (daher das r am Anfang ihres Namens), was bedeutet, dass der Editor nach dem Aufruf Ihrer Import-Methode aus ihnen liest. Beides sind Arrays, die Sie mit Informationen füllen können.

Das Argument r_platform_variants wird verwendet, wenn Sie die Ressource je nach Zielplattform unterschiedlich importieren müssen. Während es Plattform-Varianten genannt wird, basiert es auf dem Vorhandensein von Feature-Tags, so dass sogar die gleiche Plattform je nach Einrichtung mehrere Varianten haben kann.

Um eine Plattformvariante zu importieren, müssen Sie sie mit dem Feature-Tag vor der Erweiterung speichern und dann das Tag in das Array r_platform_variants schieben, damit der Editor weiß, dass Sie es getan haben.

Angenommen wir speichern ein anderes Material für eine mobile Plattform. Wir müssten so etwas wie das Folgende tun:

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

Das Argument r_gen_files ist für zusätzliche Dateien gedacht, die während des Importprozesses erzeugt werden und beibehalten werden müssen. Der Editor wird sie sich ansehen, um die Abhängigkeiten zu verstehen und sicherzustellen, dass die zusätzliche Datei nicht versehentlich gelöscht wird.

Dies ist auch ein Array und sollte mit vollständigen Pfaden der von Ihnen gespeicherten Dateien gefüllt sein. Als Beispiel erstellen wir ein anderes Material für den nächsten Durchgang und speichern es in einer anderen Datei:

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)

Ausprobieren des Plugins

Soweit die Theorie, aber jetzt, wo das Import-Plugin fertig ist, wollen wir es testen. Stellen Sie sicher, dass Sie die Beispieldatei (mit dem in der Einleitung beschriebenen Inhalt) erstellt haben und speichern Sie sie als test.mtxt. Dann aktivieren Sie das Plugin in den Projekteinstellungen.

Wenn alles gut geht, wird das Import-Plugin zum Editor hinzugefügt und das Dateisystem wird gescannt, so dass die benutzerdefinierte Ressource im Dateisystem-Dock erscheint. Wenn Sie sie auswählen und das Import-Dock fokussieren, sehen Sie dort die einzige Option, die Sie auswählen können.

Erstellen Sie einen MeshInstance3D-Node in der Szene und legen Sie für dessen Mesh-Property ein neues SphereMesh an. Klappen Sie den Abschnitt Material im Inspektor aus und ziehen Sie die Datei aus dem Dateisystem-Dock auf die Material-Property. Das Objekt wird im Viewport mit der blauen Farbe des importierten Materials aktualisiert.

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

Gehen Sie zum Import-Dock, aktivieren Sie die Option "Rot trotzdem verwenden" und klicken Sie auf "Neu importieren". Dadurch wird das importierte Material aktualisiert und sollte automatisch die Ansicht aktualisieren, die stattdessen die rote Farbe anzeigt.

Und das war's! Ihr erstes Import-Plugin ist fertig! Werden Sie jetzt kreativ und erstellen Sie Plugins für Ihre eigenen geliebten Formate. Dies kann sehr nützlich sein, um Ihre Daten in einem benutzerdefinierten Format zu schreiben und sie dann in Godot zu verwenden, als wären es native Ressourcen. Dies zeigt, wie leistungsfähig und erweiterbar das Importsystem ist.