Scripting

Einführung

Vor Godot 3.0 war für das Programmieren eines Spiels nur eine Sprache verfügbar, nämlich doc_gdscript. Heutzutage unterstützt Godot vier (ja, vier!) offizielle Programmiersprachen und bietet die Möglichkeit, zusätzliche Sprachen dynamisch hinzuzufügen!

Das ist großartig, vor allem aufgrund der großen Flexibilität, die aber auch die Unterstützung von Sprachen erschwert.

Die „Hauptsprachen“ in Godot sind jedoch GDScript und VisualScript. Der Hauptgrund dessen Auswahl, ist die starke Vernetzung mit Godot, die eine reibungslose Bedienung ermöglicht. Beide verfügen über eine sehr gute Editorintegration, während C# und C++ in einer separaten IDE bearbeitet werden müssen. Wenn Sie ein großer Fan von statisch geschriebenen Sprachen bist, solltest Du stattdessen C# und C++ verwenden.

GDScript

doc_gdscript ist, wie oben erwähnt, die Hauptsprache in Godot. Sie zu verwenden hat ein paar Vorteile gegenüber anderen Sprachen, da sie gut in Godot integriert ist:

  • Sie ist simpel, elegant und ähnelt Sprachen wie Lua, Python, Squirrel, etc.
  • Lädt und kompiliert unheimlich schnell.
  • Durch die Integration im Editor, der Codevervollständigung für Nodes, Signale und andere Elemente innerhalb einer Szene ist es eine Freude mit der Sprache zu arbeiten.
  • Hat integrierte Vektortypen (z. B. Vektoren, Transformationen usw.), die eine effiziente Nutzung der linearen Algebra ermöglichen.
  • Unterstützt mehrere Threads genauso effizient wie statisch typisierte Sprachen - eine der Einschränkungen, die dazu geführt haben, dass VMs wie Lua, Squirrel usw. vermieden werden.
  • Verwendet kein automatisches Speichermanagement (Garbage Collector), tauscht also ein kleines Stück Automatisierung (die meisten Objekte werden ohnehin per Referenzzählung erfasst) gegen Kontrolle.
  • Ihre dynamische Natur macht es einfach, Bereiche des Codes in C++ zu optimieren (via GDNative), falls mehr Leistung nötig ist. Und das alles ohne die Engine neu zu kompilieren.

Falls Du unentschlossen bist und Erfahrung mit dem Programmieren hast, vor allem mit dynamisch typisierten Programmiersprachen, verwende GDScript!

VisualScript

Seit Version 3.0 unterstüzt Godot Visual Scripting. Dies ist eine typische Implementierung einer „Blöcke und Verbindungen“ Sprache, die an die funktionsweise von Godot angepasst wurde.

Visual Scripting ist ein hervorragendes Werkzeug für Nicht-Programmierer oder auch für erfahrene Entwickler, die Teile des Codes für andere zugänglich machen möchten, wie zum Beispiel für Spieledesigner oder Künstler.

Es kann auch von Programmierern verwendet werden, um Zustandsmaschinen oder benutzerdefinierte Workflows für visuelle Nodes zu erstellen - beispielsweise ein Dialogsystem.

.NET / C#

Da Microsofts C# eine beliebte Programmiersprache bei Spieleentwicklern ist, unterstützen wir sie nun offiziell. C# ist eine ausgereifte Programmiersprache, für die tonnenweise Code geschrieben wurden. Dank einer großzügigen Spende von Microsoft konnte die Integration ermöglicht werden.

C# besitzt einen hervorragenden Kompromiss zwischen Leistung und Benutzerfreundlichkeit, obwohl man sich seines Müllsammlers bewusst sein muss.

Da Godot Mono als .NET runtime verwendet, können viele .NET Bibliotheken und Frameworks von Drittanbietern, sowie Common Language Infrastructure-kompatible Programmiersprachen, wie zum Beispiel F#, Boo oder ClojureCLR, in Scripts verwendet werden. C# ist aber die einzige offiziell unterstützte .NET-Option.

GDNative / C++

Und schlussendlich eine der größten Ergänzungen in Release 3.0: GDNative unterstützt Skripten mit C++, ohne dass Godot neu kompiliert (oder gestartet) werden muss.

Durch die interne C API-Brücke kann jede Version von C++ verwendet und beliebige Compiler verwendet werden, um Shared Libraries zu erzeugen.

Diese Sprache ist die beste Wahl für Leistung und muss nicht alleine für ein Spiel verwendet werden, da andere Teil auch in GDScript or Visual Script geschrieben sein können. Die API ist klar und einfach zu benutzen, da sie in den meisten Teilen der eigentlichen C++ API Godots sehr änhlich ist.

Weitere Programmiersprachen können mittels der GDNative Schnittstelle verfügbar gemacht werden, diese werden allerdings nicht offiziell von uns unterstützt.

Eine Szene skripten

Für den Rest dieser Einleitung werden wir eine GUI Szene, bestehend aus einem Text und einer Schaltfläche, die bei Betätigung den Text aktualisiert, erstellen. Dies umfasst:

  • Das Schreiben eines Skriptes und das Hinzufügen in einem Node.
  • UI Elemente mit Signalen verknüpfen.
  • Das Schreiben eines Skripts, welches auf andere Nodes in der Szene zugreifen kann.

Bitte lies die doc_gdscript-Referenz, bevor du fortfährst. GDScript wurde so entworfen, dass die Sprache einfach zu verstehen und ihre Referenz kurz ist. Daher dauert es nicht mehr als ein paar Minuten, um sich einen Überblick über ihre Konzepte zu verschaffen.

Eine Szene einrichten

Verwende den Dialog „Node hier anhängen“, auf den über die Registerkarte „Szene“ (oder durch Drücken von Strg + A) zugreifen werden kann, um eine Hierarchie mit den folgenden Nodes zu erstellen:

  • Panel
    • Label
    • Taste, Knopf (Button)

Der Szenenbaum sollte so aussehen:

../../_images/scripting_scene_tree.png

Verwende den 2D-Editor, um die Schaltfläche (Button) und das Label so zu positionieren, wie im folgenden Bild dargestellt. Du kannst den Text in der der Registerkarte „Inspektor“ festlegen.

../../_images/label_button_example.png

Anschließend speichere die Szene unter dem Namen sayhello.tscn.

Ein Skript hinzufügen

Rechtsklicke auf den „Panel“-Node und wähle „Skript hinzufügen“ im Kontextmenü aus:

../../_images/add_script.png

Der Dialog zur Skripterstellung wird angezeigt. In diesem Dialogfeld kannst Du die Sprache, den Klassennamen und andere relevante Optionen des Skripts einstellen.

In GDScript stellt die Datei selbst die Klasse dar. Das Klassennamensfeld kann daher nicht bearbeitet werden.

Der Node, an den das Skript angehängt wird, ist ein Panel. Das Feld „Erbt“ wird deshalb automatisch mit „Panel“ ausgefüllt. In diesem Fall ist dies erwünscht, da das Skript die Funktionalität unseres „Panel“-Nodes erweitern soll.

Wähle einen Dateinamen für das Skript aus und klicke auf „Erstellen“:

../../_images/script_create.png

Das Skript wird dann erstellt und dem Node hinzugefügt. Dies ist erkennbar an dem „Skript öffnen“-Symbol neben dem Node in der Registerkarte „Szene“ sowie in der Skripteigenschaft im Inspektor:

../../_images/script_added.png

Um das Skript zu bearbeiten, wähle eine dieser Schaltflächen aus, welche im obigen Bild hervorgehoben sind. Dadurch wird der Skripteditor geöffnet, in dem eine Standardvorlage angezeigt wird:

../../_images/script_template.png

Die Standardvorlage enthält recht wenig. Die _ready()-Funktion wir aufgerufen, wenn der Node und alle seine Unterobjekt die aktive Szene betreten. Beachte: _ready() ist kein Konstruktor, der Konstruktor ist _init().

Die Rolle des Skriptes

Ein Skript fügt einem Node ein Verhalten hinzu. Es wird verwendet, um zu steuern, wie der Node funktioniert und wie er mit anderen Nodes interagiert: untergeordnete, übergeordnete, gleichwertige Nodes usw. Der lokale Bereich des Skripts ist der Node. Mit anderen Worten, das Skript erbt die von diesem Node bereitgestellten Funktionen.

../../_images/brainslug.jpg

Mit einem Signal umgehen

Signale werden „ausgesendet“, wenn eine bestimmte Art von Aktion stattfindet, und sie können mit jeder Funktion einer Skriptinstanz verbunden werden. Signale werden hauptsächlich in Nodes für grafische Benutzeroberflächen verwendet, obwohl andere Nodes diese auch besitzen können. Du kannst sogar eigene Signale in Deinen eigenen Skripten definieren.

In diesem Schritt verbinden wir das „pressed()“-Signal mit einer eigenen Funktion. Das Herstellen von Verbindungen ist der erste Teil und die Definition der eigenen Funktion der zweite Teil. Für den ersten Teil bietet Godot zwei Möglichkeiten, Verbindungen herzustellen: über eine grafische Benutzeroberfläche, die der Editor bereitstellt, oder über Code.

Während wir die Programmiermethode für den Rest dieser Anleitungsreihe verwenden, gehen wir kurz auf die Funktionsweise der Editor-Oberfläche als Referenz für die Zukunft ein.

Wähle den „Button“-Node im Szenenbaum aus und klicke dann auf die „Node“-Registerkarte. Als nächstes, stelle sicher, dass „Signale“ ausgewählt ist.

../../_images/signals.png

Wenn Du dann unter „BaseButton“ die Option „pressed()“ auswählst und unten rechts auf die Schaltfläche „Verbinden …“ klickst, wird der Verbindungserstellungsdialog geöffnet.

../../_images/connect_dialogue.png

Unten links siehst Du die wichtigsten Elemente, die Du zum Erstellen einer Verbindung benötigst einen Node, der die Methode implementiert, die Du auslösen möchtest (hier „Pfad zum Node“) und den Namen der Methode, die ausgelöst werden soll.

Im oberen linken Bereich wird eine Liste der Nodes Deiner Szene angezeigt, wobei der Name der Signalquelle (Node) in Rot hervorgehoben ist. Wähle hier den Node „Panel“ aus. Nach der Auswahl des Nodes wird der „Pfad zum Node“ automatisch auf den relativen Pfad von der Signalquelle zum ausgewählten Node aktualisiert.

By default, the method name will contain the emitting node’s name („Button“ in this case), resulting in _on_[EmitterNode]_[signal_name]. If you do have the „Make Function“ check button checked, then the editor will generate the function for you before setting up the connection.

Damit ist die Anleitung zum Verwenden der grafischen Benutzeroberfläche abgeschlossen. Da dies jedoch eine Skripting-Anleitung ist, tauchen wir nun in den manuellen Prozess ein!

Um dies zu erreichen, werden wir eine Funktion verwenden, die wahrscheinlich von Godot-Programmierern am häufigsten verwendet wird: Node.get_node (). Diese Funktion verwendet Pfade zum Abrufen von Nodes an einer beliebigen Stelle in der Szene, relativ zu dem Node, dem das Skript gehört.

Einfachheitshalber lösche alles unterhalb von extends Panel. Wir werden den Rest des Skriptes manuell ausfüllen.

Da „Button“ und „Label“ Unterelemente von „Panel“ sind, an welches dieses Skript angefügt ist, ist es möglich den „Button“ zu finden indem folgendes unter die _ready()-Funktion hinzugefügt wird:

func _ready():
    get_node("Button")
public override void _Ready()
{
    GetNode("Button");
}

Als nächstes schreibe eine Funktion welche aufgerufen wird, wenn auf die Schaltfläche von „Button“ geklickt wird:

func _on_Button_pressed():
    get_node("Label").text = "HELLO!"
public void _OnButtonPressed()
{
    GetNode<Label>("Label").Text = "HELLO!";
}

Abschließend verbinde das „pressed“ -Signal der Schaltfläche mit ``_ready () ``, indem Du Object.connect () verwendest.

func _ready():
    get_node("Button").connect("pressed", self, "_on_Button_pressed")
public override void _Ready()
{
    GetNode("Button").Connect("pressed", this, nameof(_OnButtonPressed));
}

Das endgültige Skript sollte wie folgt aussehen:

extends Panel

func _ready():
    get_node("Button").connect("pressed", self, "_on_Button_pressed")

func _on_Button_pressed():
    get_node("Label").text = "HELLO!"
using Godot;

// IMPORTANT: the name of the class MUST match the filename exactly.
// this is case sensitive!
public class sayhello : Panel
{
    public override void _Ready()
    {
        GetNode("Button").Connect("pressed", this, nameof(_OnButtonPressed));
    }

    public void _OnButtonPressed()
    {
        GetNode<Label>("Label").Text = "HELLO!";
    }
}

Starte die Szene und klick auf die Schaltfläche. Dies sollte zu folgenden Ergebnis führen:

../../_images/scripting_hello.png

Oh, immer noch hier? Glückwunsch zum Skripten der ersten Szene.

Bemerkung

Ein häufiges Missverständnis bezüglich dieser Anleitung ist, wie get_node (path) `` funktioniert. Für einen bestimmten Node sucht ``get_node (path) seine unmittelbar untergeordneten Elemente. Im obigen Code bedeutet dies, dass „Button“ ein untergeordnetes Element von „Panel“ sein muss. Wenn „Button“ stattdessen ein untergeordnetes Element von „Label“ wäre, dann müsste der Funktionsaufruf folgendermaßen abgeändert werden:

# Not for this case,
# but just in case.
get_node("Label/Button")
// Not for this case,
// but just in case.
GetNode("Label/Button")

Des Weiteren beachte, das Nodes über den Namen referenziert werden und nicht über den Typ.

Bemerkung

Im rechten Bereich des Verbindungsdialogs werden bestimmte Werte an die Parameter der verbundenen Funktion gebunden. Du kannst Werte verschiedener Typen hinzufügen und entfernen.

Die Programmiermethode erlaubt die selbe Funktionalität mit dem vierten Parameter im Funktionsaufruf, welcher Standardmäßig leer ist. Mehr Information dazu kann unter der Object.connect-Funktion nachgelesen werden.