# Pixeldex · Godot FSM Player Controller Starter

Drop-in finite-state-machine player controller for Godot 4.2+. Splits player behavior into Idle / Run / Jump / Fall, one file per state. Cleaner than a 200-line `_physics_process`. Easy to bolt on more states.

License: **CC0**.
Source: https://pixeldex.dev/prompts/godot-fsm-player-controller

---

## Two ways to use this

### Option A, import as a fresh project (~30 seconds)

1. Open Godot 4.2+.
2. Project Manager → **Import** → pick this `godot-fsm-starter` folder.
3. Open the imported project. Press **F5** (Play). You'll be asked to pick a main scene, choose `Player.tscn`.
4. The Player drops in. WASD / arrow keys to move, **Space** to jump.
5. The debug label above the player shows the current state name ("IDLE" / "RUN" / "JUMP" / "FALL").

You'll fall through the world because there's no ground in this starter scene, that's fine; it confirms gravity + state transitions work. Add a `StaticBody2D` with a `CollisionShape2D` underneath the Player to test landing.

### Option B, drop into your existing Godot project (~2 minutes)

1. Copy the 5 `.gd` files into your project (e.g. `res://player/`).
2. In your scene, build this node hierarchy:
 ```
 Player (CharacterBody2D) → attach Player.gd
 ├── Sprite2D → your character art
 ├── CollisionShape2D → with a shape
 ├── State (Node) → empty Node
 │ ├── State_Idle (Node) → attach State_Idle.gd
 │ ├── State_Run (Node) → attach State_Run.gd
 │ ├── State_Jump (Node) → attach State_Jump.gd
 │ └── State_Fall (Node) → attach State_Fall.gd
 └── DebugLabel (Label) → optional
 ```
3. On `Player`, in the inspector, drag the `State` node into the `state_root` field. Drag `DebugLabel` into `debug_label` (optional).
4. Make sure your project has Input Map actions called `jump`, `left`, `right` (Project → Project Settings → Input Map). The included `project.godot` already sets these up for `Space` / `A` / `D` / arrow keys.

---

## Adding a new state

Want a Dash state? Three steps:

1. Create `State_Dash.gd` extending `State`. Implement `enter()` (apply a velocity burst), `physics_update()` (count down a timer, transition to `fall` or `idle` when done).
2. Add a `State_Dash` child node under the State node in your scene.
3. From any other state's `physics_update`, call `player.change_state("dash")` on the trigger condition (e.g. `Input.is_action_just_pressed("dash")`).

That's the whole pattern. No registration, no wiring, no factories. The Player auto-discovers states by node name on `_ready`.

---

## Tuning

`Player.gd` has three constants at the top:

```gdscript
const SPEED = 200.0
const JUMP_VELOCITY = -400.0
const GRAVITY = 980.0
```

Edit those. Press Play. Repeat until movement feels right. Once you're happy, you can promote them to `@export` so they're tunable in the inspector.

---

## Common gotchas

**Player snaps back to Idle every frame.** Make sure your state's transition logic `return`s right after calling `change_state(...)`, otherwise the rest of the function runs against the old state.

**Jump triggers but no rise.** Gravity is applied in `Fall` and `Jump` only, not `Idle` or `Run`. Check your override didn't accidentally add gravity to the grounded states.

**`Input.is_action_just_pressed("jump")` errors.** Your project's Input Map doesn't have a `jump` action. Either import this starter's `project.godot` or add the actions yourself.

---

## Where to go next

- Add **coyote time** (0.1s grace after leaving ground where Jump is still allowed), a single timer field on Player.gd, decremented in `_process`.
- Add **wall slide / wall jump**, new states `WallSlide` and `WallJump`, plus `is_on_wall()` checks in `Fall`.
- Promote to a **hierarchical FSM** once you have 6+ states.

If you ship something with this, a link to https://pixeldex.dev in your README is appreciated. Not required.
