Quick answer: Make sure the Area3D has Monitoring on, a CollisionShape3D with a real shape, and a mask that includes the other object's layer. get_overlapping_bodies() only updates after a physics step, so it is empty in _ready() and in the frame something spawns. Use area_entered for other Areas and body_entered for bodies.
Area3D failures look identical — a signal that never fires — but the causes split cleanly into configuration problems and timing problems. Check configuration first; it is the cause most of the time.
Configuration checklist
- Monitoring is on. With Monitoring off, the Area detects nothing. If something else turns it off, it stays off.
- The shape exists. The Area needs a CollisionShape3D child with a Shape resource assigned and Disabled unticked. A shape on a grandchild node does not count.
- The mask includes the other object's layer. Detection depends on the Area's
collision_mask, not the body's. A body on layer 2 needs mask bit 2 set on the Area. - The right signal.
body_enteredfires for PhysicsBody3D nodes (CharacterBody3D, RigidBody3D, StaticBody3D) and GridMaps. Another Area3D triggersarea_enteredinstead, and only if that area has Monitorable on. - The handler is connected with the correct signature:
func _on_body_entered(body: Node3D).
Turn on Debug > Visible Collision Shapes to confirm the shapes actually overlap in-game. Scaled parents and offsets often put a shape somewhere other than where the mesh is.
Timing: overlaps update on the physics step
Overlap lists are computed by the physics server. They are empty until at least one physics frame has run after the Area or the body enters the tree:
func _ready():
print(get_overlapping_bodies()) # always [] here
await get_tree().physics_frame
await get_tree().physics_frame
print(get_overlapping_bodies()) # now populated
The same applies when you teleport a body into an Area by setting global_position: the enter signal arrives on the next physics step, not immediately.
Things that silently stop detection
- Toggling monitoring inside a physics callback. Changing
monitoringor a shape'sdisabledflag inside a signal raised by the physics server can error with "Function blocked during in/out signal". Defer it:set_deferred("monitoring", false). - Bodies that never move. A StaticBody3D that starts inside an Area is reported once the Area begins monitoring. If you need to re-check, call
get_overlapping_bodies()after a physics frame instead of waiting for a signal that already fired. - Very fast bodies. A projectile moving further per physics step than the Area is thick can skip over it. Use a RayCast3D or ShapeCast3D for bullets, or raise the physics tick rate.
When you want a one-off query
If you only need to know what is inside a volume at one moment, a direct query avoids the Area's lifecycle entirely:
var params := PhysicsShapeQueryParameters3D.new()
params.shape = SphereShape3D.new()
params.shape.radius = 3.0
params.transform = global_transform
params.collision_mask = 1 << 1 # layer 2
var hits := get_world_3d().direct_space_state.intersect_shape(params)