clik-engine

PhaserJS game engine for building games with Claude. 111 modules, 212 tests.

Play the examples: 2048 · Space Shooter · Card Match · Platformer · Twin-Stick · Boss Fight · PCG Dungeon

Install

New game (CLI)

npx create-clik-game my-game
npx create-clik-game my-game --template=platformer
npx create-clik-game my-game --template=puzzle
cd my-game && npm install && npm run dev

Add to existing project

npm install clik-engine phaser
import { createGame, BaseScene, ScalePreset } from 'clik-engine';

From source (engine development)

git clone git@github.com:pvydro/clik-engine.git
cd clik-engine
npm install
npm run dev          # dev harness on :5173
npm run test         # 212 tests
npm run build        # library build

Game Config

One object. That's the whole game setup.

import { createGame, ScalePreset } from 'clik-engine';

createGame({
  name: 'my-game',
  scale: ScalePreset.AUTO,
  physics: 'arcade',
  debug: import.meta.env.DEV,
  devStartScene: 'game',       // skip menus in dev
  scenes: [
    { key: 'game', class: GameScene, default: true },
  ],
  input: {
    actions: {
      left:  { keys: ['LEFT', 'A'], touch: 'swipe_left' },
      right: { keys: ['RIGHT', 'D'], touch: 'swipe_right' },
      jump:  { keys: ['SPACE'], touch: 'tap', gamepad: '0' },
    },
  },
  save: { slots: 3, version: 1 },
});

Scenes

Extend BaseScene. Call super.create() and super.update(). Everything else is wired for you.

import { BaseScene } from 'clik-engine';

export class GameScene extends BaseScene {
  create() {
    super.create();
    // this.actions  — input (keyboard/touch/gamepad)
    // this.director — scene transitions
    // this.audio    — music/sfx
    // this.save     — localStorage persistence

    this.inspectState('game', () => ({ score: this.score }));
  }

  update(time: number, delta: number) {
    super.update(time, delta);
    if (this.actions.justPressed('jump')) { /* ... */ }
    const { x, y } = this.actions.axis('left', 'right', 'up', 'down');
  }
}

Transitions

// 7 built-in types
this.director.go('game', 'menu', Transitions.fade(500));
this.director.go('menu', 'game', Transitions.slideLeft(400));
this.director.go('a', 'b', Transitions.zoom(600));

// Full-screen overlays
await ScreenTransition.fadeThrough(this, () => this.scene.start('next'));
await ScreenTransition.irisWipe(this, callback);
await ScreenTransition.pixelate(this, callback);

// Push/pop stack (menus, inventory)
const stack = new SceneStack(this.game);
stack.push('pause-menu');
stack.pop();

All Systems

Core

  • createGame() — boot
  • BaseScene — scene base class
  • SceneDirector — transitions
  • SceneStack — push/pop
  • SceneUtils — hitStop, slowMotion
  • ScreenTransition — fade, iris, pixelate
  • StateMachine — FSM
  • EventBus — global events

Rendering

  • CameraManager — follow, zoom, shake
  • MultiCamera — split screen, minimap
  • ParticleManager — emitters + presets
  • ShaderManager — post-FX
  • EffectPresets — CRT, dream, frozen
  • Letterbox — cinematic bars

Animation

  • AnimationHelper — declarative + auto-detect
  • SpriteAnimator — play/chain/face
  • AnimationStateController — FSM mapping
  • AnimationEventSystem — frame callbacks

Data

  • SaveManager — versioned slots
  • SaveMigrator — migration chain
  • AssetManifest — tiered loading
  • ManifestValidator — boot validation
  • I18nManager — localization
  • AnalyticsManager — event tracking

Platform

  • PlatformManager — OS, lifecycle
  • CapacitorHelper — mobile native
  • A11yManager — accessibility
  • ResponsiveManager — breakpoints

Utilities

  • Vector2, Color, SeededRandom
  • Grid2D, PriorityQueue, SpatialHash
  • findPath — A* pathfinding
  • ObjectPool, GameTimer, Cooldown
  • formatNumber, formatTime, pluralize

Input

Action-based. Never read raw keys. Supports keyboard, touch gestures, and gamepad through one API.

// Defined in config, used in scenes:
this.actions.isDown('move_left')
this.actions.justPressed('jump')
this.actions.justReleased('shoot')
this.actions.axis('left', 'right', 'up', 'down') // { x, y }

// Gestures
const gestures = new GestureDetector(this);
gestures.on('swipe_left', (e) => { /* ... */ });
gestures.on('pinch', (e) => cam.zoomTo(e.scale));
gestures.on('double_tap', (e) => { /* ... */ });

// Combos (fighting games, cheat codes)
const combos = new ComboDetector();
combos.addCombo({ name: 'hadouken', sequence: ['down', 'right', 'punch'] });
combos.onCombo('hadouken', () => fireball());

// Rebind at runtime
this.actions.getActionMap().rebind('jump', { keys: ['W', 'UP'] });

UI Components

22 components. All Phaser-native (canvas-rendered, visible in screenshots). No DOM.

new Button(this, { x: 400, y: 300, text: 'Play', onClick: () => {} });
new Slider(this, { x: 100, y: 50, width: 200, onChange: v => {} });
new Toggle(this, { x: 100, y: 100, label: 'Music', value: true });
new TextInput(this, { x: 200, y: 150, placeholder: 'Enter name...' });
new NumberInput(this, { x: 200, y: 200, min: 1, max: 10, value: 5 });
Toast.show(this, { message: 'Level Complete!' });
Tooltip.attach(this, button, { text: 'Start the game' });
Notification.show(this, { title: 'Achievement!', message: 'First kill' });

const confirmed = await ConfirmDialog.show({
  scene: this, title: 'Quit?', message: 'Progress will be lost.'
});

// Layouts
new GridLayout(this, { columns: 3, cellWidth: 100, cellHeight: 100, ... });
new ScrollContainer(this, { width: 300, height: 400, ... });
new TabBar(this, { tabs: [{ key: 'inv', label: 'Inventory' }, ...] });

// Themes
setTheme(NeonTheme());  // DarkTheme, LightTheme, RetroTheme

// Animation
await UIAnimator.animate(this, panel, 'slideInLeft');
await UIAnimator.stagger(this, listItems, 'fadeIn', 50);

Entity / Component

import { Entity, Health, Movement, Follower, EntityFactory } from 'clik-engine';

// Build entities
const player = new Entity(this, 100, 200);
player.entityType = 'player';
player.addComponent('health', new Health(100).onDeath(() => gameOver()));
player.addComponent('movement', new Movement(200));

// 12 built-in components:
// Health, Movement, Timer, Collectible, Spawner, DragDrop,
// Follower, Lifetime, Oscillator, FlashOnHit, Patrol, Interactable

// Prefab factory
const factory = new EntityFactory();
factory.register('coin', (scene, x, y) => {
  const e = new Entity(scene, x, y);
  e.addComponent('collectible', new Collectible('coin', 10));
  return e;
});
factory.create('coin', this, 300, 400);

// Registry
const registry = new EntityRegistry();
registry.register(player);
registry.getByType('enemy');
registry.getByTag('boss');
registry.updateAll(delta);

Physics

// Arcade
PhysicsHelper.enableBody(this, sprite);
PhysicsHelper.setVelocity(sprite, 200, 0);
PhysicsHelper.setBounce(sprite, 0.5);
PhysicsHelper.setCollideWorldBounds(sprite);
PhysicsHelper.addCollider(this, player, enemies, onHit);
PhysicsHelper.isOnFloor(player);
PhysicsHelper.oneWayPlatformCheck(player, platform);

// Raycasting
const hit = Raycast.cast(this, x, y, dirX, dirY, 500, obstacles);
const canSee = Raycast.lineOfSight(this, x1, y1, x2, y2, walls);
const nearby = Raycast.queryCircle(enemies, x, y, 200);

// Moving platforms
const plat = new MovingPlatform(this, 100, 300, 120, 16, [
  { x: 100, y: 300 }, { x: 400, y: 300, pauseMs: 1000 },
], 80);

// Object pooling
const pool = new PhysicsPool(this, (s) => s.add.circle(0,0,3,0xffff00), 50);
pool.spawn(player.x, player.y);
pool.cullOffscreen();

// Collision groups
const groups = new CollisionGroups();
groups.create('player'); groups.create('enemy'); groups.create('bullet');
groups.setCollides('bullet', ['enemy']);

Debug

Set debug: true in config. Everything shows on canvas. Game instance at window.__CLIK_GAME.

Log channels

[CLIK:SCENE]Scene lifecycle
[CLIK:STATE]Game state changes
[CLIK:INPUT]Actions, gestures, combos
[CLIK:ERROR]Errors + fix suggestions
[CLIK:ASSET]Loading progress
[CLIK:AUDIO]Music/SFX
[CLIK:SAVE]Save/load
[CLIK:ENGINE]Engine internals
// Per-channel filtering
ConsoleReporter.disableChannel(ClikLogChannel.INPUT);

// Profiler
profiler.begin('physics');
// ...
profiler.end('physics');
profiler.getTimingSummary();

// Leak detection
leakDetector.created('bullet');
leakDetector.destroyed('bullet');
leakDetector.logSummary();

// Scene inspector (DOM overlay)
new SceneInspector(game).toggle();

// Visual regression
VisualTest.saveBaseline('menu-screen');
const { match } = await VisualTest.compareToBaseline('menu-screen');

Multiplayer

// Client
const net = new NetworkManager({ url: 'ws://localhost:8080' });
net.connect();

const lobby = new Lobby(net);
lobby.quickMatch('my-game');

const room = new Room(net);
room.onPlayerJoin(p => console.log(p.name));
room.sendAction('move', { x, y });

// State sync with interpolation
const sync = new StateSync(net);
sync.registerLocal({ id: 'player1', x, y });
sync.update(delta);
const state = sync.getInterpolatedState('player2');
# Server
cd packages/clik-server && npm install && npm start

Play the Examples

Built with clik-engine. Playable in your browser.

Also included

Full API Reference (TypeDoc) · Source on GitHub · MIT License