Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

GDExtension C++-Beispiel

Einführung

Die C++-Bindings für GDExtension bauen auf der C GDExtension-API auf und bieten eine bessere Möglichkeit, Nodes und andere Built-in-Klassen in Godot mit C++ zu "erweitern". Dieses neue System ermöglicht die Erweiterung von Godot auf nahezu dem gleichen Niveau wie statisch gelinkte C++-Module.

Sie können das enthaltene Beispiel im Test-Ordner des godot-cpp-Repository auf GitHub herunterladen.

Einrichten des Projekts

Es gibt ein paar Voraussetzungen, die Sie benötigen:

  • eine ausführbare Godot 4-Datei,

  • einen C++-Compiler,

  • SCons als Build-Tool,

  • eine Kopie des godot-cpp Repository.

Siehe auch Kompilieren, da die Build-Tools identisch mit denen sind, die Sie benötigen, um Godot aus dem Quellcode zu kompilieren.

Sie können das godot-cpp-Repository von GitHub herunterladen oder Git die Arbeit für Sie erledigen lassen. Beachten Sie, dass dieses Repository verschiedene Branches für verschiedene Versionen von Godot hat. GDExtensions funktionieren nicht in älteren Versionen von Godot (nur Godot 4 und höher) und umgekehrt, also stellen Sie sicher, dass Sie den richtigen Branch herunterladen.

Bemerkung

Um GDExtension zu verwenden, müssen Sie den godot-cpp-Branch verwenden, der zu der Version von Godot passt, für die Sie entwickeln. Wenn Sie zum Beispiel Godot 4.1 verwenden, benutzen Sie den Branch 4.1, der in diesem Tutorial gezeigt wird.

Der master-Branch ist der Entwicklungsbranch, der regelmäßig aktualisiert wird, um mit dem master-Branch von Godot zusammenzuarbeiten.

Warnung

Unser langfristiges Ziel ist es, dass GDExtensions, die auf eine frühere Version von Godot geschrieben sind, in späteren Minor-Versionen funktionieren, aber nicht umgekehrt. Zum Beispiel sollte eine GDExtension, die für Godot 4.2 geschrieben ist, problemlos in Godot 4.3 funktionieren, aber eine, die für Godot 4.3 geschrieben ist, wird nicht in Godot 4.2 funktionieren.

Allerdings ist GDExtension derzeit experimentell, was bedeutet, dass wir Kompatibilitätsbrüche vornehmen können, um größere Bugs zu beheben oder kritische Funktionen aufzunehmen. Zum Beispiel sind GDExtensions, die für Godot 4.0 erstellt wurden, nicht mit Godot 4.1 kompatibel (siehe Aktualisieren Ihrer GDExtension für 4.1).

Wenn Sie Ihr Projekt mit Git versionieren, empfiehlt es sich, es als Git-Submodul hinzuzufügen:

mkdir gdextension_cpp_example
cd gdextension_cpp_example
git init
git submodule add -b 4.1 https://github.com/godotengine/godot-cpp
cd godot-cpp
git submodule update --init

Alternativ dazu können Sie es auch in den Projektordner klonen:

mkdir gdextension_cpp_example
cd gdextension_cpp_example
git clone -b 4.1 https://github.com/godotengine/godot-cpp

Bemerkung

Wenn Sie sich entscheiden, das Repository herunterzuladen oder es in Ihren Ordner zu klonen, achten Sie darauf, dass Sie das Layout des Ordners so beibehalten, wie wir es hier eingerichtet haben. Ein Großteil des Codes, den wir hier zeigen werden, setzt voraus, dass das Projekt dieses Layout hat.

Wenn Sie das Beispiel über den in der Einleitung angegebenen Link geklont haben, werden die Untermodule nicht automatisch initialisiert. Sie müssen die folgenden Befehle ausführen:

cd gdextension_cpp_example
git submodule update --init

Dadurch wird das Repository in Ihrem Projektordner initialisiert.

Bauen der C++-Bindings

Nachdem wir nun unsere Anforderungen heruntergeladen haben, ist es an der Zeit, die C++ Bindings zu erstellen.

Das Repository enthält eine Kopie der Metadaten für die aktuelle Godot-Version, aber wenn Sie diese Bindings für eine neuere Version von Godot erstellen müssen, rufen Sie einfach die ausführbare Godot-Datei auf:

godot --dump-extension-api

Die resultierende Datei extension_api.json wird im Verzeichnis der ausführbaren Datei erstellt. Kopieren Sie sie in den Projektordner und fügen Sie custom_api_file=<PATH_TO_FILE> zu dem unten stehenden scons-Befehl hinzu.

Um die Bindings zu erzeugen und zu kompilieren, verwenden Sie diesen Befehl (ersetzen Sie <Plattform> durch Windows, Linux oder Macos, abhängig von Ihrem Betriebssystem):

Der Buildprozess erkennt automatisch die Anzahl der zu verwendenden CPU-Threads für parallele Builds. Um die Anzahl der zu verwendenden CPU-Threads anzugeben, fügen Sie -jN am Ende der SCons-Kommandozeile hinzu, wobei N die Anzahl der zu verwendenden CPU-Threads ist.

cd godot-cpp
scons platform=<platform> custom_api_file=<PATH_TO_FILE>
cd ..

Dieser Schritt wird eine Weile dauern. Wenn er abgeschlossen ist, sollten Sie statische Bibliotheken haben, die in Ihr Projekt kompiliert werden können und in godot-cpp/bin/ gespeichert sind.

Bemerkung

Unter Windows oder Linux müssen Sie eventuell bits=64 zu dem Befehl hinzufügen.

Erstellen eines einfachen Plugins

Jetzt ist es an der Zeit, ein echtes Plugin zu erstellen. Wir beginnen mit der Erstellung eines leeren Godot-Projekts, in das wir ein paar Dateien einfügen werden.

Öffnen Sie Godot und erstellen Sie ein neues Projekt. Für dieses Beispiel werden wir es in einem Ordner namens demo innerhalb der Ordnerstruktur unserer GDExtension ablegen.

In unserem Demoprojekt erstellen wir eine Szene, die einen Node mit dem Namen "Main" enthält, und speichern sie als main.tscn ab. Wir werden später darauf zurückkommen.

Im obersten GDExtension-Modulordner erstellen wir einen Unterordner mit dem Namen src, in dem wir unsere Quelldateien ablegen.

Sie sollten nun die Verzeichnisse demo, godot-cpp und src in Ihrem GDExtension Modul haben.

Ihre Ordnerstruktur sollte nun folgendermaßen aussehen:

gdextension_cpp_example/
|
+--demo/                  # game example/demo to test the extension
|
+--godot-cpp/             # C++ bindings
|
+--src/                   # source code of the extension we are building

Im Ordner src beginnen wir mit der Erstellung unserer Header-Datei für den GDExtension-Node, den wir erstellen werden. Wir werden sie gdexample.h nennen:

#ifndef GDEXAMPLE_H
#define GDEXAMPLE_H

#include <godot_cpp/classes/sprite2d.hpp>

namespace godot {

class GDExample : public Sprite2D {
    GDCLASS(GDExample, Sprite2D)

private:
    double time_passed;

protected:
    static void _bind_methods();

public:
    GDExample();
    ~GDExample();

    void _process(double delta) override;
};

}

#endif

Es gibt ein paar Dinge, die zu beachten sind. Wir fügen sprite2d.hpp ein, die Bindings zur Sprite2D-Klasse enthält. Wir werden diese Klasse in unserem Modul erweitern.

Wir verwenden den Namespace godot, da alles in GDExtension in diesem Namespace definiert ist.

Dann haben wir unsere Klassendefinition, die von unserem Sprite2D über eine Containerklasse erbt. Wir werden später noch ein paar Nebeneffekte davon sehen. Das GDCLASS-Makro richtet ein paar interne Dinge für uns ein.

Danach deklarieren wir eine einzelne Membervariable namens time_passed.

Im nächsten Block definieren wir unsere Methoden. Wir haben unseren Konstruktor und Destruktor definiert, aber es gibt zwei weitere Funktionen, die einigen wahrscheinlich bekannt vorkommen werden, und eine neue Methode.

Die erste ist _bind_methods, eine statische Funktion, die Godot aufruft, um herauszufinden, welche Methoden aufgerufen werden können und welche Propertys sie freigeben. Die zweite ist unsere Funktion _process, die genau so funktioniert wie die Funktion _process, die Sie aus GDScript kennen.

Lassen Sie uns unsere Funktionen implementieren, indem wir unsere Datei gdexample.cpp erstellen:

#include "gdexample.h"
#include <godot_cpp/core/class_db.hpp>

using namespace godot;

void GDExample::_bind_methods() {
}

GDExample::GDExample() {
    // Initialize any variables here.
    time_passed = 0.0;
}

GDExample::~GDExample() {
    // Add your cleanup here.
}

void GDExample::_process(double delta) {
    time_passed += delta;

    Vector2 new_position = Vector2(10.0 + (10.0 * sin(time_passed * 2.0)), 10.0 + (10.0 * cos(time_passed * 1.5)));

    set_position(new_position);
}

Dies sollte ganz einfach sein. Wir implementieren jede Methode unserer Klasse, die wir in unserer Header-Datei definiert haben.

Beachten Sie unsere Funktion _process, die verfolgt, wie viel Zeit vergangen ist und eine neue Position für unser Sprite mit Hilfe einer Sinus- und Cosinusfunktion berechnet.

Es gibt noch eine C++-Datei, die wir brauchen; wir nennen sie register_types.cpp. Unser GDExtension-Plugin kann mehrere Klassen enthalten, jede mit ihrer eigenen Header- und Quelldatei, wie wir sie oben in GDExample implementiert haben. Was wir jetzt brauchen, ist ein kleines Stück Code, das Godot über alle Klassen in unserem GDExtension-Plugin informiert.

#include "register_types.h"

#include "gdexample.h"

#include <gdextension_interface.h>
#include <godot_cpp/core/defs.hpp>
#include <godot_cpp/godot.hpp>

using namespace godot;

void initialize_example_module(ModuleInitializationLevel p_level) {
    if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
        return;
    }

    ClassDB::register_class<GDExample>();
}

void uninitialize_example_module(ModuleInitializationLevel p_level) {
    if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
        return;
    }
}

extern "C" {
// Initialization.
GDExtensionBool GDE_EXPORT example_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, const GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) {
    godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);

    init_obj.register_initializer(initialize_example_module);
    init_obj.register_terminator(uninitialize_example_module);
    init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);

    return init_obj.init();
}
}

Die Funktionen initialize_example_module und uninitialize_example_module werden jeweils aufgerufen, wenn Godot unser Plugin lädt und wenn es das Plugin entlädt. Alles, was wir hier tun, ist, die Funktionen in unserem Binding-Modul zu parsen, um sie zu initialisieren, aber Sie müssen vielleicht noch mehr einrichten, je nach Ihren Bedürfnissen. Wir rufen die Funktion register_class für jede unserer Klassen in unserer Bibliothek auf.

Die wichtigste Funktion ist die dritte Funktion namens example_library_init. Wir rufen zunächst eine Funktion in unserer Binding-Bibliothek auf, die ein Initialisierungsobjekt erzeugt. Dieses Objekt registriert die Initialisierungs- und Beendigungsfunktionen der GDExtension. Außerdem legt es die Ebene der Initialisierung fest (core, servers, scene, editor, level).

Zuletzt brauchen wir noch die Header-Datei für die register_types.cpp namens register_types.h.

#ifndef GDEXAMPLE_REGISTER_TYPES_H
#define GDEXAMPLE_REGISTER_TYPES_H

#include <godot_cpp/core/class_db.hpp>

using namespace godot;

void initialize_example_module(ModuleInitializationLevel p_level);
void uninitialize_example_module(ModuleInitializationLevel p_level);

#endif // GDEXAMPLE_REGISTER_TYPES_H

Kompilieren des Plugins

Wir können nicht einfach von Hand eine SConstruct-Datei schreiben, die SCons zum Bauen verwenden würde. Für den Zweck dieses Beispiels verwenden wir einfach diese hartcodierte SConstruct-Datei, die wir vorbereitet haben. Wir werden in einem späteren Tutorial ein detaillierteres Beispiel für die Verwendung dieser Build-Dateien behandeln.

Bemerkung

Diese SConstruct-Datei wurde geschrieben, um mit dem neuesten godot-cpp-Master verwendet zu werden. Möglicherweise müssen Sie kleine Änderungen vornehmen, wenn Sie sie mit älteren Versionen verwenden oder die SConstruct-Datei in der Godot 4.0-Dokumentation nachschlagen.

Nachdem Sie die SConstruct-Datei heruntergeladen haben, legen Sie sie in Ihre GDExtension-Ordnerstruktur neben godot-cpp, src und demo, und führen Sie folgendes aus:

scons platform=<platform>

Sie sollten das Modul jetzt unter demo/bin/<Plattform> finden können.

Bemerkung

Hier haben wir sowohl godot-cpp als auch unsere gdexample-Bibliothek als Debug-Builds kompiliert. Für optimierte Builds sollten Sie sie mit dem Schalter target=template_release kompilieren.

Verwendung des GDExtension-Moduls

Bevor wir wieder nach Godot zurückkehren, müssen wir noch eine Datei in demo/bin/ erstellen.

Diese Datei teilt Godot mit, welche dynamischen Bibliotheken für die einzelnen Plattformen geladen werden sollen und wie die Eingabefunktion für das Modul lautet. Sie heißt gdexample.gdextension.

[configuration]

entry_symbol = "example_library_init"
compatibility_minimum = "4.1"

[libraries]

macos.debug = "res://bin/libgdexample.macos.template_debug.framework"
macos.release = "res://bin/libgdexample.macos.template_release.framework"
windows.debug.x86_32 = "res://bin/libgdexample.windows.template_debug.x86_32.dll"
windows.release.x86_32 = "res://bin/libgdexample.windows.template_release.x86_32.dll"
windows.debug.x86_64 = "res://bin/libgdexample.windows.template_debug.x86_64.dll"
windows.release.x86_64 = "res://bin/libgdexample.windows.template_release.x86_64.dll"
linux.debug.x86_64 = "res://bin/libgdexample.linux.template_debug.x86_64.so"
linux.release.x86_64 = "res://bin/libgdexample.linux.template_release.x86_64.so"
linux.debug.arm64 = "res://bin/libgdexample.linux.template_debug.arm64.so"
linux.release.arm64 = "res://bin/libgdexample.linux.template_release.arm64.so"
linux.debug.rv64 = "res://bin/libgdexample.linux.template_debug.rv64.so"
linux.release.rv64 = "res://bin/libgdexample.linux.template_release.rv64.so"
android.debug.x86_64 = "res://bin/libgdexample.android.template_debug.x86_64.so"
android.release.x86_64 = "res://bin/libgdexample.android.template_release.x86_64.so"
android.debug.arm64 = "res://bin/libgdexample.android.template_debug.arm64.so"
android.release.arm64 = "res://bin/libgdexample.android.template_release.arm64.so"

Diese Datei enthält einen Abschnitt configuration, der die Eingabefunktion des Moduls steuert. Sie sollten auch die minimale kompatible Godot-Version mit compatability_minimum festlegen, die verhindert, dass ältere Godot-Versionen versuchen, Ihre Erweiterung zu laden.

Der Abschnitt libraries ist der wichtige Teil: er teilt Godot den Ort der dynamischen Bibliothek im Dateisystem des Projekts für jede unterstützte Plattform mit. Es wird auch dazu führen, dass nur diese Datei exportiert wird, wenn Sie das Projekt exportieren, was bedeutet, dass das Datenpaket keine Bibliotheken enthält, die mit der Target-Plattform inkompatibel sind.

Schließlich können Sie im Abschnitt dependencies zusätzliche dynamische Bibliotheken angeben, die ebenfalls eingebunden werden sollen. Dies ist wichtig, wenn Ihr GDExtension-Plugin die Bibliothek eines anderen Herstellers implementiert und Sie eine dynamische Bibliothek eines Drittanbieters mit Ihrem Projekt bereitstellen müssen.

Hier ist eine weitere Übersicht zur Überprüfung der korrekten Dateistruktur:

gdextension_cpp_example/
|
+--demo/                  # game example/demo to test the extension
|   |
|   +--main.tscn
|   |
|   +--bin/
|       |
|       +--gdexample.gdextension
|
+--godot-cpp/             # C++ bindings
|
+--src/                   # source code of the extension we are building
|   |
|   +--register_types.cpp
|   +--register_types.h
|   +--gdexample.cpp
|   +--gdexample.h

Zeit, wieder zu Godot zurückzukehren. Wir laden die Hauptszene, die wir zu Beginn erstellt haben, und fügen nun einen neu verfügbaren GDExample-Node zur Szene hinzu:

../../../_images/gdextension_cpp_nodes.webp

Wir werden diesem Node das Godot-Logo als Textur zuweisen und die Property centered deaktivieren:

../../../_images/gdextension_cpp_sprite.webp

Wir sind endlich bereit, das Projekt auszuführen:

../../../_images/gdextension_cpp_animated.gif

Benutzerdefiniertes Editor-Icon

Standardmäßig verwendet Godot das Node-Icon im Szenendock für GDExtension-Nodes. Das eigene Icon kann über die Datei gdextension hinzugefügt werden. Das Icon des Nodes wird durch Bezugnahme auf seinen Namen und den Ressourcenpfad einer SVG-Datei festgelegt.

Zum Beispiel:

[icons]

GDExample = "res://icons/gd_example.svg"

Der Pfad sollte auf ein 16 x 16 Pixel großes SVG-Bild verweisen. Lesen Sie die Anleitung für Erstellung von Icons für weitere Informationen.

Propertys hinzufügen

GDScript erlaubt es Ihnen, Ihrem Skript mit dem Schlüsselwort export Propertys hinzuzufügen. In GDExtension müssen Sie die Propertys mit einer Getter- und Setter-Funktion registrieren oder direkt die Methoden _get_property_list, _get und _set eines Objekts implementieren (aber das würde den Rahmen dieses Tutorials sprengen).

Fügen wir eine Property hinzu, mit der wir die Amplitude unserer Welle steuern können.

In unserer Datei gdexample.h müssen wir eine Membervariable sowie Getter- und Setter-Funktionen hinzufügen:

...
private:
    double time_passed;
    double amplitude;

public:
    void set_amplitude(const double p_amplitude);
    double get_amplitude() const;
...

In unserer Datei gdexample.cpp müssen wir eine Reihe von Änderungen vornehmen. Wir zeigen nur die Methoden, die wir am Ende ändern, entfernen Sie nicht die Zeilen, die wir weglassen:

void GDExample::_bind_methods() {
    ClassDB::bind_method(D_METHOD("get_amplitude"), &GDExample::get_amplitude);
    ClassDB::bind_method(D_METHOD("set_amplitude", "p_amplitude"), &GDExample::set_amplitude);
    ClassDB::add_property("GDExample", PropertyInfo(Variant::FLOAT, "amplitude"), "set_amplitude", "get_amplitude");
}

GDExample::GDExample() {
    // Initialize any variables here.
    time_passed = 0.0;
    amplitude = 10.0;
}

void GDExample::_process(double delta) {
    time_passed += delta;

    Vector2 new_position = Vector2(
        amplitude + (amplitude * sin(time_passed * 2.0)),
        amplitude + (amplitude * cos(time_passed * 1.5))
    );

    set_position(new_position);
}

void GDExample::set_amplitude(const double p_amplitude) {
    amplitude = p_amplitude;
}

double GDExample::get_amplitude() const {
    return amplitude;
}

Sobald Sie das Modul mit diesen Änderungen kompiliert haben, werden Sie sehen, dass eine Property zu unserer Schnittstelle hinzugefügt wurde. Sie können diese Property nun ändern, und wenn Sie Ihr Projekt ausführen, werden Sie sehen, dass unser Godot-Icon entlang einer größeren Figur wandert.

Machen wir dasselbe, aber für die Geschwindigkeit unserer Animation, und verwenden wir eine Setter- und Getter-Funktion. Unsere Header-Datei gdexample.h benötigt wiederum nur ein paar Zeilen mehr Code:

...
    double amplitude;
    double speed;
...
    void _process(double delta) override;
    void set_speed(const double p_speed);
    double get_speed() const;
...

Dies erfordert ein paar weitere Änderungen in der Datei gdexample.cpp. Auch hier zeigen wir nur die Methoden, die sich geändert haben, also entfernen Sie nichts, was wir weglassen:

void GDExample::_bind_methods() {
    ...
    ClassDB::bind_method(D_METHOD("get_speed"), &GDExample::get_speed);
    ClassDB::bind_method(D_METHOD("set_speed", "p_speed"), &GDExample::set_speed);
    ClassDB::add_property("GDExample", PropertyInfo(Variant::FLOAT, "speed", PROPERTY_HINT_RANGE, "0,20,0.01"), "set_speed", "get_speed");
}

GDExample::GDExample() {
    time_passed = 0.0;
    amplitude = 10.0;
    speed = 1.0;
}

void GDExample::_process(double delta) {
    time_passed += speed * delta;

    Vector2 new_position = Vector2(
        amplitude + (amplitude * sin(time_passed * 2.0)),
        amplitude + (amplitude * cos(time_passed * 1.5))
    );

    set_position(new_position);
}

...

void GDExample::set_speed(const double p_speed) {
    speed = p_speed;
}

double GDExample::get_speed() const {
    return speed;
}

Wenn das Projekt nun kompiliert wird, sehen wir eine weitere Property namens "speed". Wenn man ihren Wert ändert, wird die Animation schneller oder langsamer. Außerdem haben wir eine Property "range" hinzugefügt, die beschreibt, in welchem Bereich der Wert liegen kann. Die ersten beiden Argumente sind der Minimal- und der Maximalwert und das dritte Argument ist die Schrittweite.

Bemerkung

Der Einfachheit halber haben wir nur den "hint_range" der Property-Methode verwendet. Es gibt noch viele weitere Optionen, aus denen Sie wählen können. Diese können verwendet werden, um weiter zu konfigurieren, wie Propertys auf der Godot-Seite angezeigt und eingestellt werden.

Signale

Zu guter Letzt funktionieren Signale auch in GDExtension vollständig. Damit Ihre Extension auf ein Signal reagiert, das von einem anderen Objekt ausgegeben wird, müssen Sie connect auf diesem Objekt aufrufen. Uns fällt kein gutes Beispiel für unser wackelndes Godot-Icon ein, wir müssten ein viel umfassenderes Beispiel zeigen.

Dies ist die erforderliche Syntax:

some_other_node->connect("the_signal", Callable(this, "my_method"));

Um unser Signal the_signal von einem anderen Node mit unserer Methode my_method zu verbinden, müssen wir der Methode connect den Namen des Signals und ein Callable übergeben. Das Callable enthält Informationen über ein Objekt, auf dem eine Methode aufgerufen werden kann. In unserem Fall assoziiert es unsere aktuelle Objektinstanz this mit der Methode my_method des Objekts. Dann fügt die Methode connect dies zu den Beobachtern von the_signal hinzu. Wann immer the_signal nun ausgesendet wird, weiß Godot, welche Methode welchen Objekts es aufrufen muss.

Beachten Sie, dass Sie my_method nur aufrufen können, wenn Sie sie zuvor in Ihrer Methode _bind_methods registriert haben. Andernfalls wird Godot nichts von der Existenz von my_method wissen.

Um mehr über Callable zu erfahren, lesen Sie die Klassenreferenz hier: Callable.

Üblicher ist es, dass Ihr Objekt Signale aussendet. Für unser wackelndes Godot-Icon werden wir etwas Albernes machen, um zu zeigen, wie es funktioniert. Wir werden jedes Mal, wenn eine Sekunde vergangen ist, ein Signal aussenden und die neue Position weitergeben.

In unserer Header-Datei gdexample.h müssen wir einen neues Member time_emit definieren:

...
    double time_passed;
    double time_emit;
    double amplitude;
...

Dieses Mal sind die Änderungen in gdexample.cpp etwas aufwändiger. Zuerst müssen Sie time_emit = 0.0; entweder in unserer _init Methode oder in unserem Konstruktor setzen. Wir werden uns die anderen zwei notwendigen Änderungen nacheinander ansehen.

In unserer Methode _bind_methods müssen wir unser Signal deklarieren. Dies wird wie folgt gemacht:

void GDExample::_bind_methods() {
    ...
    ClassDB::add_property("GDExample", PropertyInfo(Variant::FLOAT, "speed", PROPERTY_HINT_RANGE, "0,20,0.01"), "set_speed", "get_speed");

    ADD_SIGNAL(MethodInfo("position_changed", PropertyInfo(Variant::OBJECT, "node"), PropertyInfo(Variant::VECTOR2, "new_pos")));
}

Hier kann unser Makro ADD_SIGNAL ein einzelner Aufruf mit einem MethodInfo-Argument sein. Der erste Parameter von MethodInfo ist der Name des Signals, und die restlichen Parameter sind PropertyInfo-Typen, die das Wesentliche der einzelnen Parameter der Methode beschreiben. PropertyInfo-Parameter werden mit dem Datentyp des Parameters und dem Namen, den der Parameter standardmäßig hat, definiert.

Hier fügen wir also ein Signal hinzu, mit einer MethodInfo, die das Signal "position_changed" nennt. Die PropertyInfo-Parameter beschreiben zwei wesentliche Argumente, eines vom Typ Object, das andere vom Typ Vector2, jeweils mit den Namen "node" und "new_pos".

Als nächstes müssen wir unsere Methode _process ändern:

void GDExample::_process(double delta) {
    time_passed += speed * delta;

    Vector2 new_position = Vector2(
        amplitude + (amplitude * sin(time_passed * 2.0)),
        amplitude + (amplitude * cos(time_passed * 1.5))
    );

    set_position(new_position);

    time_emit += delta;
    if (time_emit > 1.0) {
        emit_signal("position_changed", this, new_position);

        time_emit = 0.0;
    }
}

Nachdem eine Sekunde vergangen ist, senden wir unser Signal aus und setzen unseren Zähler zurück. Wir können unsere Parameterwerte direkt zu emit_signal hinzufügen.

Sobald die GDExtension-Bibliothek kompiliert ist, können wir zu Godot gehen und unseren Sprite-Node auswählen. Im Node-Dock finden wir unser neues Signal und verknüpfen es durch Drücken des Connect-Buttons oder durch Doppelklick auf das Signal. Wir haben ein Skript zu unserem Haupt-Node hinzugefügt und unser Signal wie folgt implementiert:

extends Node

func _on_Sprite2D_position_changed(node, new_pos):
    print("The position of " + node.get_class() + " is now " + str(new_pos))

Jede Sekunde geben wir unsere Position auf der Konsole aus.

Nächste Schritte

Wir hoffen, das obige Beispiel hat Ihnen die Grundlagen gezeigt. Sie können auf diesem Beispiel aufbauen, um vollwertige Skripte zur Steuerung von Nodes in Godot mit C++ zu erstellen.