G2.7 — Physics Bodies & Areas¶
What you'll learn
- Move a
CharacterBody2Dby settingvelocityand callingmove_and_slidein_physics_process. - Give bodies and areas a
CollisionShape2D, without which they detect nothing. - Detect overlaps with
Area2Dand itsbody_entered/area_enteredsignals. - Configure collision layers ("what I am on") and masks ("what I scan for"), and the asymmetry between them.
How it applies
- No collision shape means no collision. A body or area with no
CollisionShape2Dchild has nothing to collide with or detect — the "my walls don't stop the player" and "my hitbox never registers" bugs almost always trace here first. - Layer/mask is the single most common 2D detection bug. Detection is asymmetric: A senses B only if A's mask includes a layer B is on. Get the direction backwards and overlaps silently never fire — no error, just nothing happening. Understanding "I am on" versus "I look for" is the fix.
- Moving by
positiondefeats collision. Teleporting a body by writingpositionskips the physics solver, so it tunnels through walls;velocity+move_and_slidelets the engine resolve collisions and slide along surfaces. - The layer matrix is an interaction test surface. Which pairs can interact (player vs enemy hitbox, pickup vs player, enemy vs world) is a matrix; naming and partitioning it deliberately keeps the interaction space small and testable, the same instinct as an interaction test matrix in QA.
Concepts¶
Body types, briefly¶
Godot has three 2D body types; pick by who drives the motion:
StaticBody2D— does not move (walls, floors, level geometry).RigidBody2D— driven by the physics simulation (forces, gravity, bouncing). You influence it with forces, not by setting position.CharacterBody2D— driven by your code. You compute avelocityand let a helper resolve collisions. This is what player and enemy characters use.
For a code-controlled character, CharacterBody2D is the answer; the others appear as level geometry
and simulated objects respectively.
CharacterBody2D: velocity and move_and_slide¶
You move a CharacterBody2D by setting its velocity (pixels per second, a Vector2) and calling
move_and_slide() each physics frame:
extends CharacterBody2D
@export var speed := 200.0
func _physics_process(_delta: float) -> void:
var dir := Input.get_vector("left", "right", "up", "down") # normalized (G2.5)
velocity = dir * speed
move_and_slide() # engine moves the body and resolves collisions this frame
move_and_slide consumes velocity, moves the body, and slides along anything it hits instead of
stopping dead — and it already accounts for the physics step, so you do not multiply velocity by
delta when using it (the helper handles the timing). Note what not to do: setting position
directly skips collision resolution and lets the body pass through walls.
CollisionShape2D: the shape is mandatory¶
A body or area is invisible to the physics engine until it has a CollisionShape2D child with an
actual shape (a rectangle, circle, or capsule resource) assigned:
Player (CharacterBody2D)
└── CollisionShape2D # assign a shape resource, or it collides with nothing
The most common "collision does nothing" cause is a missing or empty CollisionShape2D. The shape is a
child node so it transforms with the body (G2.2) — which is also why scaling the parent scales the
shape (a G2.2 footgun worth recalling).
Area2D: detecting overlap¶
An Area2D detects overlaps without resolving them — bodies and other areas pass through it,
and it reports the overlap via signals (G2.6):
func _ready() -> void:
$Area2D.body_entered.connect(_on_body_entered)
$Area2D.area_entered.connect(_on_area_entered)
func _on_body_entered(body: Node2D) -> void:
# a physics body overlapped this area
...
body_entered(body) fires when a physics body overlaps; area_entered(area) when another area does.
The payload is what entered. Area2D is the basis for the mechanic behind hitboxes, hurtboxes,
pickups, and detection ranges — the game books build their component architecture on it (ARPG M3);
here you learn the area and its signals themselves.
Collision layers and masks¶
Every body and area has two bit-sets that decide who interacts with whom:
- layer — the layers this node is on ("what I am").
- mask — the layers this node scans for ("what I look for").
The rule is asymmetric: node A detects node B only if A's mask includes a layer B is on. B's
own mask is irrelevant to whether A detects B. So a player-hitbox detects an enemy-hurtbox when the
hitbox's mask includes the enemy-hurtbox's layer. Naming the layers (Project Settings → Layer Names
→ 2D Physics) keeps this legible: world, player, enemy, player_hitbox, enemy_hurtbox, and so
on.
Example
The asymmetry, stated as the rule to memorize:
A node senses another only if its mask includes a layer the other is on.
Set a pickup's layer to pickup and the player's mask to include pickup, and the player detects
the pickup. If you instead set the player's layer to pickup and expect detection, nothing
happens — layer is "what I am," not "what I sense." This one-directional rule is the most common 2D
detection bug, and stating it as mask-includes-the-other's-layer is how you avoid wiring it
backwards.
Walkthrough¶
- Build a
CharacterBody2DPlayerwith aSprite2Dand aCollisionShape2D(assign a rectangle shape). In its script, setvelocityfromInput.get_vector(...)× speed and callmove_and_slidein_physics_process. Add aStaticBody2Dwall with its ownCollisionShape2D. Run and confirm the player slides along the wall instead of passing through. - Remove the player's
CollisionShape2D(or clear its shape) and run again — it now passes through the wall. Restore the shape. That is "no shape, no collision." - Add an
Area2D"pickup" with aCollisionShape2D, and connect itsbody_enteredto a handler that prints andqueue_frees the pickup. Walk the player into it and confirm the overlap fires. - Layers/masks: in Project Settings, name layers
playerandpickup. Put the player body on layerplayer; put the pickup area on layerpickupand set the player's mask (or the area's mask, as the detector) to include the other's layer. Misconfigure it deliberately (detector's mask missing the other's layer) and watch detection stop with no error; then fix it.
Optional sanity check
Try moving the player with position += dir * speed * delta instead of velocity + move_and_slide.
It moves, but walks straight through the StaticBody2D wall, because writing position skips the
physics solver. Switch back to move_and_slide and the wall stops it. That contrast is why
code-driven characters use move_and_slide, not direct position writes.
Self-check quiz¶
Q1 — Your CharacterBody2D passes straight through walls. Which is NOT a plausible cause?
A. The body has no CollisionShape2D (or its shape is empty).
B. You move it with position += instead of velocity + move_and_slide.
C. The wall StaticBody2D has no collision shape.
D. The body's modulate color is wrong.
Reveal answer
D. modulate is a visual tint (G2.3) and has nothing to do with collision. A, B, and C are
all real causes: no shape means nothing to collide with (on either the body or the wall), and
writing position directly bypasses the solver so no collision is resolved. The fix space is
shapes + move_and_slide, not color.
Q2 — A player-hitbox should detect an enemy-hurtbox. The hurtbox is on layer enemy_hurtbox. What must be true?
A. The hurtbox's mask must include player_hitbox.
B. The hitbox's mask must include enemy_hurtbox (the layer the hurtbox is on).
C. Both must be on the same layer.
D. Neither needs a mask; layers alone suffice.
Reveal answer
B. Detection is asymmetric: the detector (the hitbox) senses the other only if the detector's mask includes a layer the other is on. A points the rule at the wrong node's mask. C would make them share identity, not establish detection. D is false — the mask is exactly what defines what a node scans for.
Q3 — When should a node be an Area2D rather than a CharacterBody2D?
A. When it should detect overlaps but not physically block or be blocked (hitboxes, pickups, triggers).
B. Whenever it moves.
C. Never; Area2D is deprecated.
D. Only for UI.
Reveal answer
A. Area2D detects overlap and reports it via signals without resolving collision, which is
exactly what hitboxes, pickups, and triggers want; CharacterBody2D is for a code-driven body
that should collide and slide. B is unrelated (areas and bodies can both move). C is false. D
confuses physics areas with UI.
Integration question¶
Q4 — open
You are building the mechanics a melee hit needs (the ARPG keeps the hitbox/hurtbox architecture;
you supply the parts). The player is a CharacterBody2D that should slide along walls; an attack
should be an Area2D that detects enemies but does not physically shove them; and only the player's
attack should hit enemies, not other players or the world. Describe the nodes and shapes involved,
how the player moves, how the hit is detected, and how layers/masks are set so the attack hits only
enemy hurtboxes — and name the most likely silent failure and how you would catch it.
Reveal expected answer
Nodes/shapes: the player is a CharacterBody2D with a CollisionShape2D (for body collision
against the world) and a child Area2D "hitbox" with its own CollisionShape2D (for hit
detection); enemies have a body plus an Area2D "hurtbox" with a shape. Every body and area needs
a shape or it does nothing. Movement: the player sets velocity from input (G2.5) and calls
move_and_slide in _physics_process, so it slides along the world StaticBody2D walls rather
than tunneling — not by writing position. Detection: the hitbox Area2D reports overlaps
via area_entered(area); the handler reads the entered area as the enemy's hurtbox (using as +
a null guard, L2.1) and applies the hit. Layers/masks: name layers world, player,
enemy_hurtbox, player_hitbox. Put the hurtbox on enemy_hurtbox; set the player hitbox's
mask to include enemy_hurtbox and not player or world, so the attack senses only enemy
hurtboxes — the asymmetric "mask includes the other's layer" rule. Most likely silent failure:
the mask wired backwards (hitbox on the right layer but its mask missing enemy_hurtbox, or the
layer/mask roles swapped), so area_entered never fires and the attack does nothing with no
error. Catch it by printing in the handler (or watching the Output) and, if silent, checking the
layer/mask matrix against the rule — detection requires the detector's mask to include the
target's layer. The mechanics here (body + move_and_slide, area + signals, layer/mask) are
exactly what the game books' component architecture is assembled from.