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

  1. Monitoring is on. With Monitoring off, the Area detects nothing. If something else turns it off, it stays off.
  2. 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.
  3. 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.
  4. The right signal. body_entered fires for PhysicsBody3D nodes (CharacterBody3D, RigidBody3D, StaticBody3D) and GridMaps. Another Area3D triggers area_entered instead, and only if that area has Monitorable on.
  5. 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

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)

Related fixes