@martini-kit/phaser
@martini-kit/phaser Phaser 3 adapter for martini-kit. Bridges declarative game logic with Phaser’s imperative API, providing automatic sprite synchronization, input management, and multiplayer-aware helpers.
Installation
pnpm add @martini-kit/phaser phaser Overview
@martini-kit/phaser provides:
PhaserAdapter- Core bridge between GameRuntime and Phaser scenesSpriteManager- Automatic sprite creation/destruction across host and clientsInputManager- Declarative input binding systemPhysicsManager- Automated physics behaviorsCollisionManager- Declarative collision rulesPlayerUIManager- HUD management synced to player stateInputProfiles- Standard control schemes (WASD, Arrows, Mobile, Xbox)initializeGame()- High-level entry point
@martini-kit/phaser is optional. You can use @martini-kit/core with any engine (Unity, Godot, Three.js). This package just makes Phaser integration seamless.
Quick Start
import { initializeGame } from '@martini-kit/phaser';
import { game } from './game';
import { createScene } from './scene';
initializeGame({
game,
scene: createScene,
phaserConfig: {
width: 800,
height: 600,
physics: { default: 'arcade' }
}
}); Core Exports
PhaserAdapter
The main bridge between martini-kit’s GameRuntime and Phaser scenes.
import { PhaserAdapter } from '@martini-kit/phaser';
class GameScene extends Phaser.Scene {
adapter!: PhaserAdapter;
create() {
this.adapter = new PhaserAdapter(runtime, this);
}
} Constructor
new PhaserAdapter(runtime: GameRuntime, scene: Phaser.Scene) Methods
| Method | Description |
|---|---|
isHost(): boolean | Check if this peer is the host |
getMyPlayerId(): string | Get current player’s ID |
getState(): TState | Get current game state |
trackSprite(sprite, key): void | (Host only) Sync sprite to network |
registerRemoteSprite(key, sprite): void | (Client only) Register sprite for updates |
updateInterpolation(): void | (Client only) Smooth sprite movement |
onChange(callback): () => void | Subscribe to state changes |
createSpriteManager(config): SpriteManager | Create sprite manager |
createInputManager(): InputManager | Create input manager |
createPhysicsManager(config): PhysicsManager | Create physics manager |
createCollisionManager(config): CollisionManager | Create collision manager |
createPlayerUIManager(config): PlayerUIManager | Create UI manager |
Example
create() {
this.adapter = new PhaserAdapter(runtime, this);
if (this.adapter.isHost()) {
// Host creates physics sprites
const sprite = this.physics.add.sprite(100, 100, 'player');
this.adapter.trackSprite(sprite, `player-${this.adapter.getMyPlayerId()}`);
} else {
// Clients create visual sprites from state
this.adapter.onChange((state) => {
if (!state._sprites) return;
for (const [key, data] of Object.entries(state._sprites)) {
if (!this.remoteSprites.has(key)) {
const sprite = this.add.sprite(data.x, data.y, 'player');
this.adapter.registerRemoteSprite(key, sprite);
this.remoteSprites.set(key, sprite);
}
}
});
}
}
update() {
if (!this.adapter.isHost()) {
this.adapter.updateInterpolation();
}
} SpriteManager
Handles sprite creation/destruction automatically on host and clients.
const spriteManager = adapter.createSpriteManager({
stateKey: 'enemies',
// Host: Create physics sprite
onCreatePhysics: (scene, id, data) => {
const sprite = scene.physics.add.sprite(data.x, data.y, 'enemy');
sprite.setCollideWorldBounds(true);
return sprite;
},
// Client: Create visual sprite
onCreate: (scene, id, data) => {
return scene.add.sprite(data.x, data.y, 'enemy');
},
// Update sprites every frame
onUpdate: (sprite, data) => {
sprite.setPosition(data.x, data.y);
},
// Cleanup on destruction
onDestroy: (sprite) => {
sprite.destroy();
}
}); Configuration
interface SpriteManagerConfig {
stateKey: string; // Which state property holds sprites
onCreate?: (scene, id, data) => Sprite; // Client sprite creation
onCreatePhysics?: (scene, id, data) => Sprite; // Host physics sprite
onUpdate?: (sprite, data) => void; // Per-frame updates
onDestroy?: (sprite) => void; // Cleanup logic
} Use Cases
- Enemy spawning/despawning
- Collectible items
- Projectiles
- Any entities that appear/disappear dynamically
InputManager
Declarative input binding system.
const inputManager = adapter.createInputManager();
// Bind keyboard keys
inputManager.bindKeys({
'W': { action: 'move', input: { y: -1 }, mode: 'continuous' },
'S': { action: 'move', input: { y: 1 }, mode: 'continuous' },
'A': { action: 'move', input: { x: -1 }, mode: 'continuous' },
'D': { action: 'move', input: { x: 1 }, mode: 'continuous' },
'SPACE': { action: 'jump', input: {}, mode: 'pressed' }
});
// Bind cursor keys
const cursors = this.input.keyboard.createCursorKeys();
inputManager.bindCursors(cursors, {
horizontal: { action: 'move', speed: 200 },
vertical: { action: 'move', speed: 200 },
space: { action: 'jump' }
});
// Use input profiles
import { BUILT_IN_PROFILES } from '@martini-kit/phaser';
inputManager.loadProfile(BUILT_IN_PROFILES.WASD); Input Modes
continuous- Fires while key is held (movement)pressed- Fires once when key is pressed (jump, shoot)
Built-in Profiles
WASD- Standard PC controlsARROWS- Arrow key controlsMOBILE- Touch controlsXBOX- Gamepad mapping
PhysicsManager
Automates common physics behaviors.
const physicsManager = adapter.createPhysicsManager({
stateKey: 'inputs',
behaviors: [
{
type: 'platformer',
speed: 200,
jumpVelocity: -350,
applyTo: (playerId) => this.players[playerId]
}
]
}); Built-in Behaviors
Platformer:
{
type: 'platformer',
speed: number, // Horizontal speed
jumpVelocity: number, // Jump strength (negative)
applyTo: (playerId) => Sprite
} Top-down:
{
type: 'top-down',
speed: number, // Movement speed
applyTo: (playerId) => Sprite
} Custom:
{
type: 'custom',
update: (sprite, input, playerId) => {
// Your physics logic
}
} CollisionManager
Declarative collision rule system.
const collisionManager = adapter.createCollisionManager({
rules: [
{
between: ['player', 'enemy'],
onCollide: (player, enemy) => {
runtime.submitAction('damage', { amount: 10 });
}
},
{
between: [playerSpriteManager, coinSpriteManager],
onCollide: (player, coin, playerKey, coinKey) => {
runtime.submitAction('collect', { coinId: coinKey });
}
}
]
}); Rule Types
String-based:
{
between: ['player', 'platform'],
onCollide: (a, b) => { }
} SpriteManager-based:
{
between: [playerManager, enemyManager],
onCollide: (playerSprite, enemySprite, playerKey, enemyKey) => { }
} Group-based:
{
between: [playerGroup, platformGroup],
onCollide: (player, platform) => { }
} PlayerUIManager
Manage HUD elements synced to player state.
const uiManager = adapter.createPlayerUIManager({
stateKey: 'players',
elements: [
{
type: 'text',
property: 'score',
format: (score) => `Score: ${score}`,
offset: { x: 0, y: -50 },
style: { fontSize: '16px', color: '#fff' }
},
{
type: 'bar',
property: 'health',
max: 100,
offset: { x: 0, y: -40 },
width: 40,
height: 4,
fillColor: 0x00ff00,
bgColor: 0x333333
}
]
}); Element Types
Text:
{
type: 'text',
property: string,
format: (value) => string,
offset: { x: number, y: number },
style: Phaser.Types.GameObjects.Text.TextStyle
} Bar (health, mana, etc.):
{
type: 'bar',
property: string,
max: number,
offset: { x: number, y: number },
width: number,
height: number,
fillColor: number,
bgColor: number
} InputProfiles
Reusable control schemes.
import {
registerProfile,
getProfile,
listProfiles,
BUILT_IN_PROFILES
} from '@martini-kit/phaser';
// Use built-in profile
const wasd = BUILT_IN_PROFILES.WASD;
// Register custom profile
registerProfile('custom', {
move: {
keys: ['W', 'A', 'S', 'D'],
mode: 'continuous'
},
jump: {
keys: ['SPACE'],
mode: 'pressed'
}
});
// Apply profile
inputManager.loadProfile(getProfile('custom')); initializeGame()
High-level entry point that handles platform configuration.
import { initializeGame } from '@martini-kit/phaser';
const { runtime, phaser } = initializeGame({
game: gameDefinition,
scene: (runtime) => GameScene,
phaserConfig: {
width: 800,
height: 600,
physics: {
default: 'arcade',
arcade: { gravity: { x: 0, y: 800 } }
}
}
}); Configuration
interface martini-kitConfig {
game: GameDefinition;
scene: (runtime: GameRuntime) => typeof Phaser.Scene;
phaserConfig?: Phaser.Types.Core.GameConfig;
} What it does
- Reads
__martini-kit_CONFIG__from window (set by platform) - Creates appropriate transport (Local, IframeBridge, Trystero)
- Initializes GameRuntime
- Creates Phaser game instance
- Returns both runtime and phaser for advanced use cases
Host vs Client Pattern
The most critical pattern in martini-kit:
create() {
this.adapter = new PhaserAdapter(runtime, this);
if (this.adapter.isHost()) {
// HOST: Physics sprites with bodies
const sprite = this.physics.add.sprite(100, 100, 'player');
sprite.setCollideWorldBounds(true);
this.adapter.trackSprite(sprite, `player-${playerId}`);
} else {
// CLIENT: Visual-only sprites
this.adapter.onChange((state) => {
if (!state._sprites) return;
for (const [key, data] of Object.entries(state._sprites)) {
if (!this.sprites.has(key)) {
const sprite = this.add.sprite(data.x, data.y, 'player');
this.adapter.registerRemoteSprite(key, sprite);
this.sprites.set(key, sprite);
}
}
});
}
}
update() {
if (!this.adapter.isHost()) {
this.adapter.updateInterpolation();
}
} - Host uses
physics.add.*+trackSprite() - Client uses
add.*+registerRemoteSprite() - Client must call
updateInterpolation()inupdate() - Always check
if (!state._sprites) returnon clients
Shape-Based Games
For games without image assets (common in web IDEs):
// HOST: Rectangle with physics
const rect = this.add.rectangle(100, 100, 32, 32, 0xff0000);
this.physics.add.existing(rect);
const body = rect.body as Phaser.Physics.Arcade.Body;
body.setCollideWorldBounds(true);
this.adapter.trackSprite(rect, `player-${playerId}`);
// CLIENT: Rectangle without physics
const rect = this.add.rectangle(data.x, data.y, 32, 32, 0xff0000);
this.adapter.registerRemoteSprite(key, rect); Shapes work with all helpers (SpriteManager, PhysicsManager, etc.).
Next Steps
- Quick Start - Build your first game
- @martini-kit/core API - Core concepts and runtime
- Transports - Choose your networking backend
Examples
See working examples in the demos:
- Fire & Ice - Cooperative platformer
- Blob Battle - Competitive agar.io clone
- Paddle Battle - Pong-style game