Easy Multiplayer

Rollback netcode for the browser. Add deterministic multiplayer to any game with a few lines of code.

Get Started See Examples
import { EasyMultiplayer } from 'easy-multiplayer/EasyMultiplayer.js';

const game = new EasyMultiplayer({
    room: 'my-game',
    tickRate: 20,
    canvas: document.getElementById('game'),
    state: { players: {}, coins: [] }
});

game.defineInput('left',  () => keys.ArrowLeft || false);
game.defineInput('right', () => keys.ArrowRight || false);

game.on('tick', ({ state, query, random, players }) => {
    for (const pid of players) {
        if (query(pid, i => i.left && !i.right)) state.players[pid].x--;
        if (query(pid, i => i.right && !i.left)) state.players[pid].x++;
    }
});

game.on('draw', ({ state, myId, canvas }) => { /* render */ });

game.start();

Key Features

No Class Inheritance

Define your game with callbacks: on('tick'), on('draw'), on('playerJoined'). No base classes to extend.

Query-Based Input

Ask questions about input with predicates: query(pid, i => i.left && !i.right). Rollback only happens when an answer changes.

Automatic State Sync

Your game state is a plain JS object. The library snapshots, hashes, and syncs it automatically across all peers.

One-Line Networking

Pass a room name and call start(). Peer-to-peer connections via WebTorrent are handled for you.

Deterministic RNG

Use random() in your tick callback. It's seeded, deterministic, and automatically synced across rollbacks.

Visual Test Suite

Run multiple game instances side-by-side with simulated network delay. Detect desyncs, inspect state diffs, check determinism.

How It Works

Easy Multiplayer uses rollback netcode — the same approach used in fighting games and fast-paced multiplayer games. Every peer runs the game simulation locally at a fixed tick rate. Inputs are predicted, then corrected when the real inputs arrive over the network. If a prediction was wrong, the game rolls back and resimulates.

The query system is what makes this practical. Instead of comparing raw input values to decide whether to rollback, the game asks questions about input ("is the player pressing left?"). Only when a question's answer changes does a rollback happen. This dramatically reduces unnecessary rollbacks.

Learn more about the internals →

Get Started

Import the library via a CDN import map, create a game, define inputs, write your tick and draw logic, and call start(). That's it.

Follow the guide →