Multiplayer
Connect to a room. Send messages. Receive messages. That's the whole API.
Quick Start
import { connect } from '@watchtower-sdk/core'
// Connect to a room
const room = await connect('my-room')
// Send to everyone
room.broadcast({ x: 100, y: 200 })
// Receive messages
room.on('message', (from, data, meta) => {
console.log(`Player ${from}:`, data)
})
// Room info
room.players // Who's in the room
room.isHost // Am I the host?
room.code // Share this to invite othersHow It Works
Watchtower gives you the infrastructure:
- Connection — WebSocket to room, auto-reconnect
- Rooms — Create/join with codes
- Messaging — Broadcast to all, or send to one
- Player tracking — Who's in, join/leave events
- Timestamps — Server time + tick on every message
You build the game logic: how to sync state, how to smooth movement, how to handle game rules. The server is a message relay — it doesn't know what a "player position" is. It just routes your messages.
Rooms
// Create a new room (auto-generated code)
const room = await connect()
console.log('Share this:', room.code) // e.g., "X7K2M9"
// Join a specific room
const room = await connect('X7K2M9')
// Leave the room
room.leave()Messaging
Broadcast (to everyone)
// Send anything JSON-serializable
room.broadcast({ type: 'move', x: 100, y: 200 })
room.broadcast({ type: 'chat', text: 'Hello!' })
room.broadcast({ type: 'shoot', angle: 45 })Direct (to one player)
// Send to a specific player
room.send('player123', { type: 'private', text: 'Hey!' })Receive
room.on('message', (from, data, meta) => {
// from: who sent it
// data: what they sent
// meta.serverTime: when server received it
// meta.tick: message sequence number
if (data.type === 'move') {
updatePlayer(from, data.x, data.y)
}
if (data.type === 'chat') {
showChat(from, data.text)
}
})Player Tracking
// Current players
room.players // [{ id, name, meta, joinedAt }, ...]
room.playerCount // Number of players
room.playerId // Your ID
room.hostId // Host's ID
room.isHost // Are you the host?
// Events
room.on('join', (player) => {
console.log(`${player.name || player.id} joined`)
})
room.on('leave', (player) => {
console.log(`${player.id} left`)
})Patterns
Cursor Party
const cursors = {}
// Send my position
document.onmousemove = (e) => {
room.broadcast({ x: e.clientX, y: e.clientY })
}
// Receive others
room.on('message', (from, data) => {
cursors[from] = data
})
room.on('leave', (player) => {
delete cursors[player.id]
})Turn-Based (Chess, Cards)
let gameState = { board: initialBoard, turn: null }
// Host controls game state
if (room.isHost) {
gameState.turn = room.playerId
room.broadcast({ type: 'state', ...gameState })
}
room.on('message', (from, data) => {
if (data.type === 'state') {
gameState = data
render()
}
// Host validates and applies moves
if (data.type === 'move' && room.isHost) {
if (isValidMove(gameState, data)) {
gameState = applyMove(gameState, data)
gameState.turn = getNextPlayer()
room.broadcast({ type: 'state', ...gameState })
}
}
})
function makeMove(from, to) {
room.broadcast({ type: 'move', from, to })
}Action Game (Shooter, Platformer)
const players = {}
const LERP = 0.15
// Send position updates
setInterval(() => {
room.broadcast({
type: 'pos',
x: myPlayer.x,
y: myPlayer.y,
anim: myPlayer.animation
})
}, 50) // 20Hz
// Receive and store
room.on('message', (from, data, meta) => {
if (data.type === 'pos') {
if (!players[from]) players[from] = { x: data.x, y: data.y }
players[from].targetX = data.x
players[from].targetY = data.y
players[from].anim = data.anim
}
if (data.type === 'shoot') {
spawnBullet(from, data.angle)
}
})
// Smooth interpolation in render loop
function update() {
for (const p of Object.values(players)) {
p.x += (p.targetX - p.x) * LERP
p.y += (p.targetY - p.y) * LERP
}
requestAnimationFrame(update)
}
update()Smoothing Movement
The SDK doesn't smooth movement automatically — you decide how. The simplest approach is lerping:
const LERP = 0.15 // 0.1 = smooth, 0.3 = snappy
function update() {
for (const p of Object.values(players)) {
p.x += (p.targetX - p.x) * LERP
p.y += (p.targetY - p.y) * LERP
}
requestAnimationFrame(update)
}Every message includes meta.serverTime and meta.tickif you need more sophisticated interpolation or ordering.
What Games Can I Build?
- Cursor parties — broadcast positions
- .io games — positions + simple actions
- Turn-based — host-authoritative game state
- Card games — use direct messages for private hands
- Shooters — events for shots, positions for movement
- Co-op — shared objectives, broadcast progress
- Chat rooms — just messages
💡 Scaling
Rooms support ~50 players. For larger worlds, use zone sharding: split your world into regions, each as a separate room.
Next Steps
- Rooms — Creating, joining, room codes
- Messages — Broadcast, direct, events
- Game Patterns — Common multiplayer patterns
- SDK Reference — Complete API