Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions BUILD_THE_GAME.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

This document is the end-to-end walkthrough for building a game using this template. It's written for two audiences at once: a human who just clicked **"Use this template"**, and an AI agent that has joined the project mid-flight. Read it front-to-back once; come back for the sections you need.

> **First time making a game?** Read [`docs/learn/`](./docs/learn/README.md) **before** this document. It covers the mental-model shift, AI asset generation, game-design fundamentals, and the solo-dev workflow. This document assumes you already know what a sprite, tilemap, and core loop are. The `learn/` primer will teach you all three in 2.5 hours.

---

## 1. Why AI-first?
Expand Down Expand Up @@ -381,6 +383,7 @@ Split it. `max-lines` is a 400-line soft limit for a reason.

| Document | Covers |
| --------------------------------------------------------------------- | ------------------------------------------ |
| [`docs/learn/`](./docs/learn/README.md) | **Start here if new to game dev** — mental model, core concepts, AI asset generation, game design, solo workflow |
| [`docs/ai/architecture.md`](./docs/ai/architecture.md) | Full layer diagram + golden rule |
| [`docs/ai/contribution-contract.md`](./docs/ai/contribution-contract.md) | Ten non-negotiable rules |
| [`docs/ai/naming-conventions.md`](./docs/ai/naming-conventions.md) | File suffixes, interface names, imports |
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

# Phaser + TypeScript AI-First Starter

A reusable, strictly-typed, architecturally-enforced starter for Phaser games — built so AI agents can contribute at high velocity without eroding structure.
A reusable, strictly-typed, architecturally-enforced starter for Phaser games — built so AI agents can contribute at high velocity without eroding structure, and so **a programmer who has never shipped a game can become a one-person game studio in a single weekend.**

Backend engineer, tech lead, web developer who always wanted to make games but never had the time? Start with [`docs/learn/`](./docs/learn/README.md) — a 2.5-hour primer that bridges the gap between "I can write code" and "I know how to use AI to generate art, design a core loop, and ship a playable game on itch.io."

> **Use this as a GitHub template.** Click **"Use this template"** on the repo page, fork it into a new repo for each game, then read [`BUILD_THE_GAME.md`](./BUILD_THE_GAME.md) to walk from empty fork to shipped feature.

Expand All @@ -41,6 +43,7 @@ Everything else (CI, Scorecard, CodeQL, Dependabot) works out of the box.

| If you are... | Read next |
| ------------------------------------------- | --------------------------------------------------------------- |
| **A programmer who never shipped a game** | [`docs/learn/`](./docs/learn/README.md) — 2.5-hour primer on game dev, AI asset generation, game design, and solo workflow |
| **A human forking this to build a game** | [`BUILD_THE_GAME.md`](./BUILD_THE_GAME.md) — the full walkthrough |
| **An AI agent in a fresh session** | [`CLAUDE.md`](./CLAUDE.md) / [`AGENTS.md`](./AGENTS.md) + [`docs/ai/catalog.md`](./docs/ai/catalog.md) |
| **Just evaluating the architecture** | [`docs/ai/architecture.md`](./docs/ai/architecture.md) + this README |
Expand Down
120 changes: 120 additions & 0 deletions docs/learn/01-game-dev-for-programmers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# 01 — Game Dev for Programmers

You know how to write code. What's different about writing game code?

Short version: your program is no longer a function that takes a request and returns a response. It's a **loop that never stops**, reading input, mutating state, and rendering pixels, 60 times a second. Getting used to that inversion is 80% of the mental shift.

## The game loop

Every game, ever, looks roughly like this:

```ts
while (gameIsRunning) {
const deltaMs = time.elapsedSinceLastFrame();
const intent = input.poll();
state = tick(state, intent, deltaMs);
render(state);
time.waitForNextFrame();
}
```

Phaser hides the `while` loop. You give it scenes; it calls your `update(time, delta)` method about once every 16.67ms (60 FPS). Everything else flows from that.

## What transfers from your day job

| From | To | Why |
|---|---|---|
| Pure functions for business rules | Pure functions in `src/domain/` | Same reason: trivial to test, trivial to reason about |
| Dependency injection (ports/adapters) | Ports for time, random, storage, audio | The engine is a side-effect provider; you keep the core clean |
| Event-driven architecture | Typed event bus (`src/shared/events/`) | Same pattern, different consumers |
| State management (Redux, Zustand, etc.) | `GameState` aggregate + reducer-ish features | The ideas map 1:1 |
| Test-driven development | `src/**/*.test.ts` | Domain code is unit-testable even for games |
| Conventional commits + PR reviews | Same tooling, same discipline | You already do this |
| Observability / logging | Debug overlays, telemetry, analytics | The output shape differs; the instinct is the same |

**Your best existing skill is knowing where to draw abstraction lines.** This template already draws them in the right places — you just need to resist the urge to undo them when a tutorial shows you a Phaser-everything approach.

## What does NOT transfer (and surprises people)

### 1. Frames, not requests

In web dev, your code runs when something asks. In games, your code runs **constantly**. If your `update()` takes 20ms, you dropped a frame and the player sees stutter. Budget matters differently — "premature optimization" is less of a sin at the hot loop.

### 2. Mutation happens — intentionally

Phaser gives you `Sprite` objects. Those objects are mutable: `sprite.x = 42` is the norm, not an anti-pattern. The template quarantines the mutable engine work in `src/runtime/`, keeps `src/domain/` pure. Don't try to make everything immutable — only the parts where purity pays off.

### 3. Time is an input

Your logic can't read the wall clock freely. `Date.now()` is banned from `src/domain/**` because:
- Tests need deterministic replays
- Networked play needs lockstep sync
- Recording/replay features need it

Every tick takes a `deltaMs` parameter. If you need timestamps, inject an `ITimePort`.

### 4. Randomness is an input

Same reason. `Math.random()` is banned from `src/domain/**`. Use `IRandomPort` seeded at game start. That way:
- A test run is reproducible
- Speedrunners can share seeds
- Crash reports can replay the exact sequence

### 5. Coordinates are not CSS

The Phaser coordinate system has `(0,0)` at the top-left, Y increases downward, positions are in pixels (not rem/em/%). Conversions from "world space" (where gameplay happens) to "screen space" (what the camera shows) come up constantly. You'll need to think in two coordinate systems more than you're used to.

### 6. The renderer can lie about your state

You can't debug a visual glitch by reading your state — the sprite might be in the right place visually for the wrong reason (wrong origin, wrong scale, wrong depth, last frame's interpolation). Keep a truth source (`GameState`) and render *from* it; never read game logic *from* a sprite's position.

## The architectural pattern this template enforces

```
┌──────────────────────────────────────────────┐
│ Input (keyboard, gamepad, network, AI bot) │ ← runtime/
└──────────────┬───────────────────────────────┘
│ command
┌──────────────────────────────────────────────┐
│ Domain: applyMoveIntent, resolveOverlap │ ← src/domain/
│ Pure, deterministic, testable │
└──────────────┬───────────────────────────────┘
│ new state + events
┌──────────────────────────────────────────────┐
│ Features: orchestrate domain + emit events │ ← src/features/
└──────────────┬───────────────────────────────┘
┌──────────────────────────────────────────────┐
│ Runtime: render sprites, play audio, HUD │ ← runtime/
└──────────────────────────────────────────────┘
```

Every frame walks that flow. If you ever feel tempted to "just do it in the scene" — stop. The reason the rest of the industry has compiler-enforced layer rules in large games is exactly that pull.

## A mental model that has worked for people coming from backend

Think of each tick as a **single HTTP request** that takes 16ms:
- Request body = the current `GameState` + the player's intent + `deltaMs`
- Handler = your feature orchestration
- Response = the next `GameState` + events for side effects
- Side effects (sprite updates, audio) happen in runtime after the "response" lands

In this mental model, `GameState` is your DB row, features are your controllers, runtime is your view layer, and ports are your DAOs/clients. You already know how to build this shape.

## Things you will Google a lot (and shouldn't be embarrassed about)

- "How to make a sprite move with arrow keys" → cursor keys, `scene.input.keyboard.createCursorKeys()`
- "How to detect collision" → `scene.physics.add.overlap(a, b, handler)` — or the pure-domain version we use here
- "Why does my sprite jitter" → delta time not applied to velocity
- "How to flip a sprite horizontally" → `sprite.setFlipX(true)`
- "How to make a camera follow the player" → `scene.cameras.main.startFollow(sprite)`
- "Why does my tween not play" → you forgot `this.tweens.add(...)` returns a handle; don't `await` it unless it's a promise wrapper

Keep a file of your own Phaser gotchas as you hit them. After a month you'll have your own reference.

## Next

Now that the mental model is in place, [02 — Core Concepts](./02-core-concepts.md) walks through the specific vocabulary of 2D game dev. No mystery jargon past that point.
Loading
Loading