Skripten

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 sind, sollten Sie 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 keinen Garbage Collector, opfert daher jedoch ein wenig Automatismus (die meisten Objekte werden sowieso per Referenzzählung erfasst) für mehr 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 Sie unentschlossen sind und Erfahrung mit dem Programmieren haben, vor allem mit dynamisch typisierten Programmiersprachen, verwenden Sie GDScript!

VisualScript

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

VisualScript 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 Arbeitsabläufe 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 Programmiersprache ist die beste Wahl für Leistung und muss nicht alleine für ein Spiel verwendet werden, da andere Teile auch in GDScript oder VisualScript geschrieben werden können. Die API ist klar und einfach zu benutzen, da sie in den meisten Teilen der eigentlichen C++ API von Godot sehr ähnlich 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.

Bevor Sie fortfahren überfliegen Sie die GDSkript-Referenz und versehen sie eventuell mit einem Lesezeichen. Es ist eine Sprache, die einfach gestaltet ist, und die Referenz ist in Abschnitte unterteilt, um einen Überblick über die Konzepte zu erhalten.

Eine Szene einrichten

Wenn das Projekt "Instanzen" aus dem vorherigen Abschnitt noch geöffnet ist, schließen Sie es (Projekt -> Projektliste beenden) und erstellen Sie ein neues Projekt.

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

  • Panel
    • Label
    • Schaltfläche (Button)

Der Szenenbaum sollte so aussehen:

../../_images/scripting_scene_tree.png

Verwenden Sie den 2D-Editor um die Schaltfläche (Button) und das Label so zu positionieren, wie im folgenden Bild dargestellt. Sie können den Text in der Registerkarte "Inspektor" festlegen.

../../_images/label_button_example.png

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

Ein Skript hinzufügen

Rechtsklicken Sie auf den „Panel“-Node und wählen "Skript hinzufügen" im Kontextmenü aus:

../../_images/add_script.png

Der Dialog zur Skripterstellung wird angezeigt. In diesem Dialogfeld können Sie 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ählen Sie einen Dateinamen für das Skript aus und klicken 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ählen Sie 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. Sie können sogar eigene Signale in Ihren 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ählen Sie den „Button“-Node im Szenenbaum aus und klicken dann auf die "Node"-Registerkarte. Als nächstes, stellen Sie sicher, dass "Signale" ausgewählt ist.

../../_images/signals.png

Wenn Sie dann unter "BaseButton" die Option "pressed()" auswählen und unten rechts auf die Schaltfläche "Verbinden ..." klicken, wird der Verbindungserstellungsdialog geöffnet.

../../_images/connect_dialogue.png

Oben im Dialogfeld wird eine Liste der Nodes Ihrer Szene angezeigt, wobei der Name des sendenden Nodes blau hervorgehoben ist. Wählen Sie hier den Knoten "Panel".

Im unteren Teil des Dialogs wird der Name der zu erstellenden Methode angezeigt. Standardmäßig enthält der Methodenname den Namen des emittierenden Nodes ("Button" in diesem Fall), was in _on_[EmitterNode]_[signal_name] resultiert.

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 verbinden Sie die "pressed" -Signal Schaltfläche mit ``_on_Button_pressed() ``, wärend Sie Object.connect() verwenden.

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

Das 'advanced' Feld des Verbindungsdialogs dient zum Binden bestimmter Werte an die Parameter der verbundenen Funktion. Sie können 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.