Durable Objects in Action: Building a Multiplayer World on Workers

Durable Objects solve a real problem for developers working with Cloudflare Workers: how do you maintain consistency and state in a distributed, serverless environment? To understand this practically, I built a simple multiplayer 3D world called Durable World, running Unity WebGL on Pages with Workers orchestrating the backend via Durable Objects and WebSockets. It deploys entirely on the Cloudflare stack.

Real-time multiplayer at the edge isn't just a theoretical exercise. The recent multiplayer Doom post showed how WebAssembly, WebSockets, and Durable Objects can be combined to port classic games. Durable World takes a more modern approach, treating the client as authoritative: the browser runs the compiled Unity game, and Workers handle the coordination of game state across connected players.

In practice, the Durable Object acts as a message router. It accepts incoming state changes from clients, tracks who is connected, and pushes updates to all active players via WebSockets.

Character Management at the Edge

Getting started with Durable Objects can feel intimidating. But implementing one for this project turned out to be surprisingly straightforward. The Character module, a Durable Object built on the modules-rollup-esm template, handles incoming requests and acts as a WebSocket provider for clients.

The connection logic follows a familiar pattern: check for an Upgrade header and set up a WebSocketPair containing server and client sockets. The real work happens in handleSession, which manages two tasks: onboarding new players with randomly generated usernames and WebSockets, and broadcasting position updates from each player to everyone else in the game. A tick function pushes the current game state out to clients, while the rest of the code parses incoming messages to determine which sockets receive new data.

When a new connection is established, the Worker derives a unique ID from the user's IP address and starts streaming WebSocket messages to the client. Incoming data, such as a player position, triggers a rebroadcast to every other socket in the game — but not back to the sender.

Unity Client-Side Logic

Unity's Game Objects are the primary building blocks for everything in the scene, and C# scripts attach behaviors to them. Durable World uses three categories of Game Objects:

  • The static world itself — mostly cubes that don't exist in the networked state at all, with colliders preventing characters from falling through or walking through walls.
  • Your player capsule — controlled by keyboard input via a C# script.
  • Other players' capsules — stripped of any local input logic, positioned and rotated entirely by incoming WebSocket messages.

When the game launches, you explore a single-player environment until the WebSocket connection is established. From there, the Connection singleton takes over. It connects to Workers and dispatches functions on every new update. Player position and rotation are serialized to JSON and sent to the server every 0.2 seconds — effectively 5 frames per second up to the edge.

Receiving and Smoothing Game State

Sending your own position is only half the equation. The client also listens for messages pushed down from Workers. Each message contains the full game state — an approach that could certainly be optimized later — which the client parses to create, update, or animate each remote player. The code creates a new local player instance for each user in the gameState payload, updates position and rotation, and even sets a UI label for the player name.

Sending updates at 5 frames per second isn't enough for a playable game. Most games target 30 to 60 fps, so movement between received states needs to be smoothed. Instead of pushing 60 updates per second through the Durable Object, the client uses interpolation on the rendering side. When a new position or rotation arrives, the game transitions the remote character from its current location to the target using a smoothing function. Unity provides this through APIs like SmoothDamp, which eases rapid jarring movement over time. An InterpolateMovement script applies this to each remote player's transform.

Building Further on the Edge

Durable Objects and WebSockets bring a new category of real-time, interactive applications to Workers, and games are just the first use case. The source for Durable World is available on GitHub if you want to explore it yourself, and the Cloudflare Workers Discord is the place to discuss the next steps for building distributed real-time systems.