Step 3: Initialize the Game
Create src/main.ts:
import Phaser from 'phaser';
import { GameRuntime } from '@martini-kit/core';
import { LocalTransport } from '@martini-kit/transport-local';
import { game } from './game';
import { createPaddleBattleScene } from './scene';
// LocalTransport lets you test with multiple browser tabs
const transport = new LocalTransport({
roomId: 'paddle-battle-room',
isHost: true,
});
const runtime = new GameRuntime(game, transport, {
isHost: transport.isHost(),
playerIds: [transport.getPlayerId()],
});
const PaddleBattleScene = createPaddleBattleScene(runtime, transport.isHost());
new Phaser.Game({
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'game',
physics: {
default: 'arcade',
arcade: {
gravity: { x: 0, y: 0 },
debug: false,
},
},
backgroundColor: '#1a1a2e',
scene: PaddleBattleScene,
}); Create index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Paddle Battle - Multiplayer Pong</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #0a0a0a;
font-family: system-ui, -apple-system, sans-serif;
}
#game {
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
}
</style>
</head>
<body>
<div id="game"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html> Update package.json:
{
"scripts": {
"dev": "vite",
"build": "vite build"
}
} Step 4: Test Your Game!
Start the development server:
pnpm dev Open http://localhost:3000 in two browser tabs side by side:
- First tab (Host): Controls left paddle with W/S or Arrow Up/Down
- Second tab (Client): Controls right paddle
You should see both paddles and the ball in perfect sync!
Troubleshooting
Ball doesn’t bounce off paddles?
- Check that you’ve added colliders:
this.physics.add.collider(this.ball, paddle)
Paddles jittery on client?
- Phaser: Ensure
spriteManager.update()is called every frame - Core: Check that state changes trigger visual updates
Second player’s paddle doesn’t appear?
- Verify
onPlayerJoinhook is creating paddles properly
Scores don’t sync?
- Make sure you’re using
context.targetId:submitAction('score', undefined, playerId)
Next Steps
Add More Features
1. Add a Winning Condition
In src/game.ts:
score: {
apply: (state, context) => {
const player = state.players[context.targetId];
if (!player) return;
player.score += 1;
if (player.score >= 11) {
state.gameOver = true;
state.winner = context.targetId;
}
// Reset ball...
},
} 2. Add Sound Effects
In src/scene.ts create():
this.sound.add('bounce');
this.sound.add('score');
// In the collider callback:
this.sound.play('bounce'); Learn More
- Core Concepts - Read Architecture
- Phaser Helpers - Check out the Phaser Helpers API
- More Examples - Explore Example Games
Congratulations! 🎉
You’ve built a complete multiplayer game with martini-kit! You now understand:
✅ Defining game state with TypeScript ✅ Handling multiplayer actions and state sync ✅ Host-authoritative architecture ✅ Smooth client-side rendering
✅ Using SpriteManager for automatic sprite sync ✅ Using InputManager for simplified input handling ✅ Using createPlayerHUD for automatic HUD management
Ready to build more complex games? Check out the Phaser helpers documentation!