Benutzerdefinierte Module in C++¶
Module¶
Godot ermöglicht den modularen Ausbau 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, der zur Verwendung und Wiederverwendung in verschiedenen Modulen aufgeteilt werden kann.
Module befinden sich im Unterverzeichnis modules/
des Build-Systems. Standardmäßig sind Dutzende von Modulen aktiviert, z.B. GDScript (das ja nicht Teil der Basis-Engine ist), die Mono-Laufzeit, ein Modul für reguläre Ausdrücke und andere. Es können beliebig viele neue Module erstellt und kombiniert werden. Das SCons-Build-System kümmert sich transparent darum.
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.
Portieren eines vorhandenen Spiels.
Schreiben eines ganz neuen Spiels in C++, weil Sie ohne C++ nicht leben können.
Neues Modul erstellen¶
Before creating a module, make sure to download the source code of Godot and compile it.
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 VCS in Module auschecken und verwenden.
The example module will be called "summator" (godot/modules/summator
).
Inside we will create a simple summator class:
/* summator.h */
#ifndef SUMMATOR_H
#define SUMMATOR_H
#include "core/reference.h"
class Summator : public Reference {
GDCLASS(Summator, Reference);
int count;
protected:
static void _bind_methods();
public:
void add(int p_value);
void reset();
int get_total() const;
Summator();
};
#endif // SUMMATOR_H
Und dann die cpp Datei.
/* summator.cpp */
#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:
/* register_types.h */
void register_summator_types();
void unregister_summator_types();
/* yes, the word in the middle must be the same as the module folder name */
/* register_types.cpp */
#include "register_types.h"
#include "core/class_db.h"
#include "summator.h"
void register_summator_types() {
ClassDB::register_class<Summator>();
}
void unregister_summator_types() {
// Nothing to do here in this example.
}
Als nächstes müssen wir eine SCsub
Datei erstellen, damit das Build-System 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-Zeichenfolgenliste 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 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 Umgebung anhängen:
env.Append(CPPPATH=["mylib/include"]) # this is a relative path
env.Append(CPPPATH=["#myotherlib/include"]) # this is an 'absolute' path
If you want to add custom compiler flags when building your module, you need to clone
env
first, so it won't add those flags to whole Godot build (which can cause errors).
Example SCsub
with custom flags:
# SCsub
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, ist dies ein einfaches Python-Skript, das den Namen config.py
haben 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 builden (in diesem Fall bedeutet True
, dass es für jede Plattform erstellt wird).
Und das ist es, 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 komprimieren und das Modul mit allen anderen teilen. Beim Erstellen für jede Plattform (Anweisungen in den vorherigen Abschnitten) wird Ihr Modul einbezogen.
Bemerkung
In C++ - Modulen gibt es eine Parametergrenze von 5 für Dinge wie Unterklassen. Dies kann durch Einfügen der Header-Datei core/method_bind_ext.gen.inc
auf 13 erhöht werden.
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 Verknüpfung mit externen 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 Exportvoreinstellung 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 Compiling.
Ein Modul extern kompilieren¶
Beim Kompilieren eines Moduls werden die Quellen 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 Modulquellen 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 = no
manuell 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 einer VCS (wiegit
) als umständlich erweist Sie müssen sicherstellen, dass nur der enginebezogene Code 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 folgt:
scons custom_modules=../modules
Das Build-System 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.
Siehe auch
:ref:``Einführung in das Buildsystem - Build-Option für benutzerdefinierte Module <doc_buildsystem_custom_modules>`.
Verbesserung des Build-Systems für die Entwicklung¶
Warnung
This shared library support is not designed to support distributing a module to other users without recompiling the engine. For that purpose, use GDNative instead.
So far, we defined a clean SCsub that allows us to add the sources of our new module as part of the Godot binary.
This static approach is fine when we want to build a release version of our game, given we want all the modules in a single binary.
However, the trade-off is that every single change requires a full recompilation of the game. Even though SCons is able to detect and recompile only the file that was changed, finding such files and eventually linking the final binary takes a long time.
Die Lösung, um solche Kosten zu vermeiden, besteht darin, ein eigenes Modul als gemeinsam genutzte Bibliothek zu erstellen, das beim Starten der Binärdatei unseres Spiels dynamisch geladen wird.
# SCsub
Import('env')
sources = [
"register_types.cpp",
"summator.cpp"
]
# First, create a custom env for the shared library.
module_env = env.Clone()
# Position-independent code is required for a shared library.
module_env.Append(CCFLAGS=['-fPIC'])
# Don't inject Godot's dependencies into our shared library.
module_env['LIBS'] = []
# Define the shared library. By default, it would be built in the module's
# folder, however it's better to output it into `bin` next to the
# Godot binary.
shared_lib = module_env.SharedLibrary(target='#bin/summator', source=sources)
# Finally, notify the main build environment it now has our shared library
# as a new dependency.
# LIBPATH and LIBS need to be set on the real "env" (not the clone)
# to link the specified libraries to the Godot executable.
env.Append(LIBPATH=['#bin'])
# SCons wants the name of the library with it custom suffixes
# (e.g. ".x11.tools.64") but without the final ".so".
shared_lib_shim = shared_lib[0].name.rsplit('.', 1)[0]
env.Append(LIBS=[shared_lib_shim])
Einmal kompiliert, sollten wir ein bin
Verzeichnis haben, das sowohl die godot*
binär Datei als auch unseren libsummator*.so
enthält. Da sich die .so jedoch nicht in einem Standardverzeichnis befindet (wie /usr/lib
), müssen wir unserer Binärdatei helfen, sie zur Laufzeit mit der Umgebungsvariablen LD_LIBRARY_PATH
zu finden:
export LD_LIBRARY_PATH="$PWD/bin/"
./bin/godot*
Bemerkung
You have to export
the environment variable. Otherwise,
you won't be able to run your project from the editor.
On top of that, it would be nice to be able to select whether to compile our
module as shared library (for development) or as a part of the Godot binary
(for release). To do that we can define a custom flag to be passed to SCons
using the ARGUMENT
command:
# SCsub
Import('env')
sources = [
"register_types.cpp",
"summator.cpp"
]
module_env = env.Clone()
module_env.Append(CCFLAGS=['-O2'])
if ARGUMENTS.get('summator_shared', 'no') == 'yes':
# Shared lib compilation
module_env.Append(CCFLAGS=['-fPIC'])
module_env['LIBS'] = []
shared_lib = module_env.SharedLibrary(target='#bin/summator', source=sources)
shared_lib_shim = shared_lib[0].name.rsplit('.', 1)[0]
env.Append(LIBS=[shared_lib_shim])
env.Append(LIBPATH=['#bin'])
else:
# Static compilation
module_env.add_source_files(env.modules_sources, sources)
Standardmäßig erstellt der Befehl scons
unser Modul als Teil von Godots Binärdatei und als gemeinsam genutzte Bibliothek, wenn summator_shared = yes
übergeben wird.
Schließlich können Sie den Build sogar noch weiter beschleunigen, indem Sie Ihr freigegebenes Modul im Befehl SCons explizit als Ziel angeben:
scons summator_shared=yes platform=x11 bin/libsummator.x11.tools.64.so
Benutzerdefinierte Dokumentation schreiben¶
Das Schreiben von Dokumentation mag wie eine langweilige Aufgabe erscheinen, es wird jedoch dringend empfohlen, Ihr neu erstelltes Modul zu dokumentieren, damit Benutzer leichter davon profitieren 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, der von jemand anderem geschrieben wurde. Seien Sie also freundlich zu Ihrem zukünftigen Selbst!
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_classes
verwenden.Jetzt müssen wir
config.py
bearbeiten und das folgende Schnipsel hinzufügen:def get_doc_path(): return "doc_classes" def get_doc_classes(): return [ "Summator", ]
Die Funktion get_doc_path()
wird vom Build-System 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 Build-System 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
Sie können Git verwenden, um zu überprüfen, ob Sie einige Ihrer Klassen verpasst haben, indem Sie die nicht verfolgten Dateien mit git status
überprüfen. Zum Beispiel:
user@host:~/godot$ git status
Beispielausgabe:
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 und einfach die benötigten Dateien kopieren.
Führen Sie den Befehl aus:
user@host:~/godot/bin$ ./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.
Bearbeiten Sie die Datei(en) gemäß der Anleitung Beitrag zur Klassenreferenz und kompilieren Sie die Engine neu.
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
Hinzufügen von benutzerdefinierten Editor-Symbolen¶
Ähnlich wie Sie eine in sich geschlossene Dokumentation innerhalb eines Moduls schreiben, können Sie auch eigene benutzerdefinierte Symbole erstellen, damit Klassen im Editor angezeigt werden.
Informationen zum eigentlichen Erstellen von Editor-Symbolen für die Integration in die Engine finden Sie zuerst unter Editor Icons.
Führen Sie die folgenden Schritte aus, nachdem Sie Ihre Symbole 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 Editorsymbolen des Moduls zu suchen.Verschieben Sie Ihre neu erstellten
svg
Symbole (optimiert oder nicht) in diesen Ordner.Kompilieren Sie die Engine neu und führen Sie den Editor aus. Jetzt werden die Symbole gegebenenfalls in der Benutzeroberfläche des Editors angezeigt.
Wenn Sie Ihre Symbole 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"
Zusammenfassend¶
Nicht vergessen:
Verwenden Sie das Makro
GDCLASS
für die Vererbung, damit Godot es einschließen kannVerwenden Sie
_bind_methods
, um Ihre Funktionen an Skripte zu binden und sie als Rückrufe für Signale zu verwenden.
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 Sprite) erben, wird Ihre neue Klasse im Editor im Vererbungsbaum im Dialogfeld "Knoten hinzufügen" angezeigt.
Wenn Sie von Resource erben, wird es in der Ressourcenliste angezeigt, und alle offengelegten Eigenschaften können beim Speichern/Laden serialisiert werden.
Mit derselben Logik können Sie den Editor und fast jeden Bereich der Engine erweitern.