Erweiterungen importieren

Bemerkung

In dieser Anleitung wird davon ausgegangen, dass Sie bereits wissen wie man generische Plugins erstellt. Im Zweifelsfall lesen Sie bitte die Seite Erweiterungen (Plugins) erstellen. 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 erstklassige Ressourcen behandelt werden können. Der Editor selbst wird mit vielen Import-Plugins geliefert, die die gängigen Ressourcen wie PNG Bilder, Collada und glTF Modelle, Ogg Vorbis Sounds und vieles mehr verarbeiten.

Diese Anleitung 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, die die drei Kanäle einer Farbe darstellen. 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 Klassenmitglied 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

Die Hauptfigur der Show ist die EditorImportPlugin-Klasse. Sie ist für die Implementierung der Methoden verantwortlich, die von Godot aufgerufen werden, wenn sie wissen muss, wie sie 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 und wird dem Benutzer im Import-Panel angezeigt.

Sie sollten diesen Namen als Fortsetzung von "Importieren als" wählen, z. B. "Importieren als Silly 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, die jede Erweiterung darstellen, 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 werden (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 "SpatialMaterial"

Die importierte Ressource hat einen bestimmten Typ, damit der Editor weiß, zu welchem Eigenschafts-Slot sie gehört. Dies ermöglicht das Ziehen und Ablegen aus dem FileSystem-Panel auf eine Eigenschaft im Inspector.

In unserem Fall handelt es sich um ein :ref:``class_SpatialMaterial`, 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 Importcode auf eine andere Datei abstrahieren, um diesbezüglich Doppelarbeit zu vermeiden.

Optionen und Voreinstellungen

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 Presets 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 Presets 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 Presets zurück, die dieses Plugin definiert. Wir haben jetzt nur ein Preset, aber wir können diese Methode zukunftssicher machen, indem wir die Größe unserer Presets-Aufzählung zurückgeben.

func get_preset_name(preset):
    match preset:
        Presets.DEFAULT:
            return "Default"
        _:
            return "Unknown"

Hier haben wir die Methode get_preset_name(), die den Presets 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 Presets hinzuzufügen. Wir verwenden das Catch-All-Schema, um auch etwas zurückzugeben. Obwohl Godot nicht nach Presets jenseits der von Ihnen definierten Preset-Anzahl fragen wird, ist es immer besser, auf Nummer sicher zu gehen.

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

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

Dies ist die Methode, die die verfügbaren Optionen definiert. get_import_options() gibt ein Array von Dictionaries zurück, und jedes Dictionary enthält einige Schlüssel, die überprüft werden, um die Option so anzupassen, wie sie dem Benutzer angezeigt wird. Die folgende Tabelle zeigt die möglichen Schlüssel:

Schlüssel

Art

Beschreibung

name

Zeichenkette

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

default_value

beliebig

Der Standardwert der Option für diese Voreinstellung.

property_hint

Aufzählungswert

Einer der PropertyHint Werte, der als Hinweis verwendet werden soll.

hint_string

Zeichenkette

Der Hinweistext der Eigenschaft. Das gleiche, wie Sie es in der Anweisung export in GDScript hinzufügen würden.

usage

Aufzählungswert

Einer der PropertyUsageFlags Werte, um die Verwendung zu definieren.

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

Beachten Sie, dass die Methode get_import_options die Preset-Nummer erhält, so dass Sie die Optionen für jedes verschiedene Preset konfigurieren können (insbesondere den Standardwert). In diesem Beispiel verwenden wir die Anweisung match, aber wenn Sie viele Optionen haben und die Voreinstellungen nur den Wert ändern, möchten Sie vielleicht zuerst das Array mit den Optionen erstellen und es dann basierend auf dem Preset ändern.

Warnung

Die Methode get_import_options wird auch dann aufgerufen, wenn Sie keine Presets definiert haben (indem Sie get_preset_count 0 zurück geben lassen). Sie müssen ein Array zurückgeben, auch wenn es leer ist, sonst können Sie Fehler erhalten.

func get_option_visibility(option, 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 schwere Teil 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 = File.new()
    var err = file.open(source_file, File.READ)
    if err != OK:
        return err

    var line = file.get_line()

    file.close()

Der erste Teil unserer Importmethode öffnet und liest die Quelldatei. Wir verwenden dazu die Klasse File 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 es eine neue Variable Color und setzt ihre Werte entsprechend der Eingabedatei. Wenn die Option use_red_anyway aktiviert ist, dann setzt es die Farbe stattdessen als reines Rot.

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

Dieser Teil erstellt ein neues SpatialMaterial, als importierte Ressource. Wir erstellen eine neue Instanz davon und setzen dann die Albedofarbe als den Wert, den wir zuvor erhalten haben.

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

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 ohne die Erweiterung kommt, also fügen wir sie 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 bemeraufkt, 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 tun:

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

The r_gen_files argument is meant for extra files that are generated during your import process and need to be kept. The editor will look at it to understand the dependencies and make sure the extra file is not inadvertently deleted.

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 = SpatialMaterial.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_path, next_pass)
if err != OK:
    return err
r_gen_files.push_back(next_pass_path)

Das Plugin ausprobieren

This has been theoretical, but now that the import plugin is done, let's test it. Make sure you created the sample file (with the contents described in the introduction section) and save it as test.mtxt. Then activate the plugin in the Project Settings.

If everything goes well, the import plugin is added to the editor and the file system is scanned, making the custom resource appear on the FileSystem dock. If you select it and focus the Import dock, you can see the only option to select there.

Create a MeshInstance node in the scene, and for its Mesh property set up a new SphereMesh. Unfold the Material section in the Inspector and then drag the file from the FileSystem dock to the material property. The object will update in the viewport with the blue color of the imported material.

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

Go to Import dock, enable the "Use Red Anyway" option, and click on "Reimport". This will update the imported material and should automatically update the view showing the red color instead.

And that's it! Your first import plugin is done! Now get creative and make plugins for your own beloved formats. This can be quite useful to write your data in a custom format and then use it in Godot as if they were native resources. This shows how the import system is powerful and extendable.