Phaser multiplayer without a server
Most Phaser multiplayer tutorials start with “set up a Node.js server with Socket.IO”. That is where browser games stall: the game is a static bundle anyone can host for free, but the server is a process that needs a machine, a domain, TLS and uptime.
Here the server is hosted. Your Phaser project stays static. Remote players' positions arrive peer-to-peer over WebRTC, and anything that must be trusted — health, score, pickups — goes through shared state that the API validates against rules you declare.
A complete Phaser integration
import Phaser from 'phaser';
import { Multiplayer } from 'https://multiplayerapi.com/sdk/mp.esm.min.js';
const mp = new Multiplayer({ key: 'mp_pk_...' });
class Arena extends Phaser.Scene {
async create() {
this.me = this.physics.add.sprite(400, 300, 'ship');
this.others = {};
this.cursors = this.input.keyboard.createCursorKeys();
await mp.login();
this.room = await mp.quickmatch({ mode: 'arena', max_players: 8 });
// Remote positions: direct channel, interpolated in update()
this.room.on('peer', (p, from) => {
const s = this.others[from] ??= this.add.sprite(p.x, p.y, 'ship').setTint(0x5aa6ff);
s.target = p;
});
this.room.on('members', (members) => {
const ids = new Set(members.map((m) => m.account_id));
for (const id in this.others) if (!ids.has(id)) { this.others[id].destroy(); delete this.others[id]; }
});
// Health and score: shared state, validated by the server
this.room.mine('hp', 100);
this.room.state.watch('players.*.hp', (hp, path) => this.events.emit('hp', path, hp));
this.time.addEvent({ delay: 50, loop: true,
callback: () => this.room.send({ x: this.me.x, y: this.me.y, r: this.me.rotation }) });
}
update() {
if (!this.me) return;
const v = 220;
this.me.setVelocity(
(this.cursors.right.isDown - this.cursors.left.isDown) * v,
(this.cursors.down.isDown - this.cursors.up.isDown) * v);
for (const s of Object.values(this.others)) if (s.target) {
s.x += (s.target.x - s.x) * 0.25; // smooth toward the last packet
s.y += (s.target.y - s.y) * 0.25;
s.rotation = s.target.r;
}
}
}
new Phaser.Game({ type: Phaser.AUTO, width: 800, height: 600,
physics: { default: 'arcade' }, scene: Arena });What goes where
| Channel | Call | Delivery | Use it for |
|---|---|---|---|
| Direct | room.send(data) | peer-to-peer, best effort, zero server cost | positions, aim, animation |
| State | room.set(path, value) | server-validated, kept for the match, reaches late joiners | health, score, whose turn |
| Events | room.emitEvent(topic, data) | ordered, guaranteed, not persisted | chat, rematch, one-shots |
Phaser-specific tips
- Send on a timer, not every frame. A 50 ms
time.addEventis plenty; interpolate inupdate(). - Pickups use
setnx.room.setnx(`coins.${id}`, mp.account.id)— the first player to claim a coin keeps it, the second gets a refusal, no arbiter needed. - Handle
host. If one player spawns enemies, listen toroom.on('host', ...)so the next host takes over when the first leaves. - Turn-based Phaser games (cards, board) put everything in state and use
cason the turn — see the turn-based guide.
Do I need a Node.js server for Phaser multiplayer?
No. The Phaser game stays a static site; the hosted API handles accounts, rooms, signaling, validated state and the relay fallback.
Does it work with Phaser 3 and Phaser 4?
Yes. The SDK is engine-agnostic JavaScript with no dependency on Phaser's internals, so it works with any Phaser version that runs modern JavaScript.
How many players can join a Phaser room?
Up to 128 players per room and 2,000 live rooms per game on the $19.99/month plan.
Make your game online today
Free sandbox keys in one call, no account and no card. $19.99/month when you ship, for every game you make.
Free sandbox · $19.99/mo to go live · cancel anytime