Physics & Collision

Godot Fundamentals

Chapter 6 · Physics & Collision

Every movement script so far has moved straight through walls, floors, and anything else in its path — both Chapter 4 and Chapter 5 flagged this openly as unfinished business. This chapter replaces raw position changes with Godot's own real physics nodes, which know about the rest of the game world and actually respond to it.

Three Physics Node Types

Godot splits "something that participates in physics" into three different node types, each suited to a different job.

NodeWho's in controlTypical use
CharacterBody2D Your script, fully — you set velocity and call a move function every frame A player character, an enemy with hand-scripted movement
RigidBody2D Godot's own physics engine — gravity, forces, and collisions are simulated automatically A ball, a crate, debris — anything that should behave like a real physical object
Area2D Neither — it detects overlap but applies no physical collision response at all A pickup, a damage zone, a trigger that opens a door

CollisionShape2D — Required for All Three

None of the three node types above detect anything on their own — each needs a CollisionShape2D child, with an actual Shape resource assigned to its own shape property (a RectangleShape2D, a CircleShape2D, etc.).

An empty CollisionShape2D detects nothing — adding the node alone isn't enough. In the Inspector, its Shape property must be set to a real shape resource, or the whole node is invisible to physics regardless of how it's positioned.

CharacterBody2D — Scripted Movement That Respects the World

A CharacterBody2D has a built-in velocity property (a Vector2), and a built-in move_and_slide() method that actually moves the node by that velocity, sliding smoothly along any wall or floor it collides with instead of passing through it.

extends CharacterBody2D const SPEED = 200.0 func _physics_process(delta: float) -> void: var direction := Input.get_vector("move_left", "move_right", "move_up", "move_down") velocity = direction * SPEED move_and_slide()
Godot 3 vs. Godot 4 — move_and_slide() takes no arguments now — in Godot 3, it took a velocity parameter and returned the resulting velocity, which you had to store back yourself. Godot 4 simplified this: set the node's own built-in velocity property first, then call move_and_slide() with no arguments at all — it reads and updates velocity automatically. A lot of tutorials still online use the old Godot 3 call shape; if code from one of those throws an "too many arguments" error, this is almost always why.

For a platformer, gravity is applied by hand each frame, and is_on_floor() reports whether the body is currently resting on the ground:

extends CharacterBody2D const GRAVITY = 900.0 const JUMP_VELOCITY = -400.0 func _physics_process(delta: float) -> void: if not is_on_floor(): velocity.y += GRAVITY * delta if Input.is_action_just_pressed("jump") and is_on_floor(): velocity.y = JUMP_VELOCITY move_and_slide()

Area2D — Detecting Overlap Without Physical Collision

Unlike CharacterBody2D or RigidBody2D, an Area2D doesn't stop anything from passing through it — it simply notices when something does, and emits a signal.

# --- coin.gd, attached to an Area2D --- extends Area2D func _ready() -> void: body_entered.connect(_on_body_entered) func _on_body_entered(body: Node2D) -> void: if body.is_in_group("player"): print("Coin collected!") queue_free()

The connection pattern here is exactly Chapter 3's own custom-signal approach — body_entered is simply a signal Godot's built-in Area2D already declares for you, connected and used the same way as the custom coin_collected signal from that chapter.

Groups, brieflyis_in_group("player") checks whether a node was tagged into a named group (set in the editor's Node panel, under "Groups"). It's a lightweight way to ask "is this specifically the player?" without the Area2D needing a direct reference to the player scene at all — another small instance of the same decoupling idea from earlier chapters.
If body_entered never fires — the most common cause is a collision layer/mask mismatch: the Area2D's own collision_mask has to include whatever layer the other body is actually on. Both nodes also need a real Shape assigned (see above), and the Area2D's own monitoring property has to be left enabled.

RigidBody2D — Letting the Engine Take Over

A RigidBody2D is deliberately not driven by setting position or velocity directly each frame — Godot's own physics engine already applies gravity and resolves collisions on its own. Instead, you nudge it with forces or impulses.

extends RigidBody2D func launch() -> void: # A one-time push, like a kick or an explosion. apply_central_impulse(Vector2(0, -400))

This course focuses mainly on CharacterBody2D, since a hand-controlled player is this course's own central goal — RigidBody2D is worth knowing exists for anything that should behave like a genuinely physical object rather than a directly-controlled character.

Coming from Python The CharacterBody2D-vs-RigidBody2D split maps reasonably well onto a distinction you may already know from game-adjacent Python work: writing your own explicit update loop (you decide exactly what happens to an object's position each tick — CharacterBody2D) versus handing an object to a physics simulation library and letting it compute the result for you (RigidBody2D). Area2D has no close Python parallel by itself — it's closest to a pure event/collision-detector with no physical behavior attached at all.

Coding Challenges

Challenge 1
Build a CharacterBody2D with a CollisionShape2D (a real shape assigned), plus a couple of static wall nodes with their own collision shapes nearby. Attach the four-directional move_and_slide() controller from this chapter and confirm the character stops at the walls instead of passing through.
→ Solution
Challenge 2
Add gravity and jumping to a CharacterBody2D on a static floor, using is_on_floor() to only allow jumping while grounded. Confirm the character can't "double jump" by holding the jump key in mid-air.
→ Solution
Challenge 3
Build an Area2D "coin" with a CollisionShape2D. Add a CharacterBody2D player tagged into a "player" group. Connect the coin's body_entered signal so that walking into it prints "Coin collected!" and removes the coin from the scene.
→ Solution

Quick Reference — Physics & Collision

  • CharacterBody2D — you control velocity and call move_and_slide(); respects walls/floors
  • RigidBody2D — the engine simulates it; nudge with apply_central_impulse()/forces
  • Area2D — detects overlap only, no physical collision response; used for triggers/pickups
  • Every one of the three needs a CollisionShape2D child with a real Shape assigned
  • Godot 4: move_and_slide() takes no arguments — set velocity first, it updates automatically
  • is_on_floor() — true only while a CharacterBody2D is resting on the ground
  • Area2D's body_entered(body) signal — connect it exactly like a custom signal (Chapter 3)