Warum Kollisions-Layer wichtig sind
Kollisions-Layer sind die Grundlage aller Physik-Interaktionen in Godot 4 — Bewegung, Trefferabfrage, Raycasting, Area-Trigger und mehr. Den Unterschied zwischen Layer und Mask zu verstehen ist entscheidend, und ihn misszuverstehen ist einer der häufigsten Stolpersteine für Einsteiger wie auch erfahrene Entwickler.
Layer vs. Mask — Das mentale Modell
Jedes Physik-Objekt in Godot hat zwei Bitmask-Eigenschaften:
- Layer = „Ich existiere auf diesen Layern" (was ich bin)
- Mask = „Ich scanne diese Layer" (was ich sehen / treffen kann)
Grundregel
A kollidiert mit B, wenn A's Mask B's Layer enthält ODER B's Mask A's Layer enthält. Nur eine Seite muss die andere „sehen", damit eine Kollision auftritt.
Layer in den Projekteinstellungen benennen
Bevor du irgendeinen Code schreibst, benenne deine Layer. Das macht den Inspector deutlich einfacher zu nutzen und verhindert Verwirrung, wenn dein Projekt wächst.
Projekteinstellungen > Layer-Namen > 2D-Physik (oder 3D-Physik):
| Layer-Nr. | Name | Zweck |
|---|---|---|
| 1 | Player | Die Spielerfigur |
| 2 | Enemy | Alle Gegner-Körper |
| 3 | Environment | Wände, Böden, Plattformen |
| 4 | Projectile | Geschosse, Pfeile, Zauber |
| 5 | Pickup | Items, Münzen, Heilpakete |
| 6 | Trigger | Sensoren, Spawn-Zonen, Checkpoints |
Layer im Code setzen (GDScript)
Godot 4 bietet zwei Ansätze — die wertbasierte API (empfohlen) und die direkte Bitmask-Zuweisung:
# Godot 4 API — value-based (1-indexed, recommended)
set_collision_layer_value(1, true) # I am on layer 1
set_collision_mask_value(2, true) # I detect layer 2
# Or use bitmask directly
collision_layer = 1 # Layer 1 only (bit 0)
collision_mask = 6 # Layers 2 and 3 (bits 1 + 2 = 6)
# Read current state
var on_layer_1: bool = get_collision_layer_value(1)
var scans_layer_2: bool = get_collision_mask_value(2)
Bitmask-Falle
Layer-Nummern sind bei set_collision_layer_value() 1-basiert, aber die zugrunde liegende Bitmask ist 0-basiert. Layer 1 = Bit 0 = Wert 1, Layer 2 = Bit 1 = Wert 2, Layer 3 = Bit 2 = Wert 4. Im Zweifel nutze die wertbasierte API, um Fehler zu vermeiden.
Gängige Layer-Schemata
Platformer / Side-Scroller
| Objekt | Layer | Mask | Erklärung |
|---|---|---|---|
| Player | 1 | 2, 3, 5, 6 | Erkennt Gegner, Umgebung, Pickups, Trigger |
| Enemy | 2 | 1, 3 | Erkennt Spieler und Umgebung |
| Environment | 3 | — | Passiv — wird von anderen erkannt |
| Projectile | 4 | 2, 3 | Trifft Gegner und Wände |
| Pickup | 5 | — | Passiv — der Spieler erkennt es |
| Trigger | 6 | 1 | Erkennt nur den Spieler |
Top-Down / Roguelike
| Objekt | Layer | Mask | Erklärung |
|---|---|---|---|
| Player | 1 | 2, 3, 5, 6 | Erkennt Gegner, Wände, NPCs, Sensoren |
| Enemy | 2 | 1, 3 | Erkennt Spieler und Wände |
| Wall | 3 | — | Passiv |
| Bullet | 4 | 1, 2, 3 | Trifft Spieler, Gegner und Wände |
| NPC | 5 | 3 | Kollidiert nur mit Wänden |
| Sensor | 6 | 1 | Erkennt das Betreten durch den Spieler |
Area2D / Area3D Layer
Areas nutzen dasselbe Layer/Mask-System. Zusätzlich haben sie zwei Umschalt-Eigenschaften:
-
monitoring— Wenntrue, erkennt diese Area aktiv andere Objekte, die sie betreten. -
monitorable— Wenntrue, können andere Areas diese Area erkennen.
Signale: area_entered vs. body_entered
body_entered wird ausgelöst, wenn ein PhysicsBody (CharacterBody, RigidBody, StaticBody) die Area betritt. area_entered wird ausgelöst, wenn eine andere Area sie betritt. Achte darauf, für deinen Anwendungsfall das richtige Signal zu verbinden.
# Pickup Area2D — detect when the Player body enters
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node2D) -> void:
if body.is_in_group("player"):
collect()
queue_free()
RayCast Kollisionsmaske
RayCast-Nodes haben nur eine Mask (keinen Layer), weil sie Detektoren und keine physischen Körper sind. Setze die Mask, um zu filtern, was der Strahl treffen kann:
# RayCast that only detects enemies (layer 2)
$RayCast2D.collision_mask = 2 # Layer 2 = Enemy
# Or use the value-based API:
$RayCast2D.set_collision_mask_value(1, false) # Ignore Player
$RayCast2D.set_collision_mask_value(2, true) # Detect Enemy
$RayCast2D.set_collision_mask_value(3, false) # Ignore Environment
# Line-of-sight ray that ignores projectiles
$LineOfSight.collision_mask = 0
$LineOfSight.set_collision_mask_value(1, true) # Player
$LineOfSight.set_collision_mask_value(3, true) # Environment
Praktisches Beispiel: Player – Enemy – Projectile
Hier ist ein vollständiges Layer-Setup für ein typisches Action-Spiel. Kommentare zeigen die Überlegung hinter jeder Entscheidung:
# Player (CharacterBody2D)
# Layer: 1 (Player) — "I am the player"
# Mask: 2 (Enemy), 3 (Environment), 5 (Pickup)
# — I collide with enemies, walls, and can pick up items
# Enemy (CharacterBody2D)
# Layer: 2 (Enemy) — "I am an enemy"
# Mask: 1 (Player), 3 (Environment)
# — I collide with the player and walls
# Player Bullet (Area2D)
# Layer: 4 (Projectile) — "I am a projectile"
# Mask: 2 (Enemy), 3 (Environment)
# — I hit enemies and walls, but NOT the player who fired me
# Enemy Bullet (Area2D)
# Layer: 4 (Projectile) — "I am a projectile"
# Mask: 1 (Player), 3 (Environment)
# — I hit the player and walls, but NOT the enemy who fired me
Tipp: Friendly-Fire verhindern
Beachte, dass Player-Bullets Layer 2 (Enemy) maskieren, aber nicht Layer 1 (Player), und Enemy-Bullets Layer 1 (Player) maskieren, aber nicht Layer 2 (Enemy). So verhinderst du Friendly-Fire rein durch die Layer-Konfiguration — ganz ohne Code.
Debugging-Tipps
- Sichtbare Kollisionsformen: Gehe im Editor zu Debug > Sichtbare Kollisionsformen, um alle Kollisionsformen zur Laufzeit darzustellen. Das offenbart sofort fehlende oder falsch ausgerichtete Collider.
- Inspector-Prüfung: Wähle einen Node aus und klappe Collision > Layer und Collision > Mask im Inspector auf. Fahre über jedes Bit, um seinen Namen zu sehen (falls du sie in den Projekteinstellungen benannt hast).
-
Zur Laufzeit ausgeben:
print("Layer: ", collision_layer, " Mask: ", collision_mask), um Bitmask-Werte während des Spielens zu verifizieren.
Migrationsänderungen Godot 3 → 4
| Godot 3 | Godot 4 |
|---|---|
set_collision_layer_bit(bit, value) |
set_collision_layer_value(layer, value) |
set_collision_mask_bit(bit, value) |
set_collision_mask_value(layer, value) |
bit-Parameter war 0-basiert
|
layer-Parameter ist 1-basiert
|
| 20 Layer verfügbar | 32 Layer verfügbar |
Migrations-Stolperstein
Wenn du ein Godot-3-Projekt portierst, denke daran, dass set_collision_layer_bit(0, true) zu set_collision_layer_value(1, true) wird. Der Index verschiebt sich um +1. Übersiehst du das, sind alle deine Layer um eins verschoben.
Häufige Fehler
1. Vergessen, die Mask zu setzen
Dein Objekt hat einen Layer, aber die Mask ist leer (alles Nullen). Das Objekt existiert in der Physikwelt, erkennt aber nichts. Andere Objekte mit passenden Masken erkennen es weiterhin, aber move_and_slide() auf diesem Objekt geht durch alles hindurch.
2. Layer mit Mask verwechseln
Den Layer des Spielers auf 2 (Enemy) setzen statt seine Mask. Jetzt ist der Spieler ein Gegner, soweit es die Physik-Engine betrifft. Merke dir immer: Layer = was ich bin, Mask = was ich scanne.
3. Bitmask-Werte statt Layer-Nummern verwenden
set_collision_layer_value(4, true) schreiben in dem Glauben, es setze den Bitmask-Wert 4 (Layer 1+2). In Wirklichkeit aktiviert es Layer 4. Die wertbasierte API nimmt Layer-Nummern, keine Bit-Werte.
4. Einseitige Erkennung, wo Beidseitigkeit erwartet wird
Objekt A maskiert B's Layer, aber B maskiert nicht A's Layer. move_and_slide() auf A kollidiert mit B, aber move_and_slide() auf B geht durch A hindurch. Damit sich zwei CharacterBody-Nodes gegenseitig blockieren, müssen beide den Layer des anderen in ihrer Mask haben.
Physik-Setup mit Godot MCP Pro automatisieren
Schluss mit dem manuellen Umschalten von Layer-Kontrollkästchen. Lass die KI dein gesamtes Kollisions-Setup in Sekunden konfigurieren — inklusive Layer-Benennung, Masken-Zuweisung und dem Hinzufügen von Raycasts.
Godot MCP Pro holen — $15setup_collision
set_physics_layers
get_physics_layers
get_collision_info
add_raycast