Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Verwendung von ScreneTree

Einführung

In der vorangegangen Anleitung drehte sich alles um das Konzept der Nodes. Szenen sind Sammlungen von Nodes. Sie werden aktiv sobald sie in den Szenenbaum eingefügt werden.

MainLoop

Die interne Funktionsweise von Godot ist wie folgt. Es gibt die Klasse OS, die einzige Instanz, die am Anfang läuft. Danach werden alle Treiber, Server, Skriptsprachen, das Szenensystem usw. geladen.

Sobald die Initialisierung abgeschlossen ist, wird OS eine MainLoop übergeben, um ausgeführt zu werden. Bis zu diesem Punkt ist alles ein interner Ablauf (siehe main/main.cpp im Quellcode, falls Interesse besteht, die Internen Abläufe nachzuvollziehen).

Die Nutzer-Anwendung, oder das Spiel, startet in der MainLoop. Diese Klasse definiert einige Methoden für die Initialisierung, Leerlauf (Frame-synchrones Callback), Fixed (Physik-synchrones Callback) und Eingabe. Auch hier handelt es sich um eine Low-Level-Funktionen, und bei der Entwicklung von Spielen in Godot ist es selten sinnvoll, eine eigene MainLoop zu schreiben.

SceneTree

Eine Möglichkeit, die Funktionsweise von Godot zu erklären, besteht darin, dass es sich um eine High-Level-Spielengine über einer Low-Level-Middleware handelt.

Das Szenensystem ist die Game Engine, wohingegen das OS und die Server zur Low Level-API gehören.

Das Szenensystem liefert seine eigene Hauptschleife zum OS, SceneTree. Dieser wird automatisch instanziiert und gesetzt, wenn eine Szene ausgeführt wird, ohne dass Sie zusätzlich etwas tun müssen.

Es ist wichtig zu wissen, dass es diese Klasse gibt, weil sie einige wichtige Verwendungszwecke hat:

  • Sie beinhaltet den Root-Viewport, zu welcher eine Szene als Child-Element hinzugefügt wird, welches beim ersten Öffnen Teil des Szenen-Baums (Scene Tree) wird.

  • Sie enthält Informationen zu Gruppen und hat Möglichkeiten, Node-Methoden einer Gruppe aufzurufen oder eine Liste dieser zu liefern.

  • Sie beinhaltet einige Funktionalität zum Globalen Zustand, wie dem Pausieren oder Beenden des Prozesses.

Wenn ein Node Teil des Szenen-Baums ist, kann auf den Szenen-Baum als Singleton über Node.get_tree() zugegriffen werden.

Root-Viewport

Der Root-Viewport (Viewport) befindet sich immer am oberen Ende der Szene. Durch einen Node kann mittels zwei verschiedener Wege auf ihn zugegriffen werden:

get_tree().root # Access via scene main loop.
get_node("/root") # Access via absolute path.

Dieser Node enthält den Haupt-Viewport. Alles, was ein Child-Element eines Viewports ist, wird standardmäßig darin gezeichnet, daher ist es sinnvoll, dass der oberste aller Nodes immer ein Node dieses Typs ist, sonst würde man nichts sehen.

Während andere Viewports in der Szene erstellt werden können (für Split-Screen-Effekte und dergleichen), ist dies das einzige, das nie vom Benutzer erstellt wird. Er wird automatisch im SceneTree erstellt.

Szenenbaum

Wenn ein Node direkt oder indirekt mit dem Root-Viewport verbunden ist, wird er Teil des Szenenbaums.

Das bedeutet, dass es, wie in früheren Tutorials erklärt, die _enter_tree() und _ready() Callbacks (sowie _exit_tree()) erhält.

../../_images/activescene.webp

Sobald Nodes den Szenenbaum betreten, werden sie aktiv. Sie erhalten Zugang zu allem, was sie verarbeiten müssen, erhalten Eingaben, zeigen 2D- und 3D-Grafiken, erhalten und senden Benachrichtigungen, spielen Töne ab, etc. Doch sobald sie vom Szenenbaum entfernt werden, verlieren sie jene Fähigkeiten.

Baum-Reihenfolge

Die meisten Node-Operationen in Godot, wie z.B. das Zeichnen von 2D, die Verarbeitung oder das Abrufen von Benachrichtigungen, erfolgen in Baumreihenfolge, d.h. von oben nach unten, wie im Editor zu sehen (auch bekannt als Pre-Order-Traversal):

../../_images/toptobottom.webp

So wird z.B. beim obersten Node in einer Szene zuerst die Funktion _process() aufgerufen, dann beim darunterliegenden Node Funktion _process(), dann beim darunterliegenden Node und so weiter.

Eine wichtige Ausnahme ist die Funktion _ready(): bei jedem Parent-Node wird die Funktion _ready() erst dann aufgerufen, wenn bei allen Child-Nodes die Funktion _ready() aufgerufen wurde, so dass der Parent-Node weiß, dass seine Child-Nodes vollständig zum Zugriff bereit sind. Dies ist auch als Post-Order-Traversal bekannt. In der obigen Abbildung würde NameLabel zuerst benachrichtigt werden (aber erst nach seinen Child-Nodes, falls er welche hat!), gefolgt von Name, usw., und Panel würde zuletzt benachrichtigt werden.

Die Reihenfolge der Operationen kann auch durch die Node-Property process_priority außer Kraft gesetzt werden. Nodes mit einer niedrigeren Nummer werden zuerst aufgerufen. Zum Beispiel würden Nodes mit den Prioritäten "0, 1, 2, 3" in dieser Reihenfolge von links nach rechts aufgerufen werden.

"Aktiv werden" durch das Betreten des Szenenbaums

  1. Eine Szene wird von der Festplatte geladen oder per Skript erzeugt.

  2. Der Root-Node dieser Szene (es gibt nur einen Root-Node, erinnern Sie sich?) wird entweder als Child des "Root"-Viewports (von SceneTree) oder zu einem seiner untergeordnetn Nodes hinzugefügt.

  3. Jeder Node der neu hinzugefügten Szene erhält die "enter_tree"-Benachrichtigung ( _enter_tree()-Callback in GDScript) in der Reihenfolge von oben nach unten (Pre-Order Traversal).

  4. Jeder Node erhält der Einfachheit halber die "ready"-Benachrichtigung ( _ready() Callback in GDScript), sobald alle seine Child-Nodes die "ready"-Benachrichtigung erhalten haben (Post-Order Traversal).

  5. Wenn eine Szene (oder ein Teil davon) entfernt wird, erhalten sie die "exit scene"-Benachrichtigung ( _exit_tree()-Callback in GDScript) in der Reihenfolge von unten nach oben (die genaue Umkehrung der Reihenfolge von oben nach unten).

Änderung der aktuellen Szene

Nachdem eine Szene geladen wurde, möchten Sie vielleicht diese Szene durch eine andere ersetzen. Eine Möglichkeit, dies zu tun, ist die Verwendung der Funktion SceneTree.change_scene_to_file():

func _my_level_was_completed():
    get_tree().change_scene_to_file("res://levels/level2.tscn")

Anstatt Dateipfade zu verwenden, kann man auch fertige PackedScene-Ressourcen mit der entsprechenden Funktion SceneTree.change_scene_to_packed(PackedScene scene) verwenden:

var next_scene = preload("res://levels/level2.tscn")

func _my_level_was_completed():
    get_tree().change_scene_to_packed(next_scene)

Dies sind schnelle und nützliche Wege, um die Szene zu wechseln, haben aber den Nachteil, dass das Spiel stehen bleibt, bis die neue Szene geladen ist und läuft. An einem bestimmten Punkt in der Entwicklung Ihres Spiels kann es besser sein, richtige Ladebildschirme mit Fortschrittsbalken, animierte Indikatoren oder Hintergrundladen mit Threading zu erstellen. Dies muss manuell mit Singletons (AutoLoad) und Laden im Hintergrund gemacht werden.