Singleton Pattern und Autoloads
Heute soll es kurz und knapp um das Singleton Pattern gehen und warum man zwar von "Autoload Singleton" im Godot Kontext spricht, diese aber eigentlich "nichts" mit dem Pattern zu tun haben.
Außerdem möchte ich dir noch ein kleines Code Snippet an die Hand geben, mit dem du sehr einfach die Vorteile dieses Patterns genießen kannst.
Autoloads sind gar keine Singletons?!
Richtig! Die Dokumentation sagt selbst, dass die Autoloads nicht das eigentliche Pattern implementieren, weil es möglich ist, ein Autoload mehrfach zu instanziieren. Das spricht gegen das Pattern.
Vorteile von Autoloads
Neben der Tatsache, dass man auf Autoloads von überall aus erreichen kann, ist es auch ein großer Vorteil, dass man diese im Godot Editor konfigurieren kann. Der Zusammenhang zu den Singletons kommt daher, dass die Autoloads wie Singletons verwendet werden soll(t)en. Im Endeffekt sind Autoloads auch einfach nur Nodes, die unter /root/ angehangen werden. Darüber sind sie auch immer schnell erreichbar.
Singleton Pattern
Das Singleton Pattern auf der anderen Seite bietet den Vorteil, dass technisch sichergestellt wird, dass es eine einzige Instanz einer Klasse gibt. Das ist besonders dann nützlich, wenn man weiß, dass es etwas nur exakt einmal im Spiel gibt und geben wird.
In einem Singleplayer Spiel wird das - wie der Begriff schon sagt - u.a. der Player sein. Wenn dieser das Singleton Pattern implementiert, kann total einfach von überall aus auf den Player zugegriffen werden.
Singleton Code Snippet
Ich implementiere das Pattern sehr gerne folgendermaßen:
class_name Player
extends CharacterBody3D
# Hier is das "Herzstück":
static var instance: Player = null
func _enter_tree() -> void:
if not instance:
instance = self
func _exit_tree() -> void:
if instance:
instance = null
# ... Rest von der Player Klasse ...Nachtrag: "technically" implementiert man darüber auch nicht das Pattern, weil eine existierende Instanz lediglich auf null gesetzt wird. Damit auch wirklich ausschließlich eine Instanz aktiv sein kann, müsste man diese korrekt aufräumen. Da es keinen "richtigen" Konstruktor gibt, müsste man hier selbst etwas implementieren und per Konvention arbeiten. Ein statisches construct() könnte bspw. entweder die existierende Instanz zurückgeben oder eine neue Instanz erzeugen, dann könnte man halbwegs sicher stellen, dass es nur eine Instanz dieser Klasse gibt.
Wichtig ist hierbei, dass die instance Property statisch ist, das bedeutet nämlich, dass wir folgendes machen können: Player.instance.do_stuff(). So einfach können wir von überall aus auf die Player-Instanz zugreifen.
Wenn es also bspw. einen NPC gibt, der sein Verhalten abhängig vom Spielerzustand ändern soll, dann kann er einfach auf den Player zugreifen ohne ihn erst über eine Gruppe abzufragen oder ihn über Signale oder get_parent()-Ketten referenzieren zu müssen.
Der große Vorteil: Es ist total egal, wo sich der Player aktuell im SceneTree befindet. Die Stelle, die auf den Player zugreifen will, muss nicht den SceneTree-Aufbau kennen und sich auch nicht um die richtige Abarbeitung von Signalen kümmern o.ä.
Fazit
Ich finde das Pattern total wichtig und sinnvoll. Ich persönlich nutze es gerne um Systemen, die die Player-Klasse nicht kennen soll, Zugriff zum Player zu geben. Ein System welches eine Cutscene steuert kann so relativ einfach den Input vom Player disablen oder auch mal das HUD ausblenden. Ohne, dass die Player-Klasse dieses Cutscene-System kennen muss.
Was ist Deine Meinung dazu? Wie nutzt du Autoloads? Interessiert dich mein Ansatz und würdest du ihn mal in deinem eigenen Projekt ausprobieren? Wenn ja, was gefällt dir daran und wenn nein, warum nicht?