Serious Games GODOT Umsetzung Chainguard

Alles für den Start mit GODOT

Warum GODOT?

Vorab, GODOT ist 100% Open Source und kostenlos. Gerade für kleine Indie Teams und Soloentwickler reicht gerade letzteres bereits aus, um die Nutzung von GODOT zu rechtfertigen. Normalerweise ist "weil es alle machen" kein sonderlich überzeugendes Argument, jedoch sorgt das in der Game-Dev Szene dafür, dass aktuelle Tutorials und Informationen weit verbreitet sind. Auch durch die hervorragende Doku. Es existiert rings um GODOT eine aktive und hilfsbereite Community. Einsteigern wird z.B. im Subreddit r/godot oder r/GodotEngine schnell geholfen. GODOT ist quasi das "Blender" der Spieleengines.

GODOT ist sehr leichtgewichtig und mit etwas Vorwissen im Bereich Spieleentwicklung quasi an einem Nachmittag erlernbar. Wer von Unity kommt, wird sich mit GDScript, C# oder C++ in GODOT schnell zurechtfinden.

Für die allermeisten Projekte, gerade für die bei Serious Games üblichen Genres, bietet GODOT viele Features um eine schnelle 2D und 3D Entwicklung zu gewährleisten.

GODOT kann man entweder direkt über die Website downloaden, oder, meine persönliche Empfehlung, über Steam. Letzteres hat den Vorteil, dass man einerseits via Steam automatisch neue Versionen erhält und andererseits, vor dem Start der Engine, stets die aktuellen News und Patchnotes der Engine in Steam sieht.

Projektstruktur

Ein Projekt in GODOT ist am Ende ein großer Node-Tree. An einzelnen Nodes können Skripte hängen. Nodes sind entweder in keinem Raum, in 2D oder 3D. Abschnitte des Node-Trees werden als Szenen abgespeichert und können wiederverwendet werden.

Letzteres ist wie man primär auf dem Node-Tree arbeitet. Man startet in einer "main" Szene und erstellt für einzele Elemente des Spiels, wie zum Beispiel dem "Hauptmenü", "Spieler" oder "Gegner", separate Szenen welche man in die "main" inkludiert. Auch diese Szenen können dann wieder aus mehreren Szenen bestehen. So kann die Szene "Hauptmenü" zum Beispiel mehrere Szenen "Schaltfläche" instanziieren für die verschiedenen drückbaren Knöpfe.

Wie bereits oben erwähnt kann an jedes Node ein Skript gehängt werden. Skripte sind in GODOT üblicherweise in GDSkript oder C# geschrieben aber über Erweiterungen sind zum Beispiel auch C++ oder RUST möglich. Jedes Skript ist eine Klasse. Elemente kommunizieren über Signale mit den Skripten, mehr dazu im Bereich Lifecycle.

Für dieses Projekt bleiben wir bei GDSkript, dies ist für den Einstieg am einfachsten und für die allermeisten Projekte gut geeignet.

Lifecycle

Allgemein

Ganz allgemein gesprochen geht es beim Lifecycle einer Engine darum in welcher Abfolge welche Methoden zyklisch aufgerufen werden. Die Lifecycle Methoden von GODOT lassen sich grob in 3 Kategorien einordnen. Methoden, welche direkt auf den Lifecycle des Node selbst reagieren, Methoden welche auf Aktionen im Node-Tree reagieren und Signale, welche vor allem Interaktionen zwischen Nodes beschreiben.

Methoden

Methoden die auf den Lifecycle selbst reagieren.

  • void _ready()
    From GODOT 3.0 Doku En:
    "Called when the node is "ready", i.e. when both the node and its children have entered the scene tree. If the node has children, their _ready() callbacks get triggered first, and the parent node will receive the ready notification afterwards."

    Übersetzt mit Gemini De:
    Wird aufgerufen, wenn der Node „bereit“ (ready) ist, d. h. wenn sowohl der Node als auch seine Child-Nodes in den Scene Tree eingetreten sind. Wenn der Node Child-Nodes hat, werden deren _ready()-Callbacks zuerst ausgelöst, und der Parent-Node erhält die Ready-Benachrichtigung danach.

  • void _process(delta: float)
    From GODOT 3.0 Doku En:
    Called on each idle frame, prior to rendering, and after physics ticks have been processed. delta is the time between frames in seconds.

    Übersetzt mit Gemini De:
    Wird bei jedem Idle-Frame aufgerufen, vor dem Rendering und nachdem die Physik-Ticks verarbeitet wurden. delta ist die Zeit zwischen den Frames in Sekunden.

  • void _physics_process(delta: float)
    From GODOT 3.0 Doku En:
    Called once on each physics tick, and allows Nodes to synchronize their logic with physics ticks. delta is the logical time between physics ticks in seconds and is equal to Engine.time_scale / Engine.physics_ticks_per_second.

    Übersetzt mit Gemini De:
    Wird einmal bei jedem Physik-Tick aufgerufen und ermöglicht es Nodes, ihre Logik mit den Physik-Ticks zu synchronisieren. delta ist die logische Zeit zwischen den Physik-Ticks in Sekunden und entspricht Engine.time_scale / Engine.physics_ticks_per_second.

Methoden die auf im Kontext des Node-Tree reagieren.

  • void _input(event: InputEvent)
    From GODOT 3.0 Doku En:
    Called when there is an input event. The input event propagates up through the node tree until a node consumes it.

    Übersetzt mit Gemini De:
    Wird aufgerufen, wenn ein Input-Event (Eingabeereignis) auftritt. Das Input-Event pflanzt sich durch den Node-Baum nach oben fort, bis ein Node es konsumiert.

  • void add_child(node: Node, force_readable_name: bool = false, internal: InternalMode = 0)
    From GODOT 3.0 Doku En:
    Adds a child node. Nodes can have any number of children, but every child must have a unique name. Child nodes are automatically deleted when the parent node is deleted, so an entire scene can be removed by deleting its topmost node.

    Übersetzt mit Gemini De:
    Fügt einen Child-Node hinzu. Nodes können eine beliebige Anzahl von Kindern haben, aber jedes Kind muss einen eindeutigen Namen besitzen. Child-Nodes werden automatisch gelöscht, wenn der Parent-Node gelöscht wird, sodass eine gesamte Szene entfernt werden kann, indem ihr oberster Node gelöscht wird.

  • void _enter_tree()
    From GODOT 3.0 Doku En:
    Called when the node enters the SceneTree (e.g. upon instantiating, scene changing, or after calling add_child() in a script). If the node has children, its _enter_tree() callback will be called first, and then that of the children.

    Übersetzt mit Gemini De:
    Wird aufgerufen, wenn der Node in den SceneTree eintritt (z. B. bei der Instanziierung, einem Szenenwechsel oder nach dem Aufruf von add_child() in einem Skript). Wenn der Node Child-Nodes hat, wird zuerst sein eigener _enter_tree()-Callback aufgerufen und anschließend die Callbacks der Kinder.

  • void _exit_tree()
    From GODOT 3.0 Doku En:
    Called when the node is about to leave the SceneTree (e.g. upon freeing, scene changing, or after calling remove_child() in a script). If the node has children, its _exit_tree() callback will be called last, after all its children have left the tree.

    Übersetzt mit Gemini De:
    Wird aufgerufen, wenn der Node kurz davor steht, den SceneTree zu verlassen (z. B. beim Löschen/Freeing, einem Szenenwechsel oder nach dem Aufruf von remove_child() in einem Skript). Wenn der Node Child-Nodes hat, wird sein _exit_tree()-Callback zuletzt aufgerufen, nachdem alle seine Kinder den Baum verlassen haben.

Den Rest der "Standard" Lifecycle Methoden und genauere Beschreibungen findet man in der GODOT 3.0 Doku

Signale

Nodes können eigene Lifecycle Events auslösen, die Signale. Signale beschreiben meist Interaktionen zwischen Nodes. Zum Beispiel kann ein Area2D/3D Element ein Signal senden und damit den Durchlauf der zugehörigen Lifecyclemethode triggern, sobald ein Objekt in die Area eindringt. Die Methode ist dabei für das Area2D/3D Node unique. Sprich 2 Instanzen dieses Nodes können in 2 verschiedenen Skripten unterschiedliche Events triggern. So kann bei einem Pong Klon ein Area2D Node die Kollisionsfläche des Spielers repräsentieren und ein anderes das Spielfeldende. Je nachdem welche Area der Ball berührt wird er abgelenkt oder zählt als aus.
Die Fragen und Antworten in diesem FAQ sind nicht explizit auf Chainguard bezogen. Sie stellen allgemeine Probleme dar, welche es häufig gibt, wenn jemand gerade, in Godot, mit einem eigenen Projekt anfängt.

Die Node existiert zum Zeitpunkt der Variablen-Zuweisung noch nicht im Scene Tree. Das @onready-Keyword kann verwendet werden, um die Zuweisung zu verzögern, bis die Node geladen ist.
@onready var sprite = $Sprite2D

  • _process läuft frame-abhängig (so schnell wie möglich) und ist ideal für visuelle Updates wie Animationen.
  • _physics_process läuft in festen Intervallen (Standard: 60 FPS) und muss für alle physikalischen Berechnungen und Bewegungen genutzt werden, um Jitter zu vermeiden.
Funktion Frequenz Einsatzbereich
_process(delta) Variabel (FPS) Grafik, UI, Timer
_physics_process(delta) Konstant (60/s) Bewegung, Kollision
  • Nicht verbunden: Das Signal wurde im „Node“-Tab oder via Code (signal.connect()) nicht verknüpft.
  • Falsche Signatur: Die Empfänger-Funktion erwartet eine andere Anzahl an Argumenten als das Signal sendet.
  • Objekt-Referenz: Das Objekt, das das Signal senden soll, ist null oder wurde bereits aus dem Scene-Tree entfernt.
  • Prüfe, dass der Process Mode des Node nicht auf "Disabled" steht oder die Zeit (Engine.time_scale) auf 0 gesetzt wurde.
  • Ein CharacterBody2D bewegt sich nicht automatisch durch das Setzen der velocity. Damit die Engine die physikalische Verschiebung und Kollisionsprüfung berechnet, muss move_and_slide() aufgerufen werden.

    func _physics_process(delta):
    velocity = Vector2(100, 0) # Geschwindigkeit setzen
    move_and_slide() # Bewegung ausführen
  • Fehlendes Shape: Ein Physik-Node (Area2D, CharacterBody2D) benötigt zwingend einen CollisionShape2D oder CollisionPolygon2D als Kind-Element, dem im Inspector eine Form (z. B. CircleShape2D) zugewiesen wurde.
  • Layer & Mask: Das Objekt muss auf einem Layer liegen, den das andere Objekt mit seiner Mask scannt. (Layer = "Wer bin ich?", Mask = "Wen suche ich?").
  • Falscher Signaltyp: Eine Area2D erkennt nur dann andere Objekte, wenn das Signal area_entered (für andere Areas) oder body_entered (für Physik-Körper) korrekt verbunden wurde.
  • Fehlende Export Templates: Stelle sicher, dass die Export-Vorlagen passend zu der Godot-Version installiert sind.
  • Keine Hauptszene definiert: Das Spiel weiß nicht, welche Szene zuerst geladen werden soll.
  • Dateipfade & Case Sensitivity: Während Windows Pfade wie Bild.png und bild.png gleich behandelt werden, unterscheidet Godot (besonders beim Export für Linux/Android) strikt zwischen Groß- und Kleinschreibung.
  • Nicht-Ressourcen-Dateien: Dateien wie .json oder .txt werden oft nicht automatisch exportiert. Füge sie im Export-Dialog hinzu.
Godot trennt strikt zwischen dem Editor-Modus und dem Laufzeit-Modus. Änderungen, die man während der Testphase vornimmt, werden standardmäßig nicht dauerhaft gespeichert.

Nimm dauerhafte Änderungen immer nur vor, wenn das Spiel nicht läuft, oder kopiere die Werte aus dem Remote-Inspector und füge sie nach dem Beenden im Local-Inspector wieder ein.
Die Wahl hängt davon ab, ob physikalische Kräfte (wie Blockaden) oder lediglich Erkennung (Überlappung) gewünscht sind.
  • Area2D (Trigger): Erkennt, wenn etwas in einen Bereich eintritt, bietet aber keine physische Barriere. Ideal für Münzen, Fallen, Zonen-Wechsel oder Schalter.
  • PhysicsBody2D (Kollision): Interagiert mit der Physik-Engine, um Bewegung zu stoppen oder zu beeinflussen.
    • CharacterBody2D: Für Spielfiguren (via Code gesteuert).
    • StaticBody2D: Für unbewegliche Objekte wie Böden oder Wände.
    • RigidBody2D: Vollständige Physik-Simulation (fällt, rollt, prallt ab).
Diese Fehlermeldung bedeutet, dass GODOT einen Namen (Variable, Funktion oder Klassenname) in deinem Skript nicht zuordnen kann.
  • Tippfehler: Prüfe die exakte Schreibweise.
  • Gültigkeitsbereich (Scope): Variablen, die innerhalb einer Funktion definiert wurden, sind außerhalb dieser Funktion nicht bekannt.
  • Nicht deklariert: Man versucht eine Variable zu nutzen, bevor sie mit var oder const erstellt wurde.
  • Fehlender Node-Zugriff: Man versuch eine Node direkt anzusprechen (z. B. Sprite2D.hide()), ohne vorher eine Referenz via $Sprite2D oder get_node() zu erstellen.
  • Fehlendes add_child(): Nach dem Instanziieren musst die Node explizit einem Elternknoten zugewiesen werden.
  • Reihenfolge: Wenn man add_child() vergisst, existiert die Instanz "im Nichts" und wird am Ende des Frames oft vom Garbage Collector gelöscht.
  • Position: Manchmal wird das Objekt bei (0, 0) gespawnt und liegt außerhalb des Sichtfelds der Kamera oder hinter einem anderen UI-Element.
# 1. Szene laden
var scene = load("res://enemy.tscn")

2. Instanz erstellen
var enemy = scene.instantiate()

3. Zum Baum hinzufügen
add_child(enemy)

4. Position setzen
enemy.global_position = Vector2(100, 100)