Singletons (Entwurfsmuster) - AutoLoad

Einführung

Godot's Szenensystem, obwohl mächtig und flexibel, hat einen Nachteil: es gibt keine Möglichkeit Informationen zu speichern die in mehreren Szenen benötigt werden (z.B. ein Spielstand oder Inventar).

Es ist möglich dieses Problem anzugehen mit gewissen Zwischenlösungen, diese bieten jedoch auch Begrenzungen:

  • Es kann eine übergeordnete Szene verwendet werden die andere lädt und entlädt und als 'Kinder' behandelt. Dies bedeutet jedoch auch, dass diese Szenen nicht länger individuell ausgeführt werden können und eine korrekte Funktionsweise erwartet werden kann.

  • Informationen können auf der Festplatte unter Benutzer:// gespeichert und dann von jeglichen Szenen die diese benötigen geladen werden. Jedoch wirkt sich oftmals wiederholendes Speichern und Laden von Daten abwürgend aus und kann die Anwendung verlangsamen.

Das Singleton Muster ist ein nützliches Werkzeug um dauerhafte Informationen zwischen Szenen zu speichern. In diesem Fall ist es möglich die gleiche Szene oder Klasse für mehrere Einzelelemente zu nutzen, solange sie unterschiedliche Namen haben.

Durch die Verwendung dieses Konzepts wird es Ihnen ermöglicht, Objekte zu instanziieren die:

  • immer geladen werden, unabhängig davon welche Szene gerade abgespielt wird.

  • Globale Variablen abspeichern können, wie z.B. Spielerinformationen.

  • Zum umschalten von Szenen und wechseln zwischen Szenen.

  • verhält sich wie ein Singleton, da GDScript keine globalen Variablen unterstützt.

Automatisch ladende Nodes und Skripte können diese Eigenschaften haben.

Bemerkung

Godot macht ein AutoLoad nicht zu einem "echten" Singleton gemäß dem Singleton-Entwurfsmuster. Falls gewünscht, kann es vom Benutzer immer noch mehrmals instanziiert werden.

Tipp

If you're creating an autoload as part of an editor plugin, consider registering it automatically in the Project Settings when the plugin is enabled.

Autoload

Sie können ein AutoLoad erstellen, um eine Szene oder ein Skript zu laden, das von Node erbt.

Bemerkung

Beim automatischen Laden eines Skripts wird ein :ref:'class_Node' erstellt und das Skript daran angefügt. Dieses Node wird dem Stammansichtsfenster hinzugefügt, bevor weitere Szenen geladen werden.

../../_images/singleton.png

Um eine Szene oder ein Skript zu autoloaden, wählen Sie Projekt > Projekteinstellungen aus dem Menü aus und wechseln auf den AutoLoad-Reiter.

../../_images/autoload_tab.png

Here you can add any number of scenes or scripts. Each entry in the list requires a name, which is assigned as the node's name property. The order of the entries as they are added to the global scene tree can be manipulated using the up/down arrow keys. Like regular scenes, the engine will read these nodes in top-to-bottom order.

../../_images/autoload_example.png

Das bedeutet, dass ein Node auf ein Singleton namens "SpielerVariablen" zugreifen kann mit:

var player_vars = get_node("/root/PlayerVariables")
player_vars.health -= 10

Wenn die Spalte Enable markiert ist (was die Voreinstellung ist), dann kann auf das Singleton direkt zugegriffen werden, ohne dass get_node() erforderlich ist:

PlayerVariables.health -= 10

Beachte, dass auf automatisch geladene Objekte (Skripte und/oder Szenen) genauso zugegriffen wird wie auf jedes andere Node innerhalb des Szenenbaums. Wenn man sich also den aktuellen Szenenbaum ansieht, sieht man die automatisch geladenen Nodes erscheinen:

../../_images/autoload_runtime.png

Warnung

Autoloads must not be removed using free() or queue_free() at runtime, or the engine will crash.

Szenenwechsler

Diese Anleitung zeigt das Erstellen eines Szenenumschalters mithilfe von Autoloads. Für das grundlegende Wechseln der Szene kann man die Methode SceneTree.change_scene() verwenden (siehe Szenen-Baum for mehr Details). Wenn Sie jedoch beim Ändern von Szenen ein komplexeres Verhalten benötigen, bietet diese Methode mehr Funktionen.

Um zu beginnen, laden Sie die Vorlage von hier runter: autoload.zip und öffnen diese anschließend in Godot.

Das Projekt enthält zwei Szenen: Scene1.tscn und Scene2.tscn. Jede Szene enthält ein Label, dieses den Szenennamen anzeigt und einen Knopf der mit einem pressed() -Signal verbunden wurde.

Global.gd

Wechsel zum Script Reiter und erstelle ein neues Skript namens Global.gd. Stelle sicher, dass es von Node erbt:

../../_images/autoload_script.png

Der nächste Schritt ist, dieses Skript zur Autoloader-Liste hinzuzufügen. Öffnen Sie Projekt > Projekteinstellungen aus dem Menü, wechseln zum AutoLoad-Reiter und wählen das Skript aus durch das Klicken des "Suchen"-Knopfes, oder geben Sie den folgenden Pfad ein: res://Global.gd. Drücken Sie Hinzufügen um es der AutoLoader-Liste beizufügen:

../../_images/autoload_tutorial1.png

Ab jetzt würde bei jeder Ausführung des Projekts das Skript vorgeladen werden.

Um zum Skript zurückzukehren, muss es die aktuelle Szene in der Funktion _ready() abrufen. Sowohl die aktuelle Szene (die mit der Schaltfläche) als auch Global.gd sind Kinder von root, aber automatisch geladene Nodes sind immer zuerst. Das bedeutet, dass das letzte Kind von root immer die geladene Szene ist.

extends Node

var current_scene = null

func _ready():
    var root = get_tree().root
    current_scene = root.get_child(root.get_child_count() - 1)

Als nächstes benötigen wir eine Funktion um die Szene zu modifizieren. Diese Funktion muss die aktuelle Szene leeren und diese mit der gewünschten ersetzen.

func goto_scene(path):
    # This function will usually be called from a signal callback,
    # or some other function in the current scene.
    # Deleting the current scene at this point is
    # a bad idea, because it may still be executing code.
    # This will result in a crash or unexpected behavior.

    # The solution is to defer the load to a later time, when
    # we can be sure that no code from the current scene is running:

    call_deferred("_deferred_goto_scene", path)


func _deferred_goto_scene(path):
    # It is now safe to remove the current scene
    current_scene.free()

    # Load the new scene.
    var s = ResourceLoader.load(path)

    # Instance the new scene.
    current_scene = s.instance()

    # Add it to the active scene, as child of root.
    get_tree().root.add_child(current_scene)

    # Optionally, to make it compatible with the SceneTree.change_scene() API.
    get_tree().current_scene = current_scene

Durch Nutzung von Object.call_deferred() wird die zweite Funktion nur ausgeführt, wenn das komplette Programm der aktuellen Szene beendet ist. Daher wird die aktuelle Szene nicht gelöscht, solange sie noch genutzt wird (z.B. Programm läuft noch).

Schlussendlich müssen wir noch die leeren Callback-Funktionen in den zwei Szenen ausfüllen:

# Add to 'Scene1.gd'.

func _on_Button_pressed():
    Global.goto_scene("res://Scene2.tscn")

und

# Add to 'Scene2.gd'.

func _on_Button_pressed():
    Global.goto_scene("res://Scene1.tscn")

Führen Sie das Projekt aus und überprüfen, dass Sie durch die Betätigung des Knopfes zwischen den Szenen hin- und her springen können.

Bemerkung

Beachte: Wenn Szenen kurz sind, ist der Wechsel unverzüglich. Bei komplexeren Szenen, gibt es möglicherweise einen merkbaren Zeitverzug bis zum erscheinen. Wie dies gehandhabt wird ist nachfolgend erklärt: Laden im Hintergrund.

Wenn die Ladezeit relativ kurz ist (weniger als 3 Sekunden oder so), können Sie alternativ eine "Ladeplakette" anzeigen, indem Sie kurz vor dem Ändern der Szene eine Art 2D-Element anzeigen. Sie können es dann direkt nach dem Ändern der Szene ausblenden. Dies kann verwendet werden, um dem Spieler anzuzeigen, dass eine Szene geladen wird.