Stage 01 — Ground zero: a field in three dimensions
You will build: the 3D project, a lit field with a sky, a ground box you can stand on, and
a player who walks, falls, and is followed by a camera.
You'll learn: the entire 3D delta — the XZ plane, Y-up, forward-is−Z · CharacterBody3D
· environment and light as scene content · design decision 1: the camera.
Why this exists#
You know how to build a 2D field. This stage is the one-hour answer to "what's different" — because the differences are few, they are all load-bearing, and missing one of them costs a week of "why is my player falling through the world."
Build it#
A — The project#
New Godot 4.7 project, no template. Rendering stays Forward+ (the default — 3D needs a real renderer; Mobile is the 2D economy choice and it has no shadows). On Windows the default driver is d3d12; on a machine that stumbles, Project Settings → Rendering → Rendering Device → Driver is where you'd try d3d11 or vulkan — a setting, not a code change.
Physics → 3D → physics_engine: Jolt Physics (the reference project's choice; Godot's built-in 3D engine also works for this game — both earn the mention, Jolt earns the default because it's the faster of the two and costs one setting).
The viewport trick transfers unchanged: 640×360, stretch canvas_items, aspect keep,
window override 1280×720. Low-res 3D is a legitimate look (the almanac's own 3D game
ships it) and it keeps the scene readable.
Input Map: move_forward (W), move_back (S), move_left (A), move_right (D), plus
run_action (Shift) and plant_action (E) — add them all now, even the two you don't use
until stages 03–04, because the Input Map is the project's contract and you don't want it
growing by accident later. Set each action's deadzone to 0.2 (a keyboard either fires or
it doesn't; 0.2 is the standard "no drift" value).
B — The world: sky, sun, ground#
New scene Game, root Node3D. Three children make "a place":
DirectionalLight3D— the sun. Rotate it: pitch ~−45°, yaw ~30° (in the Spatial transform, X = −45, Z = 30 or thereabouts). Tick Shadow → Enable Shadow. A 3D scene with one shadow-casting light is the minimum that reads as three-dimensional — without the shadow, your capsule is a sticker.WorldEnvironment— create anEnvironmentresource in it, Background Mode Sky, add aProceduralSkyto the sky slot. Two colours (horizon, ground) and the field has a place instead of a void. Enable Glow for the first free "it looks rendered" moment.Ground— aCSGBox3D, size 15 × 1 × 15, positioned at (0, −0.5, 0) so its top face is y = 0, with Use Collision ticked. A CSG box gives you collision for free during prototyping; a proper farm floor becomes aMeshInstance3D+StaticBody3Dpair when the shape stops being a box. (CSG is the prototype tool: it's editable in-scene and a little expensive at render — fine for one box, wrong for a hundred.)
The top face at y=0 is a convention worth keeping: "ground level" is then the number 0.0
everywhere in the code that follows, and the player's feet are is_on_floor() instead of
"y is approximately 0.01".
C — The player: a capsule that walks#
New scene Player, root CharacterBody3D:
Player (CharacterBody3D)
├── Skin (Node3D)
│ └── Mesh (MeshInstance3D, CapsuleMesh) ← at (0, 1, 0): feet on the floor
│ └── Face (MeshInstance3D, BoxMesh) ← at (0, 0.475, −0.3): the front, −Z
├── WorldCollisionShape (CollisionShape3D, CapsuleShape3D)
The face box is doing design work: it marks which way is forward, and in Godot 3D that's −Z. Every facing calculation in stage 03 hangs off this one convention, so build the marker now and look at it until "the front is the −Z side" is a fact, not a memory.
class_name Player
extends CharacterBody3D
@export var speed := 4.0
func _physics_process(delta: float) -> void:
var raw := Input.get_vector(&"move_left", &"move_right", &"move_forward", &"move_back")
var direction := Vector3(raw.x, 0.0, raw.y)
velocity.x = direction.x * speed
velocity.z = direction.z * speed
if not is_on_floor():
velocity += get_gravity() * delta
move_and_slide()
The four differences from 2D, in the order they'll bite you:
Vector3(raw.x, 0.0, raw.y)— the lift.get_vectorreturns a 2D result (x = strafe, y = forward); in 3D "forward" is Z. Get the axes wrong and W walks you to the left.get_gravity()(4.3+) — asks the world for the pull instead of hardcodingVector3.DOWN * 9.8. Change the project's gravity setting and the player follows; a hardcoded constant doesn't. (Pre-4.3 code you'll find online writesProjectSettings.get_setting("physics/3d/default_gravity")— same value, more ceremony.)velocity.yis never set directly. You steer x and z; gravity owns y. The moment you writevelocity = direction * speed(the 2D habit, one line shorter), you overwrite y with 0 and the player floats — or, with a y of 0 and no gravity applied, hovers.move_and_slide()takes no arguments in 3D (2D's optionalsafe_marginmoved). The capsule slides along the ground box's top face instead of clipping it.
D — The camera, and decision 1#
Add a Camera3D as a child of Player at position (0, 8, 8) — behind and above,
looking down the field at ~45°. Because it's a child, following costs zero code; because the
player hasn't rotated yet (that's stage 03), the child relationship is honest.
Predict before you run: in stage 03 the player's skin will rotate to face its movement. If the camera stays a child of the player body, what happens to the camera when you walk left?
Design decision 1 — make the call now, record it in the architecture doc:
- (A) The plain child camera. Zero dependencies, instant follow — but as the prediction shows, it inherits the player's rotation, so stage 03 needs the camera parented to the body with the skin rotating under it, or the camera moved out of the player entirely.
- (B) The Phantom Camera addon (godot-phantom-camera): one real
Camera3Din the world; aPhantomCamera3Drequests control with a follow target, offset, and damping. The shipped scene uses it: the camera is a child ofWorld, a phantom follows the player withfollow_offset (0, 12, 12)andfollow_damping 0.25— and the damped follow is the "camera has weight" feel that a child camera can't have without code.
One-line trade-off: A is free today; B is the upgrade path the game will want. B is the
recommended path and what the shipped scene has, but A is legitimate and stage 03's skin
rotation works with either (rotate the skin, never the body — the body's rotation is the
camera's problem). If you choose B: install the addon from the asset library, tick its
plugin, add a PhantomCameraHost under your GameCamera, and a PhantomCamera3D under the
host with follow_target pointed at the player.
Either way: instance Player onto the field in Game, set it at (0, 0, 0), set Game as
main scene, and walk.
E — Commit#
git add . && git commit -m "stage 01: 3D project, lit field, walking capsule, camera decision"
Checkpoint — definition of done#
- W walks into the screen (−Z), S walks out, A/D strafe — verify all four against the face box, which is the −Z marker
- Walk to the edge of the ground and off: the player falls, gravity is doing the falling
(you never set
velocity.y), and the field's edge is a visible cliff, not a void - The shadow moves with the player; with the light's shadows disabled, say out loud what "three-dimensional" lost
- Decision 1 recorded with your reason; if you chose B, the camera damps — walk and stop, and it settles into place instead of teleporting
- Zero warnings; committed
Stretch (no instructions)#
A second ground box 20 units away with a gap between them. Walk across the gap: you should fall, and the fall should be the same fall as walking off the first edge. If it isn't, find where the two grounds disagree (hint: one of them isn't at y = 0 on top).
If you get stuck#
- Player walks in place, or walks in a direction that has nothing to do with the keys → the
Vector3(raw.x, 0, raw.y)lift is scrambled. Printrawanddirectionfor one frame of pure W: raw is(0, 1), direction must be(0, 0, −1). - Player floats / hovers / clips through the ground and reappears →
velocity.yis being overwritten. Find the line that sets the wholevelocityat once. - The scene is a flat grey void → the
WorldEnvironment's Environment resource is empty (background mode Clear Color). Set it to Sky. - Camera spins when the player turns (post-stage-03) → the camera is a child of the body
and the body's rotation is changing. Stage 03 rotates the
Skin, and the architecture doc's decision 1 says what the camera's real parent is.