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...
Benutzerdefinierte Module in C++
Module
Godot ermöglicht das modularen Erweitern der Engine. Neue Module können erstellt und dann aktiviert bzw. deaktiviert werden. Dies ermöglicht das Hinzufügen neuer Enginefunktionen auf jeder Ebene, ohne den Kern zu ändern, die zur Verwendung und Wiederverwendung in verschiedenen Modulen aufgeteilt werden können.
Modules are located in the modules/ subdirectory of the build system.
By default, dozens of modules are enabled, such as GDScript (which, yes,
is not part of the base engine), GridMap support, a regular expressions
module, and others. As many new modules as desired can be
created and combined. The SCons build system will take care of it
transparently.
Wofür?
Es wird zwar empfohlen, den größten Teil eines Spiels in Skripten zu schreiben (da dies eine enorme Zeitersparnis bedeutet), es ist jedoch durchaus möglich, stattdessen C++ zu verwenden. Das Hinzufügen von C++-Modulen kann in den folgenden Szenarien hilfreich sein:
Binden einer externen Bibliothek an Godot (wie PhysX, FMOD usw.).
Optimieren kritischer Teile eines Spiels.
Hinzufügen neuer Funktionen zur Engine oder zum Editor.
Portierung eines bestehenden Spiels nach Godot.
Schreiben eines ganz neuen Spiels in C++, weil Sie ohne C++ nicht leben können.
Bemerkung
While it is possible to use modules for custom game logic, GDExtension is generally more suited as it doesn't require recompiling the engine after every code change.
C++ modules are mainly needed when GDExtension doesn't suffice and deeper engine integration is required.
Neues Modul erstellen
Bevor Sie ein Modul erstellen, laden Sie den Quellcode von Godot herunter und kompilieren Sie ihn.
Um ein neues Modul zu erstellen, müssen Sie zunächst ein Verzeichnis in modules/ erstellen. Wenn Sie das Modul separat warten möchten, können Sie ein anderes Repository in Module auschecken und verwenden.
Das Beispielmodul wird "summator" heißen (godot/modules/summator). Darin werden wir eine Summierer-Klasse erstellen:
#pragma once
#include "core/object/ref_counted.h"
class Summator : public RefCounted {
GDCLASS(Summator, RefCounted);
int count;
protected:
static void _bind_methods();
public:
void add(int p_value);
void reset();
int get_total() const;
Summator();
};
Und hier die cpp Datei.
#include "summator.h"
void Summator::add(int p_value) {
count += p_value;
}
void Summator::reset() {
count = 0;
}
int Summator::get_total() const {
return count;
}
void Summator::_bind_methods() {
ClassDB::bind_method(D_METHOD("add", "value"), &Summator::add);
ClassDB::bind_method(D_METHOD("reset"), &Summator::reset);
ClassDB::bind_method(D_METHOD("get_total"), &Summator::get_total);
}
Summator::Summator() {
count = 0;
}
Dann muss die neue Klasse irgendwie registriert werden, sodass zwei weitere Dateien erstellt werden müssen:
register_types.h
register_types.cpp
Wichtig
Diese Dateien müssen sich im obersten Ordner Ihres Moduls befinden (neben Ihren Dateien SCsub und config.py), damit das Modul ordnungsgemäß registriert werden kann.
Diese Dateien sollten Folgendes enthalten:
#include "modules/register_module_types.h"
void initialize_summator_module(ModuleInitializationLevel p_level);
void uninitialize_summator_module(ModuleInitializationLevel p_level);
/* yes, the word in the middle must be the same as the module folder name */
#include "register_types.h"
#include "core/object/class_db.h"
#include "summator.h"
void initialize_summator_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
ClassDB::register_class<Summator>();
}
void uninitialize_summator_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
// Nothing to do here in this example.
}
Als nächstes müssen wir eine SCsub Datei erstellen, damit das Buildsystem dieses Modul kompiliert:
# SCsub
Import('env')
env.add_source_files(env.modules_sources, "*.cpp") # Add all cpp files to the build
Bei mehreren Quellen können Sie jede Datei auch einzeln zu einer Python-String-Liste hinzufügen:
src_list = ["summator.cpp", "other.cpp", "etc.cpp"]
env.add_source_files(env.modules_sources, src_list)
Dies erlaubt leistungsstarke Möglichkeiten mit Python, die Dateiliste mithilfe von Schleifen und logischen Anweisungen zu erstellen. Schauen Sie sich einige der Module an, die standardmäßig mit Godot geliefert werden.
Um Include-Verzeichnisse hinzuzufügen, die der Compiler anzeigen kann, können Sie sie an die Pfade der Umgebungvariable anhängen:
env.Append(CPPPATH=["mylib/include"]) # this is a relative path
env.Append(CPPPATH=["#myotherlib/include"]) # this is an 'absolute' path
Wenn Sie beim Bauen Ihres Moduls eigene Compilerflags hinzufügen wollen, müssen Sie zuerst env klonen, damit diese Flags nicht zum gesamten Godot-Build hinzugefügt werden (was zu Fehlern führen kann). Beispiel SCsub mit eigenen Flags:
Import('env')
module_env = env.Clone()
module_env.add_source_files(env.modules_sources, "*.cpp")
# Append CCFLAGS flags for both C and C++ code.
module_env.Append(CCFLAGS=['-O2'])
# If you need to, you can:
# - Append CFLAGS for C code only.
# - Append CXXFLAGS for C++ code only.
Und schließlich die Konfigurationsdatei für das Modul, ein Python-Skript, das den Namen config.py tragen muss:
# config.py
def can_build(env, platform):
return True
def configure(env):
pass
Das Modul wird gefragt, ob es für die jeweilige Plattform in Ordnung ist, zu bauen (in diesem Fall bedeutet True, dass es für jede Plattform erstellt wird).
Und das war's, hoffentlich war es nicht zu komplex. Ihr Modul sollte folgendermaßen aussehen:
godot/modules/summator/config.py
godot/modules/summator/summator.h
godot/modules/summator/summator.cpp
godot/modules/summator/register_types.h
godot/modules/summator/register_types.cpp
godot/modules/summator/SCsub
Sie können es dann zippen und das Modul mit allen anderen teilen. Beim Erstellen für jede Plattform (Anweisungen in den vorherigen Abschnitten) wird Ihr Modul einbezogen.
Das Modul verwenden
Sie können Ihr neu erstelltes Modul jetzt in jedem Skript verwenden:
var s = Summator.new()
s.add(10)
s.add(20)
s.add(30)
print(s.get_total())
s.reset()
Die Ausgabe wird 60 sein.
Siehe auch
Das vorherige Summator-Beispiel eignet sich hervorragend für kleine, benutzerdefinierte Module. Was ist jedoch, wenn Sie eine größere externe Bibliothek verwenden möchten? Weitere Informationen zum Binden an externe Bibliotheken finden Sie unter Binden an externe Bibliotheken.
Warnung
Wenn auf Ihr Modul vom laufenden Projekt aus zugegriffen werden soll (nicht nur vom Editor), müssen Sie auch jede Exportvorlage, die Sie verwenden möchten, neu kompilieren und dann in jeder Exportvorgabe den Pfad zur benutzerdefinierten Vorlage angeben. Andernfalls werden beim Ausführen des Projekts Fehler angezeigt, da das Modul nicht in der Exportvorlage kompiliert ist. Weitere Informationen finden Sie auf den Seiten Kompilieren.
Ein Modul extern kompilieren
Beim Kompilieren eines Moduls wird der Quellcode des Moduls direkt in das Verzeichnis modules/ der Engine verschoben. Dies ist zwar die einfachste Methode zum Kompilieren eines Moduls, es gibt jedoch mehrere Gründe, warum dies möglicherweise nicht praktikabel ist:
Sie müssen Modul-Quellcode jedes Mal manuell kopieren, wenn Sie die Engine mit oder ohne Modul kompilieren möchten, oder zusätzliche Schritte ausführen, um ein Modul während der Kompilierung mit einer Erstellungsoption ähnlich
module_summator_enabled = nomanuell zu deaktivieren. Das Erstellen symbolischer Links kann ebenfalls eine Lösung sein. Möglicherweise müssen Sie jedoch zusätzlich Betriebssystembeschränkungen überwinden, z.B. die Berechtigung für symbolische Links, wenn Sie dies über ein Skript tun.Abhängig davon, ob Sie mit dem Quellcode der Engine arbeiten müssen, ändern die direkt zu
modules/hinzugefügten Moduldateien den Arbeitsbaum so weit, dass sich die Verwendung eines VCS (wiegit) als umständlich erweist, denn Sie müssen sicherstellen, dass nur der Code mit Bezug auf die Engine durch Filtern von Änderungen committed wird.
Wenn Sie also der Meinung sind, dass die unabhängige Struktur benutzerdefinierter Module benötigt wird, nehmen Sie unser "Summator"-Modul und verschieben Sie es in das übergeordnete Verzeichnis der Engine:
mkdir ../modules
mv modules/summator ../modules
Kompilieren Sie die Engine mit unserem Modul, indem Sie die Build-Option custom_modules bereitstellen, die eine durch Kommas getrennte Liste von Verzeichnispfaden akzeptiert, die benutzerdefinierte C++-Module enthalten, ähnlich wie im Folgenden:
scons custom_modules=../modules
Das Buildsystem erkennt alle Module im Verzeichnis ../modules und kompiliert sie entsprechend, einschließlich unseres "Summator" -Moduls.
Warnung
Jeder an custom_modules übergebene Pfad wird intern in einen absoluten Pfad konvertiert, um zwischen benutzerdefinierten und integrierten Modulen zu unterscheiden. Dies bedeutet, dass Dinge wie das Generieren der Moduldokumentation möglicherweise von einer bestimmten Pfadstruktur auf Ihrem Computer abhängen.
Initialisierung der Modultypen anpassen
Module können während der Laufzeit mit anderen Built-in Engine-Klassen interagieren und sogar die Art und Weise beeinflussen, wie Core-Typen initialisiert werden. Bisher haben wir register_summator_types verwendet, um Modulklassen in der Engine verfügbar zu machen.
Eine grobe Reihenfolge der Engine-Einstellung kann als Liste der folgenden Typregistrierungsmethoden zusammengefasst werden:
preregister_module_types();
preregister_server_types();
register_core_singletons();
register_server_types();
register_scene_types();
EditorNode::register_editor_types();
register_platform_apis();
register_module_types();
initialize_physics();
initialize_navigation_server();
register_server_singletons();
register_driver_types();
ScriptServer::init_languages();
Our Summator class is initialized during the register_module_types()
call. Imagine that we need to satisfy some common module runtime dependency
(like singletons), or allow us to override existing engine method callbacks
before they can be assigned by the engine itself. In that case, we want to
ensure that our module classes are registered before any other built-in type.
Hier können wir eine optionale preregister_summator_types()-Methode definieren, die während der preregister_module_types()-Einrichtungsphase der Engine vor allem anderen aufgerufen wird.
Wir müssen nun diese Methode in die register_types-Header- und Quelldateien einfügen:
#define MODULE_SUMMATOR_HAS_PREREGISTER
void preregister_summator_types();
void register_summator_types();
void unregister_summator_types();
Bemerkung
Im Gegensatz zu anderen Register-Methoden müssen wir MODULE_SUMMATOR_HAS_PREREGISTER explizit definieren, um dem Buildsystem mitzuteilen, welche relevanten Methodenaufrufe es zur Kompilierzeit einbinden soll. Der Name des Moduls muss ebenfalls in Großbuchstaben umgewandelt werden.
#include "register_types.h"
#include "core/object/class_db.h"
#include "summator.h"
void preregister_summator_types() {
// Called before any other core types are registered.
// Nothing to do here in this example.
}
void register_summator_types() {
ClassDB::register_class<Summator>();
}
void unregister_summator_types() {
// Nothing to do here in this example.
}
Benutzerdefinierte Dokumentation schreiben
Das Schreiben von Dokumentation mag wie eine langweilige Aufgabe erscheinen, aber es wird dringend empfohlen, Ihr neu erstelltes Modul zu dokumentieren, damit die Benutzer es leichter nutzen können. Ganz zu schweigen davon, dass der Code, den Sie vor einem Jahr geschrieben haben, möglicherweise nicht mehr von dem Code zu unterscheiden ist, den jemand anderes geschrieben hat - seien Sie also nett zu Ihrem zukünftigen Ich!
Es gibt mehrere Schritte, um benutzerdefinierte Dokumente für das Modul einzurichten:
Erstellen Sie ein neues Verzeichnis im Stammverzeichnis des Moduls. Der Verzeichnisname kann beliebig sein, aber wir werden in diesem Abschnitt den Namen
doc_classesverwenden.Jetzt müssen wir
config.pybearbeiten und den folgenden Schnipsel hinzufügen:def get_doc_path(): return "doc_classes" def get_doc_classes(): return [ "Summator", ]
Die Funktion get_doc_path() wird vom Buildsystem verwendet, um den Speicherort der Dokumente zu bestimmen. In diesem Fall befinden sie sich im Verzeichnis modules/summator/doc_classes. Wenn Sie dies nicht definieren, wird der Dokumentpfad für Ihr Modul auf das Hauptverzeichnis doc/classes zurückgesetzt.
Die Methode get_doc_classes() ist erforderlich, damit das Buildsystem weiß, welche registrierten Klassen zum Modul gehören. Sie müssen alle Ihre Klassen hier auflisten. Die Klassen, die Sie nicht auflisten, landen im Hauptverzeichnis doc/classes.
Tipp
You can use Git to check if you have missed some of your classes by checking the
untracked files with git status. For example:
git status
Example output:
Untracked files:
(use "git add <file>..." to include in what will be committed)
doc/classes/MyClass2D.xml
doc/classes/MyClass4D.xml
doc/classes/MyClass5D.xml
doc/classes/MyClass6D.xml
...
Jetzt können wir die Dokumentation erstellen:
Wir können dies tun, indem wir Godots doctool ausführen, d.h. godot --doctool <Pfad>, wodurch die Engine-API-Referenz auf den angegebenen <Pfad> im XML-Format ausgegeben wird.
In unserem Fall verweisen wir auf das Stammverzeichnis des geklonten Repositorys. Sie können es auf einen anderen Ordner verweisen lassen und einfach die benötigten Dateien kopieren.
Führen Sie den Befehl aus:
bin/<godot_binary> --doctool .
Wenn Sie nun zum Ordner godot/modules/summator/doc_classes gehen, werden Sie sehen, dass er eine Summator.xml-Datei oder andere Klassen enthält, auf die Sie in Ihren get_doc_classes Funktion verwiesen haben.
Edit the file(s) following the class reference primer and recompile the engine.
Sobald der Kompilierungsprozess abgeschlossen ist, können Sie auf die Dokumente im integrierten Dokumentationssystem der Engine zugreifen.
Um die Dokumentation auf dem neuesten Stand zu halten, müssen Sie lediglich eine der XML-Dateien ändern und die Engine von nun an neu kompilieren.
Wenn Sie die API Ihres Moduls ändern, können Sie die Dokumente auch erneut extrahieren. Sie enthalten die zuvor hinzugefügten Elemente. Wenn Sie auf Ihren Godot-Ordner verweisen, stellen Sie sicher, dass Sie keine Arbeit verlieren, indem Sie ältere Dokumente aus einer älteren Engine extrahieren, die auf den neueren basiert.
Beachten Sie, dass möglicherweise ein Fehler auftritt, der dem folgenden ähnelt, wenn Sie keine Schreibzugriffsrechte für Ihren angegebenen <Pfad> haben:
ERROR: Can't write doc file: docs/doc/classes/@GDScript.xml
At: editor/doc/doc_data.cpp:956
Schreiben von benutzerdefinierten Unit-Tests
Es ist möglich, in sich geschlossene Unit-Tests als Teil eines C++-Moduls zu schreiben. Wenn Sie mit dem Unit-Testing-Prozess in Godot noch nicht vertraut sind, lesen Sie bitte Unit-Tests.
Das Verfahren ist wie folgt:
Erstellen Sie ein neues Verzeichnis mit dem Namen
tests/unter dem Stammverzeichnis Ihres Moduls:
cd modules/summator
mkdir tests
cd tests
Erstellen Sie eine neue Testsuite:
test_summator.h. Der Header muss mit dem Präfixtest_versehen werden, damit das Buildsystem ihn aufsammeln und als Teil dertests/test_main.cppeinbinden kann, in der die Tests ausgeführt werden.Schreiben Sie einige Test-Cases. Hier ist ein Beispiel:
#pragma once
#include "tests/test_macros.h"
#include "modules/summator/summator.h"
namespace TestSummator {
TEST_CASE("[Modules][Summator] Adding numbers") {
Ref<Summator> s = memnew(Summator);
CHECK(s->get_total() == 0);
s->add(10);
CHECK(s->get_total() == 10);
s->add(20);
CHECK(s->get_total() == 30);
s->add(30);
CHECK(s->get_total() == 60);
s->reset();
CHECK(s->get_total() == 0);
}
} // namespace TestSummator
Kompilieren Sie die Engine mit
scons tests=yes, und führen Sie die Tests mit dem folgenden Befehl aus:
./bin/<godot_binary> --test --source-file="*test_summator*" --success
Sie sollten jetzt die durchlaufenen Assertions sehen.
Hinzufügen von benutzerdefinierten Editor-Symbolen
Ähnlich wie Sie eine in sich geschlossene Dokumentation innerhalb eines Moduls schreiben, können Sie auch eigene benutzerdefinierte Icons erstellen, damit Klassen im Editor angezeigt werden.
Informationen zum eigentlichen Erstellen von Editor-Icons für die Integration in die Engine finden Sie zuerst unter Editor-Icons.
Führen Sie die folgenden Schritte aus, nachdem Sie Ihre Icons erstellt haben:
Erstellen Sie ein neues Verzeichnis im Stammverzeichnis des Moduls mit dem Namen
icons. Dies ist der Standardpfad für das Modul, um nach den Editor-Icons des Moduls zu suchen.Verschieben Sie Ihre neu erstellten
svg-Icons (optimiert oder nicht) in diesen Ordner.Kompilieren Sie die Engine neu und führen Sie den Editor aus. Jetzt werden die Icons gegebenenfalls in der Benutzeroberfläche des Editors angezeigt.
Wenn Sie Ihre Icons an einer anderen Stelle in Ihrem Modul speichern möchten, fügen Sie den folgenden Codeausschnitt zu config.py hinzu, um den Standardpfad zu überschreiben:
def get_icons_path(): return "path/to/icons"
Zum Abschluss
Nicht vergessen:
Verwenden Sie das Makro
GDCLASSfür die Vererbung, damit Godot es einschließen kann.Verwenden Sie
_bind_methods, um Ihre Funktionen an Skripte zu binden, damit sie als Callbacks für Signale arbeiten können.Vermeiden Sie Mehrfachvererbung für Klassen, die für Godot sichtbar sind, da
GDCLASSdies nicht unterstützt. Sie können dennoch Mehrfachvererbung in Ihren eigenen Klassen verwenden, solange diese für die Godot-Skript-API sichtbar sind.
Aber das ist noch nicht alles - je nachdem, was Sie tun, werden Sie mit einigen (hoffentlich positiven) Überraschungen begrüßt.
Wenn Sie von Node (oder einem abgeleiteten Nodetyp wie Sprite2D) erben, wird Ihre neue Klasse im Editor im Vererbungsbaum im Dialogfeld "Node hinzufügen" angezeigt.
Wenn Sie von Resource erben, wird es in der Ressourcenliste angezeigt, und alle offengelegten Propertys können beim Speichern/Laden serialisiert werden.
Mit derselben Logik können Sie den Editor und fast jeden Bereich der Engine erweitern.