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
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
npm install clik-engine phaser
import { createGame, BaseScene, ScalePreset } from 'clik-engine';
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
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 },
});
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');
}
}
// 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();
createGame() — bootBaseScene — scene base classSceneDirector — transitionsSceneStack — push/popSceneUtils — hitStop, slowMotionScreenTransition — fade, iris, pixelateStateMachine — FSMEventBus — global eventsCameraManager — follow, zoom, shakeMultiCamera — split screen, minimapParticleManager — emitters + presetsShaderManager — post-FXEffectPresets — CRT, dream, frozenLetterbox — cinematic barsAnimationHelper — declarative + auto-detectSpriteAnimator — play/chain/faceAnimationStateController — FSM mappingAnimationEventSystem — frame callbacksSaveManager — versioned slotsSaveMigrator — migration chainAssetManifest — tiered loadingManifestValidator — boot validationI18nManager — localizationAnalyticsManager — event trackingPlatformManager — OS, lifecycleCapacitorHelper — mobile nativeA11yManager — accessibilityResponsiveManager — breakpointsVector2, Color, SeededRandomGrid2D, PriorityQueue, SpatialHashfindPath — A* pathfindingObjectPool, GameTimer, CooldownformatNumber, formatTime, pluralizeAction-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'] });
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);
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);
// 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']);
Set debug: true in config. Everything shows on canvas. Game instance at window.__CLIK_GAME.
[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');
// 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
Built with clik-engine. Playable in your browser.
TilemapManager — Tiled JSON, collision, spawn points, parallaxDialogueManager — branching trees, typewriter, choicesAudioManager — crossfade, per-channel mute, loop countTweenHelper — promise-based tweens, presets (popIn, shake, pulse, float)I18nManager — locales, interpolation, browser detectionA11yManager — color blind modes, font scale, reduced motionCapacitor — template config, haptics, status bar, splash screen/clik-scaffold, /clik-playtest, /clik-build, /clik-debugFull API Reference (TypeDoc) · Source on GitHub · MIT License