EngineGodot 4.2 or newer
LanguageGDScript
Time to run~5 minutes
LevelIntermediate — comfortable with node hierarchies and inheritance
Output5 files (Player + 4 states), ~150 lines total
DependenciesNone. No plugins.

The prompt

Open a fresh chat at claude.ai/new and paste this in. Don't add anything before or after.

$ Build me a finite-state-machine player controller for Godot 4.2+ in GDScript.

// shape
- Player is a CharacterBody2D with child node "State" (Node) holding state scripts.
- One file per state: State_Idle.gd, State_Run.gd, State_Jump.gd, State_Fall.gd.
- A small base class State.gd that defines:
  - enter(player), exit(player), update(player, delta), physics_update(player, delta)
- Player.gd holds the current_state, calls its update + physics_update each frame, and exposes a change_state(name) method.

// behavior
- Idle: zero velocity. Transitions to Run on horizontal input, Jump on jump pressed, Fall if not on floor.
- Run: applies horizontal velocity, flips sprite. Same transitions as Idle.
- Jump: applies upward velocity once on enter. Transitions to Fall when velocity.y > 0.
- Fall: gravity only. Transitions to Idle on landing (is_on_floor() and no input), Run on landing with input.

// debug
- A simple Label child that shows the current state name. Update it in change_state.

// physics
- Use move_and_slide(). Read input in physics_update.
- Constants at the top of Player.gd: SPEED = 200, JUMP_VELOCITY = -400, GRAVITY = 980.

// return format
- 5 files, in separate code blocks, with file paths as headers (e.g. # res://player/Player.gd).
- No prose before or after the code blocks.
Open in Claude ▸ Run · 5 min

What this gets you

A platformer-style player that's structured the way you'd actually want to maintain it. Each state is its own file, so adding a Dash state means writing one new file and adding one transition line, not unscrambling a 200-line _physics_process.

This is the structure used by most mid-to-large indie Godot games. If you're past the "single script throws everything into _physics_process" stage, this is the next move.

What good output looks like

If Claude got it right, the output should pass these checks before you paste it:

How to wire it up

  1. Save the 5 files at the paths Claude returned (typically res://player/).
  2. Create a scene with this hierarchy: Player (CharacterBody2D)Sprite2D, CollisionShape2D, State (Node) → 4 child State nodes (one per state script), Label.
  3. Attach Player.gd to the root, and one State_*.gd to each of the 4 child State nodes.
  4. In the editor, drag the State node onto Player.gd's exported state_root field.
  5. Add an Input Map action called jump bound to space.
  6. Run the scene. The Label should read "Idle". Press D to enter Run, space to enter Jump.

Common gotchas

The player snaps back to Idle every frame

This happens when transition logic in Idle.update() doesn't actually break after calling change_state. In GDScript, change_state won't halt the rest of update() unless you return right after it. Add an early return.

Jump triggers, but the player doesn't actually rise

Almost always because velocity.y is being overwritten by gravity in Jump's physics_update before move_and_slide is called. Make sure gravity is only applied in Fall, and Jump should transition to Fall as soon as velocity.y crosses zero.

The state machine works but feels stiff

Add a coyote-time grace window in Fall (e.g. 0.1s where Jump is still allowed even though the player technically left the floor). This is the single biggest "feel" upgrade for a platformer and worth a follow-up prompt.

Where to go next

  1. Dash state. One new file: enter() applies a velocity burst, physics_update counts down a timer, transitions to Fall when done.
  2. Wall slide / wall jump. Two new states (WallSlide, WallJump), plus is_on_wall() checks in Fall.
  3. Hurt / Dead. Add a global event bus (autoload Events) so any damage source can request a state change without coupling to player.
  4. Hierarchical FSM. Once you have 8+ states, group related ones (Grounded → Idle/Run, Airborne → Jump/Fall) so transitions can be shared.

Why this prompt is shaped this way

Three small choices that make this prompt land more reliably:

  1. Specifying the file structure up front. Without "5 files in separate code blocks with file paths as headers", Claude tends to dump everything into one file or wrap it in a markdown narrative. Telling it the output format saves you a follow-up.
  2. Naming the constants. SPEED = 200, GRAVITY = 980 keeps Claude from picking weird defaults that feel either floaty or rigid.
  3. Calling out the bad pattern. Saying "no match on state strings" preempts the most common shortcut Claude takes when asked for a state machine.

Use it however you want

The prompt is CC0 — free to remix, no attribution. The output Claude returns is yours. If it helped, a link to pixeldex.app in your jam build's credits keeps the lights on.