Phaser
Phaser Integration
This guide teaches you how to properly integrate Phaser 3 with martini-kit’s host-authoritative multiplayer model. You’ll learn the critical patterns that make multiplayer Phaser games work correctly.
The Core Principle
martini-kit is host-authoritative. One player (the host) runs the actual game logic and physics. Other players (clients) receive state updates and mirror what’s happening on the host.
This means:
- Host: Creates physics sprites, runs collisions, applies game logic
- Clients: Create visual-only sprites, interpolate movement, display state
Critical Rule
The #1 beginner mistake is creating physics sprites on clients. This causes state desyncs, duplicate physics calculations, and broken gameplay.
Always check adapter.isHost() before creating physics objects.
Host vs Client Pattern
This pattern appears in every martini-kit game. Master it and you’ll avoid 90% of multiplayer bugs.
The Pattern
import { PhaserAdapter } from '@martini-kit/phaser';
class GameScene extends Phaser.Scene {
adapter!: PhaserAdapter;
remoteSprites = new Map<string, Phaser.GameObjects.Sprite>();
create() {
this.adapter = new PhaserAdapter(runtime, this);
if (this.adapter.isHost()) {
this.createHostSprites();
} else {
this.createClientSprites();
}
}
createHostSprites() {
// HOST: Create physics sprites
const state = this.adapter.getState();
for (const [playerId, playerData] of Object.entries(state.players)) {
const sprite = this.physics.add.sprite(
playerData.x,
playerData.y,
'player'
);
sprite.setCollideWorldBounds(true);
sprite.setBounce(0.2);
// Track sprite for automatic syncing
this.adapter.trackSprite(sprite, `player-${playerId}`);
}
}
createClientSprites() {
// CLIENT: Create visual sprites from state updates
this.adapter.onChange((state) => {
if (!state._sprites) return; // Critical check
for (const [key, data] of Object.entries(state._sprites)) {
if (!this.remoteSprites.has(key)) {
const sprite = this.add.sprite(data.x, data.y, 'player');
// Register for interpolation
this.adapter.registerRemoteSprite(key, sprite);
this.remoteSprites.set(key, sprite);
}
}
});
}
update() {
// CLIENT: Apply interpolation for smooth movement
if (!this.adapter.isHost()) {
this.adapter.updateInterpolation();
}
}
} Why This Works
- Host creates
this.physics.add.sprite()- Gets a physics body that can move, collide, and respond to forces - Host calls
trackSprite()- Automatically syncs position, rotation, velocity to state - Client creates
this.add.sprite()- Visual-only sprite with no physics body - Client calls
registerRemoteSprite()- Registers sprite for smooth interpolation - Client calls
updateInterpolation()- Smoothly moves sprites to match host state