Skip to content

Multiplayer performance

A room replicates what changes: the server tells every player, twenty times a second, about each entity that moved since the last tick. Static scenery costs nothing, however big the map is. What costs bandwidth is movers — platforms, spinners, belts, props a script drives every frame. A map with 67 continuously moving entities sends each player roughly 50 KB/s; the same map with the movers parked sends almost nothing.

This page covers the three tools for cutting that cost, cheapest first. All three are opt-in, and a map that touches none of them behaves exactly as before they existed.

ProblemTool
Rhythmic movers (platforms, spinners, belts) dominate the trafficClock motion — zero bytes per mover
A big map where players only care about their surroundingsReplication distance (Scene Settings)
A spectator camera or cutscene far from the characterplayers.setReplicationFocus

Clock motion: movers that cost nothing

Motion that is a pure function of time does not need the network at all. Every player's game knows the room clock (game.getRoomTime() — the same number on every screen, late joiners included), so every client can compute where a platform is right now, on its own, every frame. No snapshots, no interpolation, perfectly smooth at any distance.

The pattern takes two short scripts and one checkbox:

  1. A client script computes the pose from the clock and writes it. This moves the mesh and the local collision surface on every player's machine.
  2. A server script computes the same function. The server's copy of the world stays correct, which is what NPCs, hit detection and server raycasts collide with.
  3. Tick Clock in the Inspector header (next to Static). That tells the room to stop replicating the entity's transform, so the server's copy never fights the clients' clock-computed motion.

Both scripts are the same few lines. This one glides from wherever you placed the platform (point A) to A + move and back — capture the start point in onStart and it follows the object wherever you drag it in the editor:

js
// PlatformMotion — attach twice: once as a client script, once as a server script.
class PlatformMotion extends Behaviour {
  moveX = 0
  moveY = 6    // how far to travel from the placed position
  moveZ = 0
  period = 8   // seconds for a full there-and-back trip
  phase = 0    // 0..1 — offset platforms that share a period so they run out of step

  onStart() {
    // Point A is the object's own position — safe to capture: every runtime, late joiners
    // included, starts a Clock entity at its authored pose.
    const p = this.transform.position
    this.baseX = p[0]; this.baseY = p[1]; this.baseZ = p[2]
  }

  onUpdate() {
    // (1 - cos)/2 sweeps 0..1..0 each period — the percentage of the way from A to A + move.
    // Pure function of the room clock: no accumulation, so every machine lands on the same
    // answer forever, and the platform eases at both ends (easy to step on and off).
    const w = (1 - Math.cos((this.game.getRoomTime() / this.period + this.phase) * Math.PI * 2)) / 2
    this.transform.position = [this.baseX + w * this.moveX, this.baseY + w * this.moveY, this.baseZ + w * this.moveZ]
  }
}

In the editor play-test and in a standalone build there is one machine, both scripts run on it, and they write the same value — the pattern needs no special casing.

Rules of the road:

  • The motion must be a pure function of getRoomTime(). Anything a player can push, break, pause or change must stay an ordinary server-driven entity.
  • The room clock belongs to the room, not to each player: someone joining a running room sees the platform mid-trip — in the same place as everyone already there — and it passes through its start point every period seconds. That shared schedule is what keeps all players in sync.
  • Children of a Clock entity ride along with it automatically. Give a child its own clock script if it also moves relative to the parent — its transform is not replicated either.
  • Scale is the one transform field that still replicates, once per change. Change it at moments, not every frame.
  • The flag lives on the entity and survives prefabs: save a Clock platform as a prefab and every placed or spawned copy keeps the behaviour. On a prefab instance the template's setting wins — checking Clock on one placed copy lasts only until the next prefab Apply; set it on the template (or on a non-prefab entity) to keep it.

Replication distance

Scene Settings > Replication adds a per-map radius: entities near a player replicate every tick, entities beyond it every Nth tick (or never, at interval 0), and everything catches up the moment the player approaches — including teleports, which deliver the full picture in the same instant as the move.

  • Distance culling: off by default. Existing maps are untouched.
  • Distance: the full-rate radius around each player, 250 by default. The floor is 100 — below that a fast player could reach a stale platform before its update lands.
  • Far interval: how often the far world updates. 4 means every fifth of a second; 0 means not at all until the player comes near.

Two things to know before turning it on:

  • It applies in published multiplayer and in Test Online. The editor play-test simulates everything locally, so the setting changes nothing there — verify with Test Online.
  • Mark the entities that must never go quiet with Always Rep. in the Inspector header: a finish-line timer, a boss, a lift somebody is walking toward. The mark covers the whole parented group.

Players are never culled: everyone always sees everyone, whatever the distance.

Streaming block worlds to players

A block build — an imported schematic or a generated block world — normally rides the join message whole: about 8 MB for a 512-block world, and every player downloads it before their first frame. Stream To Players, on the build's Inspector below Stream Dist., changes that. The room sends a joiner the build's header only and then streams chunks around each player, nearest first, as they move, the way Minecraft sends its world. Joining costs the same at any world size.

  • It needs a Stream Dist.: the chunks a player receives are the chunks they would draw.
  • Mining and building still replicate live. An edit reaches the players holding that chunk, and everyone else gets it inside the chunk when they walk near. A late joiner sees the current world.
  • Water and lava in the build arrive with their chunks; swimming works wherever the ground does.
  • The editor play-test and a standalone build have the whole build locally, so the setting changes nothing there — verify with Test Online, like distance culling.
  • The map file is not affected: it still carries every block, so the save limits are the same.

Off is exactly what every map has always done.

Storing the world on the map server

A big world can also be a thing of its own on the map server. Storage: On the map server, on the block build's asset in the Inspector (Pro), keeps the blocks there as a named world and puts only a reference in the map — every save, backup and publish of the map stays small whatever the world's size. The world has its own save actions on the asset: save it to the map server as an override (every map that uses the world follows) or as a new world, save it to disk as a world file, load one from disk, reload it from the map server, or replace it with another of your stored worlds. Place a stored world in another map from New › Block world from map server, and the same world is the ground of both. Publish and Test Online ask you to save the world first when it has edits the map server does not have. Rooms load the world from the map server when they start; players still receive it streamed, so nothing changes for them.

Repointing the radius: replication focus

The radius is measured from each player's character. When a script parks a player's camera far from their body — a spectator view, a cutscene, an overview map — repoint their focus so the world stays live around what they are watching:

js
players.setReplicationFocus(id, [0, 40, 250])   // a fixed point: watch the arena from the stands
players.setReplicationFocus(id, 'CameraDolly')  // follow an entity by name or id
players.setReplicationFocus(id, null)           // back to the character

Server scripts only, and a no-op on maps that have not enabled distance culling.

Choosing

Start with Clock motion for anything rhythmic — it is the biggest saving and needs no map-wide setting. Add Replication distance when the map is genuinely large and players only interact with their surroundings; a racing map or a small arena wants everything live and should leave it off. The focus API is for the moment a script moves a player's viewpoint away from their character.