Quick answer: Set contact_monitor = true AND max_contacts_reported to a non-zero value. Both default off — without them, contact queries and signals stay empty.

A RigidBody2D crate should detect what it’s resting on, but get_colliding_bodies() returns an empty array and body_entered never fires.

Two Settings, Both Required

func _ready():
    contact_monitor = true
    max_contacts_reported = 8   # > 0, sized to expected contacts

Then the Signals Work

body_entered.connect(_on_body_entered)

func _on_body_entered(body):
    if body.is_in_group("hazard"):
        take_damage()

With both settings on, body_entered / body_exited and get_colliding_bodies() all populate.

Size the Buffer Right

If a body can touch many things at once (a crate in a pile), max_contacts_reported caps how many you see. Set it to the realistic maximum — too low silently drops contacts.

Verifying

Drop the crate onto the floor. get_colliding_bodies lists the floor. Stack crates — each reports its neighbors up to the buffer size.

“Contact monitoring needs the flag and a non-zero buffer. Either alone reports nothing.”

Only enable contact_monitor on bodies that actually query contacts — it’s off by default because it costs CPU per body.