Skip to content

Add multiplayer to a Kaplay game

Last updated October 3, 2026

You can make a Kaplay game multiplayer without writing or hosting a server. Move your player the Kaplay way, copy its position to a GameRelay entity, and draw everyone else from theirs. The whole game below is about 40 lines.

How it works

Kaplay (formerly Kaboom.js) runs your game; GameRelay keeps players in sync. Anything others need to see is an entity with typed fields. Its owner writes it; everyone else reads a smoothed copy. One player's browser is the host for shared things like coins and enemies, and if it leaves, another player takes over with the same state.

1. Install

Create an instance at gamerelay.io/app and copy its public key (gr_pub_…). Then:

npm i kaplay @gamerelay/sdk

Using Kaplay from a script tag instead? Add <script src="https://gamerelay.io/sdk/gamerelay.js"></script> too, and use GameRelay.connect from a <script type="module">.

2. A multiplayer game in one file

import kaplay from 'kaplay';
import { GameRelay } from '@gamerelay/sdk';

const k = kaplay({ width: 800, height: 600, background: [17, 17, 17] });

const relay = await GameRelay.connect({ publicKey: 'gr_pub_…' });
const room = (await relay.joinInvite().catch(() => null)) ?? (await relay.quickMatch({ maxPlayers: 4 }));
history.replaceState(null, '', room.inviteUrl()); // the address bar is now the invite link

// What other players need to see, declared the same way in every player's game.
const players = room.define('player', { x: 'number', y: 'number' });

// Your player: a Kaplay object you move as usual, and an entity you copy it to.
const hero = k.add([k.rect(24, 24), k.pos(100, 300), k.anchor('center'), k.color(255, 107, 26)]);
const me = players.spawn({ x: hero.pos.x, y: hero.pos.y });

// Everyone else: one object per entity, removed when they leave.
const others = new Map();
players.on('remove', (p) => {
  others.get(p.id)?.destroy();
  others.delete(p.id);
});

k.onUpdate(() => {
  const dir = k.vec2(
    (k.isKeyDown('right') ? 1 : 0) - (k.isKeyDown('left') ? 1 : 0),
    (k.isKeyDown('down') ? 1 : 0) - (k.isKeyDown('up') ? 1 : 0),
  );
  hero.move(dir.scale(220)); // pixels per second
  me.x = hero.pos.x; // the SDK sends what changed, about 20 times a second
  me.y = hero.pos.y;

  for (const p of players.all()) {
    if (p.mine) continue;
    let o = others.get(p.id);
    if (!o) {
      o = k.add([k.rect(24, 24), k.pos(p.x, p.y), k.anchor('center'), k.color(79, 195, 247)]);
      others.set(p.id, o);
    }
    o.pos = k.vec2(p.x, p.y); // already smoothed
  }
});

Open it in two tabs and move each square. Send the address to a friend to play together.

Moments: events

Something that happens once, like a wave or a shot, is an event. It reaches everyone in the room, you included, so handle it in one place:

k.onKeyPress('space', () => room.emit('wave', { x: me.x, y: me.y }));
room.on('wave', ({ x, y }) => {
  const ring = k.add([k.circle(8), k.pos(x, y), k.anchor('center'), k.outline(2), k.color(0, 0, 0), k.opacity(1), k.lifespan(0.5, { fade: 0.4 })]);
  ring.onUpdate(() => (ring.radius += 120 * k.dt()));
});

Shared things: the host

Coins, enemies and timers belong to the world. The host owns them, and the next host takes over if it leaves:

// Only the host spawns the coin. If the host leaves, the next one owns it, where it was.
const coins = room.define('coin', { x: 'number', y: 'number' });
if (room.isHost) coins.spawn({ x: 400, y: 300 }, { owner: 'host' });

To let exactly one player take a coin, use a claim (room.claim). The SDK guide has a full "mixed" starter with players, host-owned pickups and claims.

Common pitfalls

  • Don't use Kaplay's area() collisions to move other players. Set their position from the entity. Collide against them if you like, but only move your own.
  • Only write what you own. A write to someone else's entity is skipped with a console warning.
  • Scenes: spawn your player's entity once per room, not each time a Kaplay scene starts (or me.remove() the old one first), and keep the same room across scenes.
  • Going live: add your site under Allowed origins on the instance so nobody else can use your key.

Try it

GameRelay has a free plan, no card needed. Get a public key, then follow the quickstart, or point your AI coding agent at llms.txt.