Stage 01 — Ground zero: the project exists and the farmer walks
You will build: the Godot project itself, configured for crisp 32×32 pixel art, under git, with a player that walks in 8 directions at a fixed speed. You'll learn: project setup · CharacterBody2D movement · typed GDScript · git as a habit Warm-up: none — this is stage one. From stage 02 on, every stage opens with a few questions you answer from memory before touching the editor; retrieval is how things stick.
Why this exists#
Two mechanisms carry this whole stage. First, pixel art dies without integer math: if the
engine scales your 640×360 world to a 1387×781 window, every sprite smears. Godot solves this
with a viewport stretch — the game renders at exactly 640×360 and the window scales that
image — plus nearest-neighbor filtering so scaled pixels stay square.
Second, physics bodies move on the physics clock: _physics_process runs at a fixed 60 Hz
no matter the monitor; _process runs per rendered frame (144+ on your monitor). Movement that
collides belongs on the fixed clock — Idle and physics processing.
Build it#
A — Create the project (yes, this is the tutorial's first step)#
- Godot 4.7 → Project Manager → Create New Project.
- Name
Farming Game; point the path at afarming_gamefolder in your projects directory (snake_case — folder names travel intores://paths, and snake_case avoids case-sensitivity surprises on export). - Renderer: Mobile — desktop is your target and it matches your other prototypes; Compatibility only matters if a web build becomes a goal.
- Version Control Metadata: Git, then Create & Edit.
B — Pixel-art display settings (Project → Project Settings)#
Set each, and know why:
| Setting | Value | Why |
|---|---|---|
display/window/size/viewport_width / height | 640 × 360 | 20×11 tiles of 32px — the world's true resolution |
display/window/size/window_width_override / height | 1280 × 720 | the window is 2× the viewport on launch |
display/window/stretch/mode | viewport | render small, scale the image — pixels stay pixels |
display/window/stretch/scale_mode | integer | only whole-number zoom — no half-pixel smear at odd window sizes |
rendering/textures/canvas_textures/default_texture_filter | Nearest | scaled pixels stay square, not blurry |
debug/gdscript/warnings/untyped_declaration | Warn | the editor now flags every untyped var — your typing tutor |
Predict before you continue: with stretch mode
viewportand a 1280×720 window, how big does one 32×32 tile appear on screen, in monitor pixels? Write your answer down; check it when your first sprite is on screen.
C — Git, as a habit, not a chore#
Open a terminal in the project folder:
git init -b main
git add .
git commit -m "stage 00: empty project, pixel-art display settings"
Godot already generated .gitignore (excluding .godot/) and .gitattributes because of
step A4. Every stage ends with a commit — so you can always diff exactly what a stage changed.
D — Input actions (Project Settings → Input Map)#
Add four actions, each bound to both WASD and arrows: move_left, move_right,
move_up, move_down. Exact spelling matters — the code below reads these names.
(InputMap docs)
E — The player scene#
Scene → New Scene → root type CharacterBody2D, rename it Player. Add two children:
Player (CharacterBody2D)
├── Sprite2D ← icon.svg for now; real art later — placeholder-first was your plan
└── CollisionShape2D ← New RectangleShape2D, size ≈ 24×16, positioned at the feet
Why the feet: in top-down, characters overlap obstacles with their body but collide with
their footprint. Set the Sprite2D scale so it reads ~32px tall. Save as
player/player.tscn — feature folders, not type folders.
F — The movement script (type it, don't paste it)#
Attach a script to Player, save as player/player.gd:
class_name Player
extends CharacterBody2D
@export var speed: float = 90.0
func _physics_process(_delta: float) -> void:
# read intent: one vector from the four move_* actions
var direction: Vector2 = Input.get_vector("move_left", "move_right", "move_up", "move_down")
# convert intent to motion
velocity = direction * speed
move_and_slide()
Typing contract in action — every declaration carries a type, the function signature is
complete (_delta: float, -> void), the unused parameter is underscore-prefixed so the
warning panel stays clean.
Self-explain before running (answer out loud, seriously):
velocityis never declared in this script. Where does it come from, and who consumes it?- Why does
move_and_slide()take no arguments here, when older tutorials pass it a velocity? (If you find such a tutorial, it's Godot 3 — the API changed.) -
Predict before you run: if you changed the line to
velocity = direction * speed * _delta, what exactly would you observe? Run it once after answering, watch, then change it back — reconcile what you saw with your answer.
Now make a test scene: New Scene → Node2D root named Main, instance player.tscn into it
(drag from FileSystem), save as main.tscn, and set it as the main scene (Project Settings →
Application → Run). Press F5.
G — Commit the stage#
git add .
git commit -m "stage 01: pixel-art project, player 8-way movement"
Checkpoint — definition of done#
- Player moves in 8 directions with both WASD and arrow keys
- Diagonal movement is not faster than straight movement (test it — walk a corner)
- Sprite edges stay crisp when you resize the window
- Script panel shows zero warnings
-
git logshows your two commits - You can explain: why is diagonal not faster, when left+up are both held? (The answer
is inside
Input.get_vector— check its entry in the class reference.)
When every box ticks: commit, then spend five minutes explaining your own script line by line, out loud, before opening stage 02. It feels silly. It works.
Stretch (no instructions)#
Holding Shift makes the farmer sprint. You have everything you need.
If you get stuck#
- Player doesn't move at all → is the script attached to Player (scroll icon next to the node)? Do the four action names in the Input Map match the strings in the code exactly?
- Moves but ignores one direction → check that action's key binding; print
directionand watch the Output panel while pressing the dead key. - Sprite is blurry → which of the two scaling settings from section B governs texture sampling? Re-check it.
- Error mentioning
move_and_slidearguments → you've drifted into a Godot 3 tutorial; stage-verify against the CharacterBody2D class reference.
State your hypothesis before asking for help — "I think X because Y" earns better hints than "it doesn't work."