doc 3 of 9

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)#

  1. Godot 4.7 → Project Manager → Create New Project.
  2. Name Farming Game; point the path at a farming_game folder in your projects directory (snake_case — folder names travel into res:// paths, and snake_case avoids case-sensitivity surprises on export).
  3. Renderer: Mobile — desktop is your target and it matches your other prototypes; Compatibility only matters if a web build becomes a goal.
  4. Version Control Metadata: Git, then Create & Edit.

B — Pixel-art display settings (Project → Project Settings)#

Set each, and know why:

SettingValueWhy
display/window/size/viewport_width / height640 × 36020×11 tiles of 32px — the world's true resolution
display/window/size/window_width_override / height1280 × 720the window is 2× the viewport on launch
display/window/stretch/modeviewportrender small, scale the image — pixels stay pixels
display/window/stretch/scale_modeintegeronly whole-number zoom — no half-pixel smear at odd window sizes
rendering/textures/canvas_textures/default_texture_filterNearestscaled pixels stay square, not blurry
debug/gdscript/warnings/untyped_declarationWarnthe editor now flags every untyped var — your typing tutor

Predict before you continue: with stretch mode viewport and 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):

  1. velocity is never declared in this script. Where does it come from, and who consumes it?
  2. 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.)
  3. 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 log shows 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 direction and 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_slide arguments → 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."