Quick answer: Set Area2D monitoring = true and its collision_mask to include the static body’s layer. Connect to body_entered (not area_entered) for body detection. Layers and masks are independent — both sides need proper config.
Here is how to fix Godot Area2D not detecting StaticBody2D. You set up a damage zone with an Area2D. You connect body_entered. You walk your player (CharacterBody2D) into the zone — signal fires. You place a static destructible wall and expect the zone to detect when the wall is placed inside it — nothing. Godot’s collision system has two separate concepts (layers and masks) and multiple signal variants for areas, bodies, and mixed combinations. Getting the combination wrong silently produces no detection.
The Symptom
Area2D signals body_entered and body_exited do not fire when a StaticBody2D enters the area’s collision shape. Visual inspection shows the shapes overlap. Other types of bodies (CharacterBody2D, RigidBody2D) may detect correctly, indicating the Area2D itself works — but static bodies do not.
Variant: areas detect each other (area_entered) but not bodies. Or detection works on entry but not exit. Or two-way detection is expected but only one direction works.
What Causes This
Monitoring disabled. Area2D has a monitoring boolean that controls whether it actively watches for overlaps. If disabled, no signals fire regardless of collision configuration. Default is true, but can be toggled off inadvertently.
Collision mask does not include body’s layer. The Area2D’s collision_mask must include the bit (layer number) that the StaticBody2D is on. “Layer” = which layer I am on. “Mask” = which layers I look for. These are independent. Your static wall might be on Layer 2; if the area’s mask does not include Layer 2, it is invisible to the area.
Wrong signal connected. body_entered fires for PhysicsBody2D nodes (CharacterBody2D, RigidBody2D, StaticBody2D). area_entered fires only for other Area2D nodes. Connecting area_entered to a script that expects a body means the signal never fires for a static body.
Node added while outside and moved in programmatically. Area2D detects overlaps on physics frame updates. A body teleported into the area via position = may not immediately trigger a signal if the area was not monitoring at that frame. Waiting one frame or calling force_update_transform() helps.
CollisionShape2D disabled. Either side’s collision shape being disabled in the Inspector turns off detection. Easy to forget a temporary disable.
The Fix
Step 1: Verify monitoring. Select the Area2D. In the Inspector, confirm Monitoring is true. While you’re there, set Monitorable to true as well (lets other areas detect this one too).
extends Area2D
func _ready():
monitoring = true
monitorable = true
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node2D):
print("Body entered: ", body.name)
Step 2: Configure layers and masks. Select the StaticBody2D. Note its Collision Layer (e.g. bit 1 = layer 1, bit 2 = layer 2). Select the Area2D. Ensure its Collision Mask includes the same bit.
Godot 4 uses 1-indexed layer names in the Inspector but bitmasks internally. Layer 1 = value 1, Layer 2 = value 2, Layer 3 = value 4, etc. Most of the time the UI hides this from you — just check the matching checkbox.
For a damage zone that detects anything tagged “damageable”:
- Project Settings > Layer Names > 2D Physics: rename Layer 5 to “Damageable”
- All destructible objects: set Collision Layer = Damageable
- Damage zone Area2D: set Collision Mask = Damageable
Step 3: Connect the right signal. Four relevant signals on Area2D:
body_entered(body)— a PhysicsBody2D enteredbody_exited(body)— a PhysicsBody2D exitedarea_entered(area)— an Area2D enteredarea_exited(area)— an Area2D exited
For StaticBody2D, connect body_entered. The parameter is the body node itself; you can check body.name or body is StaticBody2D to filter.
Step 4: Handle edge cases around teleport. If you spawn a StaticBody2D inside an Area2D in the same frame, the signal may fire on the next physics tick rather than immediately. This is usually fine, but for same-frame reaction, query overlaps directly:
func _on_body_spawned(body: Node2D):
# wait one physics frame for overlaps to register
await get_tree().physics_frame
var bodies = $DamageArea.get_overlapping_bodies()
if body in bodies:
apply_damage(body)
Using get_overlapping_bodies for Snapshots
Instead of relying on entry/exit events, you can query all current overlaps via get_overlapping_bodies() on an Area2D. This is useful for damage-over-time zones that tick every second rather than on entry:
func _on_damage_timer_timeout():
for body in $DamageArea.get_overlapping_bodies():
if body.has_method("take_damage"):
body.take_damage(10)
Monitoring must still be on for this to return non-empty results, but you do not need signal connections.
3D Equivalent
Area3D has identical semantics: monitoring, monitorable, collision_layer, collision_mask, and body_entered/body_exited signals. All the same rules apply. PhysicsBody3D variants include StaticBody3D, CharacterBody3D, and RigidBody3D.
“Layers are who you are. Masks are who you look for. Both sides need to agree for detection to happen.”
Related Issues
For physics setup generally, see Godot CharacterBody2D Floor Snap. For signal connection issues, Await Signal Never Completing covers related signal patterns.
Monitoring on, mask includes body layer, body_entered signal. Three settings, detection works.