Multiplayer
Let several people share one scene at the same time. Send messages, share values, spawn objects for everyone and show other players moving smoothly.
These calls work only when multiplayer is turned on for your project and a room is open. The rest of the time they do nothing. So the same script also works in a single-player preview, and you do not need two versions.
Who is in the room
ctx.net.id; // this participant's id
ctx.net.peers(); // everyone currently in the room
ctx.net.onJoin((peer) => {});
ctx.net.onLeave((peer) => {});
What syncs by itself
Only the positions of physics bodies set to shared. You send everything else yourself.
This is on purpose. A crate, a door or a ball should be one object that everyone sees in the same place. A player character must not be shared: otherwise, when one person walks, everyone's avatar moves. → ownership
Messages
Use messages for things that happen: someone scored, a round started, a door opened.
ctx.net.send('score', { points: 10 });
ctx.net.on('score', ({ from, data }) => {
/* … */
});
A message goes to everyone except you. You do not get a confirmation back.
Shared state
Use shared state for values everyone must agree on. It is a simple map, and everyone gets a copy. If two people write the same key, the last write wins.
ctx.net.state.set(ctx.net.id, { name, colour, ready: true });
ctx.net.state.get(somePeerId);
ctx.net.state.all();
ctx.net.state.on(ctx.net.id, (value) => {});
To keep values for each player, use ctx.net.id as the key. Each person writes their own entry
and reads everyone else's.
Spawn for everyone
const id = ctx.net.spawn(props.bullet, { position, rotation });
ctx.net.despawn(id);
You get the id right away, but the object appears a moment later. The id is the same for everyone.
Only the person who spawned an object can remove it. When someone leaves the room, the objects they spawned are removed too.
Move other players smoothly
This is where things most often go wrong, so there is a special call for it.
ctx.net.follow(otherPlayerObject, { position, rotation });
ctx.net.unfollow(otherPlayerObject);
Call follow with each position you receive. Ten to twenty times a second is normal. The object
then moves smoothly, a little behind the latest position.
Calling follow again with the same position costs nothing. So you can call it every frame with
the latest position you have.
Updates arrive much less often than frames, so the object stands still and then jumps: other players seem to step instead of walk. Easing toward the newest position does not fix it either. Between updates there is nothing new to move toward, so the object stops and then lurches.
A setup that works
If you are starting from scratch, this setup will take you a long way:
- Each player controls their own character — a local body, moved by their input.
- Their position is sent out through
net.stateor messages, ten to twenty times a second. - Everyone else's characters are moved with
follow. - World objects are shared bodies, so physics keeps them in sync for you.
- The score and the round state live in
net.state, written by whoever is in charge of that event.
Next: Troubleshooting — what to do when something does not work.