Getting Started
Build a multiplayer game in under 5 minutes.
1. Set Up the HTML
Create an HTML file with a canvas and an import map pointing to the library CDN:
<canvas id="game" width="640" height="480"></canvas>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.168.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.168.0/examples/jsm/",
"easy-multiplayer/": "https://libs.letsinspire.com/easy_multiplayer/dev/"
}
}
</script>
three import is currently required because some internal modules reference Three.js. This dependency will be made optional in a future release.
2. Create the Game
import { EasyMultiplayer } from 'easy-multiplayer/EasyMultiplayer.js';
const game = new EasyMultiplayer({
room: 'my-game-room', // Players in the same room see each other
tickRate: 15, // Game logic runs 15 times per second
canvas: document.getElementById('game'),
state: { // Your game state — any serializable object
players: {},
coins: [],
scores: {}
}
});
The state object is your entire game world. The library automatically snapshots it every tick, syncs hashes with other peers, and restores it during rollback. Keep it JSON-serializable.
3. Define Inputs
Register named inputs that the library will sample every tick and sync across the network:
const keys = {};
window.addEventListener('keydown', e => keys[e.code] = true);
window.addEventListener('keyup', e => keys[e.code] = false);
game.defineInput('left', () => keys['ArrowLeft'] || false);
game.defineInput('right', () => keys['ArrowRight'] || false);
game.defineInput('up', () => keys['ArrowUp'] || false);
game.defineInput('down', () => keys['ArrowDown'] || false);
Each input is a function that returns the current value. Return values must be JSON-serializable (strings, numbers, booleans).
4. Handle Players
game.on('playerJoined', ({ playerId, state, random }) => {
state.players[playerId] = {
x: Math.floor(random() * 20),
y: Math.floor(random() * 15),
score: 0
};
});
game.on('playerLeft', ({ playerId, state }) => {
delete state.players[playerId];
});
random() for randomness (not Math.random()), and don't do side effects like DOM changes or sounds here.
5. Write Game Logic
The tick callback runs at a fixed rate. Use query() to check inputs:
game.on('tick', ({ state, query, random, players }) => {
for (const pid of players) {
const p = state.players[pid];
if (!p) continue;
// Query-based input: predicate gets all inputs, rollback only if answer changes
if (query(pid, i => i.left && !i.right)) p.x--;
if (query(pid, i => i.right && !i.left)) p.x++;
if (query(pid, i => i.up && !i.down)) p.y--;
if (query(pid, i => i.down && !i.up)) p.y++;
}
// Spawn coins using deterministic random
if (state.coins.length < 5 && random() < 0.05) {
state.coins.push({
x: Math.floor(random() * 20),
y: Math.floor(random() * 15)
});
}
});
query(pid, i => i.left && !i.right) records one result, not two. See How It Works for details.
6. Render
The draw callback runs every animation frame (typically 60fps):
game.on('draw', ({ state, myId, canvas }) => {
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw coins
ctx.fillStyle = 'gold';
for (const coin of state.coins) {
ctx.fillRect(coin.x * 32, coin.y * 32, 32, 32);
}
// Draw players
for (const [id, p] of Object.entries(state.players)) {
ctx.fillStyle = id === myId ? 'blue' : 'red';
ctx.fillRect(p.x * 32, p.y * 32, 32, 32);
}
});
myId is the local player's unique ID, so you can highlight "your" character.
7. Start
game.start();
That's it. Open the page in two browser tabs (or on two devices). Both join the same room and play together.
What Happens Under the Hood
- Peers discover each other via WebTorrent (no server needed)
- Clocks synchronize to agree on time
- Each tick: inputs are sampled, predicted for remote players, and the game state advances
- Inputs are exchanged over the network
- If a prediction was wrong and it changes a query result, the game rolls back and resimulates
- State hashes are compared periodically to detect desyncs
Read the full technical explanation →
Next Steps
- API Reference — every method, property, and callback
- How It Works — rollback netcode, the query system, determinism
- Examples — a full Pac-Man game walkthrough
- Test Suite — debug desyncs and test determinism