The tiny TypeScript game framework. Zero deps. Any renderer.
State. Loop. Input. Scenes. Selectors. Seeded RNG. Timers. Ship a game today.
👉 Docs · Live demo · API
import { createGame, defineActions } from 'gameplate'; const actions = defineActions<{ x: number }>()({ moveBy: (s, dx: number) => ({ x: s.x + dx }), }); const game = createGame({ state: { x: 0 }, actions, update: (state, dt, actions) => { if (game.keyboard.isDown('ArrowRight')) actions.moveBy(200 * dt); }, render: (state) => draw(state), // 👈 your renderer, any tech }); game.start();
That's the whole API surface. Read it, write it, ship it.
Why you'll like it
- 🪶 ~4 KB gzipped, fully tree-shakeable. Import only the primitives you use; zero runtime deps, forever.
- 🦺 TypeScript-first.
strict: true+ everynoUnchecked*flag. Inference does the work — noas any, ever. - 🎯 Renderer-agnostic. Canvas · WebGL · WebGPU · PIXI · Three.js · DOM · terminal. Pick one. Switch tomorrow.
- ⏱️ Deterministic loop. Variable timestep by default; opt into fixed-step + interpolation when physics need to be reproducible.
- 🎮 Input, normalized. Keyboard + pointer + gamepad (with radial-deadzoned sticks and edge detection). Headless no-ops cleanly — same API on Node.
- 🎬 Typed scene FSM.
menu → start → playingwith compile-time checks. Send a wrong event? TypeScript stops you. - 🧠 Memoized selectors. Reselect-style derived state, ~30 LOC, exact same shape.
- 🎲 Seeded RNG. Reproducible procedural generation —
int/pick/shuffle/fork, JSON-serializable state for save & resume. - ⏲️ Game-time timers.
after/everydriven by your loop'sdt— pause, slow-mo, and fixed-step just work. UnlikesetTimeout. - 🎞️ Record & replay. Capture every dispatched action; replay it deterministically. Bug repro, regression tests, server-authoritative validation — all in JSON.
- 🖥️ Browser & Node. Headless simulation, server-authoritative play, CI snapshot tests — same code, two runtimes.
- 📦 Dual ESM + CJS.
publintclean. Provenance signed. Tree-shakeable.
In 30 seconds
type State = { player: { x: number; y: number }; score: number }; const actions = defineActions<State>()({ move: (s, dx: number, dy: number) => ({ ...s, player: { x: s.player.x + dx, y: s.player.y + dy }, }), score: (s, pts: number) => ({ ...s, score: s.score + pts }), }); const game = createGame({ state: { player: { x: 0, y: 0 }, score: 0 }, actions }); game.actions.move(10, 0); // ✅ typed game.actions.move('lol', 0); // ❌ TS error — caught at compile time
That's state + actions. Add update for input handling, render for drawing, and you have a game.
What's in the box
createGame |
One-call setup — state + loop + input wired together. |
createStore |
The underlying typed store (subscribe, getState, setState). |
defineActions |
Curried generic that gives perfect IntelliSense without retyping S. |
createLoop |
Bare loop — variable or fixed-step with interpolation alpha. |
createKeyboard / createPointer / createGamepad |
Normalized input — keys, pointer, and Standard Gamepad with edge detection. No-op stubs on the server. |
createMachine |
Compile-time-checked finite state machine for scenes/menus. |
createSelector |
Reselect-style memoization, ~30 LOC. |
createRandom |
Seeded deterministic RNG — int/pick/shuffle/fork, serializable state for save & replay. |
createTimers |
Game-time after / every scheduling, driven by your loop's dt (respects pause & slow-mo). |
createRecorder / replay |
Deterministic record + replay of every action. JSON-serialisable. |
Built for every stack
gameplate doesn't ship a renderer — it ships the glue. Drop into:
Switch renderers without changing one line of game logic. Patterns for each →
How it fits together
⌨ keyboard ─┐
🖱 pointer ─┼──▶ actions ──▶ store ──▶ selectors ──▶ render ──▶ 🎨 your renderer
🛰 network ─┘ │ ▲
▼ │
scene FSM ──────────────────┘
loop (dt, alpha) ──▶ update ──▶ actions
A handful of composable functions. Pick the ones you need. Ignore the rest.
Quality bar
- ✅ 204/204 tests, ≥ 90 % line coverage
- ✅ CI matrix: Node 20 / 22 / 24 × Ubuntu / macOS / Windows
- ✅
publint+@arethetypeswrong/cliclean on every PR - ✅ Size limit: < 5 KB gzipped ESM (full barrel), enforced in CI — and tree-shakeable
- ✅ CodeQL security analysis on every PR
- ✅ npm provenance on every release (OIDC trusted publishing)
Contributing
PRs welcome. See CONTRIBUTING.md. Architecture decisions are recorded in DECISIONS.md.
git clone https://github.com/yankouskia/gameplate
cd gameplate
pnpm install
pnpm test:watchLicense
MIT © Alex Yankouski · Sponsor →
If gameplate saved you a weekend, ⭐ star it — it's the easiest way to say thanks.





























