Your first multiplayer game
You will build tag: whoever is “it” chases the others, staying free earns points, the round lasts 60 seconds. You need Node 22 or newer.
1. A folder and a server
Section titled “1. A folder and a server”mkdir games && cd gamesnpm init -y && npm install tickcli wsnpx tickcli . # serves every .html file here, with live roomsOpen the address it prints. The list is empty until you add a game.
2. One player moving
Section titled “2. One player moving”Save this as tag.html:
<!doctype html><html><head><meta charset="utf-8"><title>Tag</title></head><body><script src="lib:room"></script><script src="lib:game"></script><script>const W = 800, H = 600, SPEED = 230;let x, y;Game({ title: "Tag", help: "Arrow keys, WASD or the pad to run.", size: [W, H], multiplayer: { max: 8, player: { x: [0, W], y: [0, H] } }, setup(g) { x = 60 + Math.random() * (W - 120); y = 60 + Math.random() * (H - 120); g.me.set({ x, y }); }, update(g, dt) { const a = g.axis(); x = Math.max(12, Math.min(W - 12, x + a.x * SPEED * dt)); y = Math.max(12, Math.min(H - 12, y + a.y * SPEED * dt)); g.me.set({ x, y }); }, draw(g, ctx, w, h) { ctx.fillStyle = "#0f172a"; ctx.fillRect(0, 0, w, h); for (const p of g.players) { ctx.fillStyle = p.me ? "#60a5fa" : "#f87171"; ctx.beginPath(); ctx.arc(p.state.x, p.state.y, 14, 0, 7); ctx.fill(); } },});</script></body></html>Reload the list, open Tag in two windows, press Start in the first. Both dots move on both screens.
What happened:
lib:roomandlib:gameare the two browser libraries; the server points the tags at its own copies.multiplayer.playerdeclares each field a player shares and its range. Values are clamped to it, and positions go out as whole numbers.g.me.set({x, y})can be called every frame: it goes out at most 12 times a second, only when something changed.g.playersis everyone, you included. Others are drawn a little in the past and eased, so they move smoothly.
3. Rules on one browser: the host
Section titled “3. Rules on one browser: the host”Exactly one player is the host. Its host(g, dt) runs the rules and writes the shared state. Add this to the Game({...}) options:
host(g, dt) { const s = g.shared, live = g.players.filter((p) => typeof p.state.x === "number"); if (s.endsAt == null) return g.shared.set({ endsAt: g.now + 60, it: live[0]?.id ?? null, score: {} }); if (g.now >= s.endsAt) return g.gameOver(); const it = live.find((p) => p.id === s.it); if (!it) return; const score = {}; for (const p of live) if (p.id !== s.it) score[p.id] = ((s.score && s.score[p.id]) || 0) + dt; g.shared.set({ score }); for (const p of live) if (p.id !== it.id && Math.hypot(p.state.x - it.state.x, p.state.y - it.state.y) < 28) { g.shared.set({ it: p.id }); break; }},And draw who is it: in draw, give the player whose id is g.shared.it a red ring.
g.nowis a clock every screen shares, soendsAtis the same moment everywhere.g.shared.setmerges:{score: {[id]: 3}}changes one entry. Only the host may write it.- If the host leaves, another player becomes host at once and continues from
g.shared. Keep nothing important only in host variables. g.gameOver()ends the match; the lobby shows the scores and Play again.
4. Share it
Section titled “4. Share it”Press “Copy invite link” in the lobby and send it: the friend lands in your room. On a phone the shell shows a touch pad and keeps the page from zooming.
- The finished game, with a red ring and sounds:
examples/games/tag.html. - Writing a game: seats, teams, cups, shots, cameras.
- A 3D game with three.js or your own engine.