01Send on a tick, not on a frame
A game loop runs about sixty times a second. Sending a position that often spends bandwidth on differences no player can see, and on a busy room it is the fastest way to hit a rate limit.
Ten to twenty updates a second is enough. Keep updating your local player every frame as you already do, and publish on a timer.
let last = 0;
function loop(now) {
updateLocalPlayer(); // every frame, as before
if (now - last > 60) { // about 16 times a second
mp.me.set({ x: me.x, y: me.y });
last = now;
}
draw();
requestAnimationFrame(loop);
}02Draw toward the target, never at it
Updates arrive in steps, so drawing each one as it lands makes every other player jump. Keep the received value as a target and move a fraction of the remaining distance each frame.
This one change is the difference between a game that feels broken on ordinary wifi and one that feels fine.
const view = {}; // what we draw, per player id
mp.onState((state) => {
for (const [id, p] of Object.entries(state.entities.players)) {
view[id] ??= { x: Number(p.x), y: Number(p.y) };
view[id].tx = Number(p.x); // target
view[id].ty = Number(p.y);
}
});
function draw() {
for (const v of Object.values(view)) {
v.x += (v.tx - v.x) * 0.2; // 0.15 to 0.25 feels right
v.y += (v.ty - v.y) * 0.2;
drawPlayer(v);
}
}03Only your own player, by design
You can write your own entity and nothing else. There is no message that addresses another player's position, so the most common cheat in a browser game is impossible rather than merely blocked.
The trade is that movement is client-authoritative: a player can lie about where they are. That is fine for co-op and sandbox games. When a position decides a score or a hit, move that decision into server logic and send inputs rather than coordinates.
04Two things that will bite you
- Leave the room on unload. A refresh that did not leave cleanly holds the seat for about thirty seconds, so you appear twice to everyone. Wire window.addEventListener("pagehide", () => mp.leave()).
- Do not send a field per frame per property. Batch into one call with an object, and prefer fewer, larger fields to many small ones.
Common questions
How many players can move in one room at once?
Up to 256, bounded by your plan's per-game concurrent limit. Because all state replicates to everyone, traffic grows with players; test at your target size rather than assuming.
Do I need client-side prediction?
Not for casual and co-op games, where interpolation alone is enough. It matters when the server decides movement and you want your own input to feel instant.
Try it on your own game
The free tier does not expire and asks for no card. Add one script tag and have two people playing in about a minute.