Skip to main content
v2 · zero-dependency · ~3 KB gzipped

The tiny TypeScript framework
for browser and headless games.

Tiny, zero-dependency, fully-typed TypeScript framework for browser & headless games.

npm install gameplatepnpm add gameplateyarn add gameplatebun add gameplate
live · interactive · running gameplate v2

See it running, not screenshotted.

Every pixel below is rendered by a real createGame instance — the same API you'd import in your project. Fixed-step physics, additive-blend particles, keyboard + pointer wired through gameplate's input layer.

Loading interactive demo…

Everything you need. Nothing you don't.

Tiny — ~3 KB gzipped

Smaller than a single sprite sheet. Side-effect free and fully tree-shakeable, so only what you import lands in your bundle.

TypeScript-first, strict

Source is TS with strict: true + every noUnchecked* flag. Inference does the work — no as any, ever.

Renderer-agnostic

Canvas 2D, WebGL, PIXI, Three.js, DOM, SVG, terminal — gameplate doesn't care. Bring whichever renderer you love.

Deterministic loop

Variable timestep by default; opt into a fixed-step accumulator with interpolation when physics needs to be reproducible.

First-class input

Normalized keyboard + pointer with target-relative coordinates. Headless? The Node build no-ops cleanly — same API, zero crashes.

Scene FSM built-in

Compile-time-checked finite state machine for menus, modes, and lifecycles. Send an event that doesn't exist — TypeScript stops you.

Memoized selectors

Reselect-style derived state in <30 LOC. Never recompute the visible-enemies list twice in the same frame.

Browser AND Node

Same code, two runtimes. Headless simulation, server-authoritative play, CI snapshot tests — all just work.

Dual ESM + CJS

ESM-first source, dual ESM/CJS publish. publint + @arethetypeswrong/cli clean. Provenance signed.

A game in 30 lines.

Typed state, deterministic loop, normalized input, scene FSM, memoized selectors — all batteries included, all tree-shakeable, all renderer-agnostic.

  • ✅ Strict TypeScript inference — never type your state twice.
  • ✅ Variable timestep by default, fixed-step on opt-in.
  • ✅ Runs in the browser, Node, Bun, Deno, Web Workers.
  • ✅ Zero runtime dependencies. Forever.
Read the quickstart →
game.ts
import { createGame, defineActions } from 'gameplate';

type State = { x: number; y: number; score: number };

const actions = defineActions<State>()({
moveBy: (s, dx: number, dy: number) => ({ ...s, x: s.x + dx, y: s.y + dy }),
addScore: (s, points: number) => ({ ...s, score: s.score + points }),
});

const game = createGame({
state: { x: 0, y: 0, score: 0 },
actions,
update: (state, dt, actions) => {
if (game.keyboard.isDown('ArrowRight')) actions.moveBy(200 * dt, 0);
if (game.keyboard.isDown('ArrowLeft')) actions.moveBy(-200 * dt, 0);
},
render: (state) => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(state.x, state.y, 20, 20);
},
});

game.start();

You don't need another engine.
You need the glue.

vs. PIXI / Three / Phaser

Those are renderers (or full engines). gameplate is the glue between your game logic and any of them — bring whichever renderer you love.

vs. XState

XState is great for arbitrary statecharts. gameplate's FSM is small (~150 LOC), purpose-built for game scenes, and ships with the rest of the framework.

vs. rolling your own

You will. Eventually. gameplate is what your fifth from-scratch loop wants to become — typed, tested, headless-ready.