Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Erste Schritte
Workflow overview
As a GDExtension, godot-cpp is more complicated to use than GDScript and C#. If you decide to work with it, here's what to expect your workflow to look like:
Create a new godot-cpp project (from the template, or from scratch, as explained below).
Develop your code with your favorite IDE locally.
Build and test your code with the earliest compatible Godot version.
Create builds for all platforms you want to support (e.g. using GitHub Actions).
Optional: Publish on the Godot Asset Library.
Beispielprojekt
For your first godot-cpp project, we recommend starting with this guide to understand the technology involved with
godot-cpp. After you're done, you can use the godot-cpp template,
which has better coverage of features, such as a GitHub action pipeline and useful SConstruct boilerplate code.
However, the template does not explain itself to a high level of detail, which is why we recommend going through this
guide first.
Einrichten des Projekts
Es gibt ein paar Voraussetzungen, die Sie benötigen:
A Godot 4 executable.
A C++ compiler.
SCons as a build tool.
A copy of the godot-cpp repository.
See also Configuring an IDE and Compiling as the build tools are identical to the ones you need to compile Godot from source.
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, mit der Sie entwickeln. Wenn Sie zum Beispiel Godot 4.1 verwenden, benutzen Sie den 4.1-Branch. In diesem Tutorial verwenden wir 4.x, was durch die Version von Godot ersetzt werden muss, mit der Sie entwickeln.
Der master-Branch ist der Entwicklungsbranch, der regelmäßig aktualisiert wird, um mit dem master-Branch von Godot zusammenzuarbeiten.
Warnung
GDExtensions targeting an earlier version of Godot should work in later minor versions, but not vice-versa. For example, a GDExtension targeting Godot 4.2 should work just fine in Godot 4.3, but one targeting Godot 4.3 won't work in Godot 4.2.
There is one exception to this: extensions targeting Godot 4.0 will not work with Godot 4.1 and later (see 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.x 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.x 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.
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.
Open Godot and create a new project. For this example, we will place it in a
folder called project inside our GDExtension's folder structure.
In our project, we'll create a scene containing a Node called "Main" and
we'll save it as main.tscn. We'll come back to that later.
Im obersten GDExtension-Modulordner erstellen wir einen Unterordner mit dem Namen src, in dem wir unsere Quelldateien ablegen.
You should now have project, godot-cpp, and src
directories in your GDExtension module.
Ihre Ordnerstruktur sollte nun folgendermaßen aussehen:
gdextension_cpp_example/
|
+--project/ # 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:
#pragma once
#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;
};
} // namespace godot
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;
}
GDREGISTER_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();
}
}
The initialize_example_module and uninitialize_example_module functions get
called respectively when Godot loads our plugin and when it unloads it. All
we're doing here is parse through the functions in our bindings module to
initialize them, but you might have to set up more things depending on your
needs. We call the GDREGISTER_CLASS macro for each of our classes in our library.
Bemerkung
You can find information about GDREGISTER_CLASS (and alternatives) at Die Klasse "Object".
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.
#pragma once
#include <godot_cpp/core/class_db.hpp>
using namespace godot;
void initialize_example_module(ModuleInitializationLevel p_level);
void uninitialize_example_module(ModuleInitializationLevel p_level);
Kompilieren des Plugins
To compile the project we need to define how SCons using should compile it
using an SConstruct file which references the one in godot-cpp.
Writing it from scratch is outside the scope of this tutorial, but you can
the SConstruct file we prepared.
We'll cover a more customizable, detailed example on how to use these
build files in a subsequent tutorial.
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.x-Dokumentation nachschlagen.
Once you've downloaded the SConstruct file, place it in your GDExtension folder
structure alongside godot-cpp, src, and project, then run:
scons platform=<platform>
You can omit the platform option if you are compiling for the platform you
are currently using. The list of available platform options depends on which
platform dependencies are set up (use platform=list to see all available platforms).
See Einführung in das Buildsystem for details.
You should now be able to find the compiled library in project/bin/.
Bemerkung
Here, we've compiled both godot-cpp and our gdexample library as debug
builds, which is the default. For optimized builds, you should compile
them using the target=template_release option.
Verwendung des GDExtension-Moduls
Before we jump back into Godot, we need to create one more file in
project/bin/.
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"
reloadable = true
[libraries]
macos.debug = "./libgdexample.macos.template_debug.dylib"
macos.release = "./libgdexample.macos.template_release.dylib"
windows.debug.x86_32 = "./gdexample.windows.template_debug.x86_32.dll"
windows.release.x86_32 = "./gdexample.windows.template_release.x86_32.dll"
windows.debug.x86_64 = "./gdexample.windows.template_debug.x86_64.dll"
windows.release.x86_64 = "./gdexample.windows.template_release.x86_64.dll"
linux.debug.x86_64 = "./libgdexample.linux.template_debug.x86_64.so"
linux.release.x86_64 = "./libgdexample.linux.template_release.x86_64.so"
linux.debug.arm64 = "./libgdexample.linux.template_debug.arm64.so"
linux.release.arm64 = "./libgdexample.linux.template_release.arm64.so"
linux.debug.rv64 = "./libgdexample.linux.template_debug.rv64.so"
linux.release.rv64 = "./libgdexample.linux.template_release.rv64.so"
This file contains a configuration section that controls the entry function of the module.
You should also set the minimum compatible Godot version with compatibility_minimum,
which prevents older version of Godot from trying to load your extension.
The reloadable flag enables automatic reloading of your extension by the editor every time you recompile it,
without needing to restart the editor. This only works if you compile your extension in debug mode (default).
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.
You can learn more about .gdextension files at Die .gdextension-Datei.
Hier ist eine weitere Übersicht zur Überprüfung der korrekten Dateistruktur:
gdextension_cpp_example/
|
+--project/ # 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:
Wir werden diesem Node das Godot-Logo als Textur zuweisen und die Property centered deaktivieren:
Wir sind endlich bereit, das Projekt auszuführen:
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);
ADD_PROPERTY(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);
ADD_PROPERTY(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
For simplicity, we've only used the hint_range of the property method. There are a lot more options to choose from. These can be used to further configure how properties are displayed and set on the Godot side. You can find more information on property hints here @GlobalScope.
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() {
...
ADD_PROPERTY(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
We hope the above example showed you the basics. You can build upon this example to create full-fledged scripts to control nodes in Godot using C++!
Instead of basing your project off the above example setup, we recommend to restart now by cloning the
godot-cpp template, and base your project off of that.
It has better coverage of features, such as a GitHub build action and additional useful SConstruct boilerplate.