Home/Engines/Three.js multiplayer

engines · Three.js

Three.js multiplayer

A Three.js scene is a render loop and some transforms. Multiplayer is sharing those transforms fast and keeping the numbers that matter honest. The API does both, from a static page.

Free sandbox · $19.99/mo to go live · cancel anytime

why

3D multiplayer in the browser

For a 3D game the bandwidth question is the transform stream: position and rotation for every player, many times a second. Routing that through a server is what makes realtime backends expensive. Here it goes peer-to-peer over WebRTC, so the server only sees a presence call every five seconds per player once connections are up.

What must not be spoofed — hit points, kills, scores — goes through shared state, where your declared rules bound every write. Our FPS demo is built exactly this way, in Three.js, and its source is readable in the browser.

code

A complete Three.js integration

import * as THREE from 'three';
import { Multiplayer } from 'https://multiplayerapi.com/sdk/mp.esm.min.js';

const mp = new Multiplayer({ key: 'mp_pk_...' });
await mp.login();
const room = await mp.quickmatch({ mode: 'fps', max_players: 12 });

const remotes = new Map();                 // account_id -> { mesh, from, to, t0 }
room.on('peer', (p, from) => {
  let r = remotes.get(from);
  if (!r) {
    const mesh = new THREE.Mesh(new THREE.CapsuleGeometry(0.4, 1), new THREE.MeshStandardMaterial());
    scene.add(mesh);
    r = { mesh, to: p };
    remotes.set(from, r);
  }
  r.from = r.to; r.to = p; r.t0 = mp.now();  // interpolate between the last two packets
});
room.on('members', (m) => {
  const alive = new Set(m.map((x) => x.account_id));
  for (const [id, r] of remotes) if (!alive.has(id)) { scene.remove(r.mesh); remotes.delete(id); }
});

room.mine('hp', 100);
room.state.watch('players.*.hp', (hp, path) => hud.setHp(path.split('.')[1], hp));

setInterval(() => {
  const p = camera.position, q = camera.quaternion;
  room.send({ x: p.x, y: p.y, z: p.z, qx: q.x, qy: q.y, qz: q.z, qw: q.w });
}, 50);

function animate() {
  const now = mp.now();
  for (const r of remotes.values()) {
    const k = Math.min(1, (now - r.t0) / 50);
    r.mesh.position.set(
      r.from.x + (r.to.x - r.from.x) * k,
      r.from.y + (r.to.y - r.from.y) * k,
      r.from.z + (r.to.z - r.from.z) * k);
    r.mesh.quaternion.set(r.to.qx, r.to.qy, r.to.qz, r.to.qw);
  }
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}
animate();
architecture

What goes where

ChannelCallDeliveryUse it for
Directroom.send(data)peer-to-peer, best effort, zero server costpositions, aim, animation
Stateroom.set(path, value)server-validated, kept for the match, reaches late joinershealth, score, whose turn
Eventsroom.emitEvent(topic, data)ordered, guaranteed, not persistedchat, rematch, one-shots

Three.js-specific tips

  • Interpolate on mp.now(), the server-aligned clock, not Date.now(): client clocks drift by seconds.
  • Render others ~120 ms in the past. It leaves room for the next packet and turns jumps into motion.
  • Hits: the shooter reports a hit, the victim's client writes its own hp, and a rule such as {"write":"self","min":0,"max":100} bounds it. Kills that count for a leaderboard go through match attestation.
  • Quantize positions to centimeters before sending if you have many players: smaller packets, same feel.
Questions
Can I build a multiplayer FPS with Three.js and no game server?

Yes — our FPS demo is exactly that. Transforms and shots go peer-to-peer; health and kills are arbitrated in shared state.

What happens when WebRTC cannot connect two players?

That peer pair falls back to a server relay automatically. The rest of the room stays direct, so one player on a hostile network does not slow everyone down.

Does it work with React Three Fiber?

Yes. The SDK is plain JavaScript; create the client once, keep the room in a ref or store, and read remote transforms in useFrame.

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