Nachdem wir die Bewegung unseres Spielers oder unserer Spielerin und die solide Kollision mit einer Wand gemeistert haben, ist der nächste Schritt, die Interaktion mit Objekten zu lernen, die man einsammeln oder auslösen kann, ohne dass sie einen physisch blockieren.
Hierfür kommt ein anderer wichtiger Node-Typ ins Spiel: der Area2D. Im Gegensatz zu den Physics Bodies (CharacterBody2D
, StaticBody2D
, RigidBody2D
), die auf solide Kollisionen reagieren (Stoßen, Abprallen), ist eine Area2D
dafür da, Überlappungen zu erkennen. Sie blockiert nichts, sondern meldet nur, wenn ein anderer Physics Body oder eine andere Area2D
in sie eintritt, sie verlässt oder sich darin befindet.
Um auf solche Ereignisse zu reagieren („Ein Body ist in mich eingetreten!“), nutzen wir ein fundamentales Konzept von Godot: Signale. Nodes können Signale aussenden, wenn etwas Bestimmtes passiert. Andere Nodes können sich mit diesen Signalen verbinden, um eine eigene Funktion auszuführen, wenn das Signal emittiert wird. Wenn du schonmal in anderen Programmiersprachen mit Events gearbeitet hast, kannst du dir sicher schon was darunter vorstellen.
Wir erstellen eine einfache „Münze“, die unser:e Spieler:in einsammeln kann.
Du wirst:
- Eine neue Scene für ein wiederverwendbares Objekt erstellen (eine Münze).
- Den
Area2D
Node verwenden. - Dem
Area2D
eineCollisionShape2D
(für die Erkennung) und einSprite2D
(für die Grafik) hinzufügen. - Ein Script an die
Area2D
anhängen. - Ein Signal (
body_entered
) desArea2D
Nodes mit einer Funktion in unserem Script verbinden. - In dieser Funktion prüfen, ob der eintretende Body der Player ist und die Münze dann „sammeln“ (zunächst indem wir sie aus der Scene entfernen).
Schritt 1: Eine neue Scene für die Münze erstellen
Wir erstellen eine neue, eigenständige Scene für unsere Münze, die wir später beliebig oft in unserer World
-Scene platzieren können:
- Gehe im Menü auf Scene -> New Scene (Strg + N).
- Klicke auf „Other Node“ und wähle Area2D als Root Node und klicke Create.
- Benenne den Root Node im Scene Dock um, z.B. zu Coin.
- Speichere diese neue Scene: Scene -> Save Scene (Strg + S). Nenne sie
coin.tscn
und speichere sie im scene-Ordner.
Schritt 2: Visuelle Darstellung und Kollisionsform der Münze hinzufügen
Wie schon beim Player, benötigt auch die Münze eine visuelle Darstellung und eine Form für die Kollisionserkennung.
- Stelle sicher, dass du im Scene Dock den Coin (
Area2D
) Node ausgewählt hast. - Klicke auf den Button „+“ (Child Node hinzufügen) oberhalb des Scene Trees.
- Suche in der Liste nach Sprite2D, wähle ihn aus und klicke Create.
- Wähle den neuen
Sprite2D
Node im Scene Dock aus. Gehe im Inspector zum Abschnitt „Texture“ und lade eine Bild-Datei, die einem Coin ähnelt. Du kannst dir hier z.B. bei itch.io welche herunterladen - Klicke erneut auf den Button „+“ (Child Node hinzufügen), während der Coin Node (
Area2D
) noch ausgewählt ist. - Suche in der Liste nach CollisionShape2D, wähle ihn aus und klicke Create.
- Wähle den neuen
CollisionShape2D
Node im Scene Dock aus. Im Inspector siehst du die Eigenschaft „Shape“, die momentan „Empty“ ist. - Klicke auf „<empty>“ neben „Shape“ und wähle im Dropdown-Menü „New CircleShape2D“. Eine Kreisform ist oft passender für Münzen.
- Godot erstellt eine neue Form. Klicke auf das neu erschienene
CircleShape2D
Symbol. Im Viewport siehst du nun die Kollisionsform als blauer Kreis. - Ziehe die Ankerpunkte des Kreises im Viewport so, dass die Kollisionsform den
Sprite2D
(die Münze) umschließt. Du kannst auch denRadius
im Inspector unter „Shape“ anpassen (dazu nochmal auf den gerade ausgewählten CircleShape2D klicken). - Speichere die Scene
coin.tscn
(Strg + S).
Schritt 3: Script an die Münze anhängen und Signal verbinden
Nun kommt der Teil, der die Interaktion ermöglicht. Wir hängen ein Script an die Coin
(Area2D
) und verbinden ihr body_entered
Signal, das ausgelöst wird, wenn ein PhysicsBody
in die Area2D
eintritt.
- Wähle im Scene Dock den Coin (
Area2D
) Node aus. - Klicke auf den Button „Attach Script“ (Pergamentrolle mit Plus) oberhalb des Scene Trees (oder über das Kontextmenü).
- Stelle sicher, dass „Base Type“ auf
Area2D
steht und der Pfadcoin.gd
vorschlägt. Das „Template“ kann auf „Node“ bleiben. - Klicke „Create“. Du siehst nun den Script Editor mit dem Basis-Template.
- Ergänze
class_name Coin extends Area2D
am Anfang des Scripts. - Stelle sicher, dass der Coin Node im Scene Dock ausgewählt ist.
- Gehe ins Node Dock (dieses befindet sich standardmäßig rechts neben dem Inspector, unter den Reitern „Inspector“ und „Node“).
- Im Node Dock findest du eine Liste von Signalen, die der ausgewählte Node (
Area2D
) aussenden kann. Suche das Signalbody_entered(body: PhysicsBody2D)
. - Klicke doppelt auf dieses Signal oder wähle es aus und klicke unten im Node Dock auf „Connect…“.
- Ein Fenster „Connect a Signal“ öffnet sich. Im oberen Bereich siehst du das Signal (
body_entered
). Im unteren Bereich siehst du den Receiver. Stelle sicher, dass der Coin Node der Receiver ist (er ist bereits ausgewählt, da wir das Script an ihn gehängt haben). Die Methode, die generiert wird, sollte so aussehen:_on_body_entered
. - Klicke auf „Connect“. Godot erstellt automatisch die Funktion
_on_body_entered(body)
in deinemcoin.gd
Script und springt zum Script Editor.
Schritt 4: Kommunikation über Signale: Player sammelt die Münze ein
Um dem Player mitzuteilen, dass er eine Münze berührt hat und diese dann „einsammeln“ soll, nutzen wir eigene Signale. Das ist der Godot-Weg, um Nodes miteinander kommunizieren zu lassen, ohne dass sie direkt voneinander wissen müssen. Die Münze signalisiert lediglich, dass sie eingesammelt wurde. Unser Player kann dann auf dieses Signal hören und reagieren.
Hierfür werden wir:
- Ein benutzerdefiniertes Signal in unserem
coin.gd
-Script deklarieren und aussenden. - Im
player.gd
-Script eine Funktion erstellen, die auf das Einsammeln reagiert.
Die Verbindung zwischen dem Münz-Signal und der Player-Funktion in unserer world.tscn
herstellen, da die World
-Scene sowohl den Player:in als auch die Münzen kennt.
Schritt 5: Den Player identifizieren (Gruppen)
Wenn ein anderes Objekt jetzt das CollisionShape2D unserer Münze berührt (in den Körper eintritt), wird das Signal body_entered
gesendet und jeder Abonnement informiert. In unserem Fall wird die Methode _on_body_entered(body)
aufgerufen. Die Variable body
in der Funktion ist genau der Node, der gerade in unsere Münz-Area eingetreten ist.
Jetzt müssen wir nur noch sicherstellen, dass der body auch wirklich ein Player ist. Nicht das die Gegner uns alle Münzen klauen! Godot bietet ein sehr flexibles System, um Nodes zu identifizieren: Gruppen. Du kannst Nodes zu einer oder mehreren Gruppen hinzufügen. Dies ist der Weg, um Nodes nach ihrer „Rolle“ oder ihrem „Typ“ im Spiel zu kennzeichnen, anstatt sie direkt über ihren Namen oder ihren spezifischen Node-Typ zu identifizieren.
Den Player einer Gruppe hinzufügen:
- Öffne deine Scene
player.tscn
. - Wähle im Scene Dock den Root Node Player (
CharacterBody2D
) aus. - Gehe ins Node Dock (rechts neben dem Inspector). Dort findest du den Reiter „Groups“ (gleich neben „Signals„).
- Klicke auf das „+„. Es öffnet sich ein neues Fenster mit dem Namen „Create New Group“. Gib in das Feld „Name“ den Namen
Player
ein und klicke auf „Ok“.- Wichtig: Gruppennamen sind case-sensitive. Die Groß-Kleinschreibung ist später wichtig.
- Du siehst nun
Player
in der Liste der Gruppen, zu der dein Player gehört (am Haken zu erkennen).
Speichere die Scene player.tscn
(Strg + S).
Schritt 6: Ein Signal in der Münze deklarieren und aussenden
Öffne dein coin.gd
Script. Ganz oben im Script, unter extends Area2D
, deklarieren wir ein neues Signal namens collected
. Danach wird dieses Signal in der _on_Coin_body_entered
-Funktion ausgesendet, bevor die Münze entfernt wird.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class_name Coin extends Area2D # Signal, das ausgesendet wird, wenn die Münze eingesammelt wird. signal collected func _on_body_entered(body): if body.is_in_group("Player"): print("Münze berührt!") # Sende das "collected"-Signal aus. # Jeder Node, der mit diesem Signal verbunden ist, wird benachrichtigt. emit_signal("collected") # Münze am Ende des aktuellen Frames sicher aus der Szene entfernen queue_free() |
body.is_in_group("players")
gibttrue
zurück, wenn der Node in der Gruppe „players“ ist, ansonstenfalse
.signal collected
: So deklarieren wir ein benutzerdefiniertes Signal. Es ist sinnnvoll, Signale am Anfang des Scripts zu deklarieren.emit_signal("collected")
: Ruft das Signalcollected
auf und informiert alle damit verbundenen Nodes.queue_free()
ist der Weg in Godot, um einen Node und alle seine Kinder sicher aus der Scene zu entfernen. Sie markiert den Node zur Löschung am Ende des aktuellen Frames. Das Löschen erst am Ende des Frames ist wichtig, um Fehler zu vermeiden, wenn der Node noch aktiv Operationen ausführt.
Schritt 7: Eine Sammel-Funktion im Player erstellen
Öffne dein player.gd
Script. Wir fügen eine einfache Funktion collect_coin()
hinzu, die später Punkte hinzufügen, Sound abspielen oder andere Logik ausführen könnte, wenn der Player eine Münze einsammelt.
1 2 3 4 5 6 7 8 9 10 |
class_name Player extends CharacterBody2D var speed = 200 # Einheit: Pixel pro Sekunde # ... (Deine _ready, _process, _physics_process Funktionen bleiben unverändert) # Neue Funktion: Wird aufgerufen, wenn der Player:in eine Münze einsammelt func collect_coin(): print("Player hat die Münze erfolgreich gesammelt!") # Hier kommt später Logik wie Punkte erhöhen, Sound abspielen etc. |
Schritt 8: Das Signal in der World-Scene verbinden
Damit unser Player auf das collected
-Signal der Münze reagieren kann, müssen wir eine Verbindung herstellen. Die World
-Scene ist der ideale Ort dafür, da sie sowohl unseren Player:in als auch die instanziierten Münzen kennt.
Script an World-Scene anhängen:
- Öffne
world.tscn
. - Wähle den Root Node World aus.
- Klicke auf „Attach Script“ (Pergamentrolle mit Plus) oberhalb des Scene Trees.
- Als „Base Type“ sollte
Node
stehen, und der Pfadworld.gd
. Klicke „Create“. - Speichere das
world.gd
Script (Strg + S).
- Öffne
Verbindung im
world.gd
Script herstellen: Wir nutzen die_ready()
-Funktion derWorld
-Scene, da diese aufgerufen wird, sobald die Scene und alle ihre Kinder (inklusive Player:in und Münzen) bereit sind. Wir gehen alle Kinder der World durch, identifizieren Player:in und Münzen und stellen die Verbindungen her.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class_name World extends Node # Called when the node enters the scene tree for the first time. func _ready(): # Referenz auf den Player holen var player_node = null for child in get_children(): # Prüfen, ob das Kind eine Instanz von Player ist if child is Player: player_node = child break # Player gefunden, Schleife beenden if player_node == null: print("Fehler: Player-Node in der Szene nicht gefunden!") return # Abbruch, wenn kein Player da ist # Alle Münz-Instanzen in der Szene finden und deren Signal mit dem Player verbinden for child in get_children(): # Prüfen, ob das Kind eine Instanz unserer Coin-Szene ist # Hier nutzen wir auch den class_name für Coin, if child is Coin: # Verbinde das 'collected'-Signal der Münze mit der 'collect_coin'-Funktion des Players child.collected.connect(player_node.collect_coin) print("Münze ('", child.name, "') mit Player verbunden.") |
get_children()
: Gibt eine Liste aller direkten Kinder dieses Nodes (derWorld
) zurück. Darüber iterieren wir.if child is Player
: Da wir unserem Player-Scriptclass_name Player
gegeben haben, können wir hier direkt den Typ des Nodes prüfen. Das ist eine sehr robuste Methode.child.collected.connect(player_node.collect_coin)
: Dies ist der Kern der Signalverbindung im Code. Es sagt: „Wenn dascollected
-Signal von diesemchild
(unserer Münze) ausgesendet wird, dann rufe die Funktioncollect_coin()
auf demPlayer
auf.“
Das Sammeln der Münze testen
Was haben wir gemacht, damit alles funktioniert? Eine ganze Menge:
- Player zur Gruppe „Player“ hinzugefügt
- Im Münz-Script
signal collected
definiert undemit_signal("collected")
in der Funktion_on_body_entered(body)
aufgerufen und die Münze entfernt. - Im Player die Funktion
func collect_coin()
implementiert. - Im World-Script alle Münzen mit dem Player verbunden.
Nun müssen wir nur noch die Münzen in der World platzieren.
- Öffne
world.tscn
. Platziere ein paar Instanzen unserer Münz-Scene im Level (zieh die gespeichertecoin.tscn
Datei aus dem Filesystem Dock in den Viewport oder auf den World Node im Scene Dock). Passe ggf. die Größe an (im Inspector unter Node2D -> Transform -> Scale) - Führe
world.tscn
aus (F5).
Jetzt solltest du die Münzen sehen, und wenn du mit deinem Player über eine drüberläufst, sollte sie verschwinden und sowohl „Münze berührt!“ (aus dem Coin-Script) als auch „Player hat die Münze erfolgreich gesammelt!“ (aus dem Player-Script) im Output Dock (unten im Editor) erscheinen.
Dein nächster Schritt
Sehr gut! Du hast jetzt erfolgreich interaktive Objekte erstellt, die auf Überlappungen reagieren, und dabei die Signal-Kommunikation in Godot genutzt. Wir haben gelernt:
- Wie man Area2D Nodes nutzt, um Überlappungen zu erkennen.
- Das Konzept von Signalen in Godot, wie man sie deklariert, aussendet und im Code verbindet, um auf Ereignisse zu reagieren.
- Wie man Gruppen und
class_name
verwendet, um Nodes robust zu identifizieren. - Wie man durch das Verbinden von Signalen in einer übergeordneten Szene (der
World
) die Kommunikation zwischen verschiedenen instanziierten Szenen organisiert.
Demnächst werden wir weitere fortgeschrittene Anwendungen des Signalsystems beleuchten und beispielsweise ein simples UI-Element (HUD) erstellen, das die gesammelten Münzen anzeigt.
Für unseren nächsten Schritt bringen wir mit Animationen noch mehr Bewegung in die Szene.