Skip to main content

Building Doodlz.io: A Multiplayer Drawing Game

00:04:46:39

The Idea Behind doodlz.io

What happens when you combine the drawing skills of random internet strangers with the creative captioning powers of the masses? You get doodlz.io - a multiplayer game that I built to bring people together through collaborative creativity and humor.

The concept is simple but engaging: players draw images and write slogans, then mix and match them to find the funniest combinations. It's like a digital mashup of Pictionary and Cards Against Humanity, with real-time interaction that makes each session unique and unpredictable.

doodlz.io gameplay showing drawing interface and caption combinations

Technical Architecture

Building a real-time multiplayer game comes with interesting technical challenges. Here's how I approached the architecture of doodlz.io:

SvelteKit as the Foundation

I chose SvelteKit as the framework for several key reasons:

  1. Performance: Svelte's compilation approach means less JavaScript sent to the browser and faster runtime performance.
  2. Seamless transitions: SvelteKit's page transitions create a smooth, app-like experience.
  3. Developer experience: The reactive programming model makes state management intuitive.
javascript
// Example of Svelte's reactive declarations
let drawings = [];
let captions = [];
let combinations = [];

// This automatically updates when the source arrays change
$: combinations = drawings.flatMap(drawing =>
  captions.map(caption => ({ drawing, caption }))
);

Real-time Communication with WebSockets

For the real-time multiplayer functionality, WebSockets were the obvious choice. They allow:

  • Instant drawing updates as players create their masterpieces
  • Real-time voting on combinations
  • Immediate notifications when players join or leave
  • Low latency for a responsive game experience
javascript
// Server-side WebSocket implementation
const wss = new WebSocketServer({ server });

wss.on('connection', ws => {
  // Assign player to a game room
  const roomId = assignRoom(ws);

  ws.on('message', message => {
    const data = JSON.parse(message);

    // Broadcast drawing updates to other players
    if (data.type === 'drawing-update') {
      broadcastToRoom(
        roomId,
        {
          type: 'drawing-update',
          playerId: data.playerId,
          points: data.points,
        },
        ws
      );
    }

    // Handle other message types...
  });
});

The Drawing System

Creating an intuitive drawing system that works well across devices was a fun challenge. I implemented a canvas-based solution with touch and mouse support:

Canvas Drawing Logic

javascript
function setupCanvas(canvas) {
  const ctx = canvas.getContext('2d');
  let drawing = false;

  // Handle both mouse and touch events
  const startDrawing = e => {
    drawing = true;
    const { x, y } = getPointerPosition(e);
    ctx.beginPath();
    ctx.moveTo(x, y);
    sendDrawingUpdate({ type: 'start', x, y });
  };

  const draw = e => {
    if (!drawing) return;
    const { x, y } = getPointerPosition(e);
    ctx.lineTo(x, y);
    ctx.stroke();
    sendDrawingUpdate({ type: 'move', x, y });
  };

  const stopDrawing = () => {
    drawing = false;
    sendDrawingUpdate({ type: 'end' });
  };

  // Set up event listeners...
}

Stroke Optimization

To ensure smooth multiplayer drawing experiences, I implemented stroke optimization that balances detail and network efficiency:

  1. Points are sampled based on movement speed
  2. Redundant points are eliminated
  3. Strokes are compressed before sending over the network
  4. Client-side interpolation smooths out any network lag

Game Flow and State Management

The game progresses through several distinct phases, each with its own UI and interaction patterns:

  1. Lobby Phase: Players join and wait for enough participants
  2. Drawing Phase: Time-limited round where everyone draws an image
  3. Caption Phase: Players write funny captions without seeing the drawings
  4. Combination Phase: Drawings and captions are randomly paired
  5. Voting Phase: Players vote on the funniest combinations
  6. Results Phase: Scores are tallied and winners announced

Managing this complex state flow required careful planning:

javascript
// Game state machine
const gameStates = {
  LOBBY: 'lobby',
  DRAWING: 'drawing',
  CAPTION: 'caption',
  COMBINATION: 'combination',
  VOTING: 'voting',
  RESULTS: 'results',
};

function createGameStore() {
  const { subscribe, set, update } = writable({
    state: gameStates.LOBBY,
    players: [],
    drawings: [],
    captions: [],
    combinations: [],
    votes: {},
    timeRemaining: 0,
  });

  // State transition functions
  function advanceState() {
    update(game => {
      switch (game.state) {
        case gameStates.LOBBY:
          return { ...game, state: gameStates.DRAWING, timeRemaining: 60 };
        case gameStates.DRAWING:
          return { ...game, state: gameStates.CAPTION, timeRemaining: 45 };
        // Other state transitions...
      }
    });
  }

  // Other methods...

  return {
    subscribe,
    advanceState,
    // Other exported methods...
  };
}

Challenges and Solutions

Synchronization Across Clients

One of the biggest challenges was keeping the game state synchronized across clients with varying connection speeds. I implemented:

  1. A central authority model where the server is the source of truth
  2. Optimistic UI updates that show immediate feedback
  3. Conflict resolution when client predictions don't match server state
  4. Catchup mechanisms for clients that experience connection issues

Performance Optimization

Drawing applications can be resource-intensive, especially on mobile devices. To ensure smooth performance:

  1. Canvas operations are optimized to minimize redraws
  2. WebSocket message sizes are kept minimal
  3. Drawings are stored as vector paths rather than bitmap data
  4. Client rendering is throttled based on device capabilities

Mobile Touch Support

Supporting touch devices required special consideration:

  1. Preventing unwanted scrolling and zooming during drawing
  2. Calibrating touch precision for accurate drawing
  3. Optimizing UI layout for smaller screens
  4. Handling multi-touch gestures for special drawing tools

Lessons Learned

Building doodlz.io taught me valuable lessons about real-time web applications:

"The complexity of multiplayer games lies not in the features themselves, but in making them work seamlessly across network conditions and device types."

What Worked Well

  • SvelteKit: The framework's simplicity and performance made development enjoyable
  • WebSockets: Providing real-time communication without unnecessary complexity
  • Phased gameplay: Breaking the game into distinct phases kept players engaged

What I'd Do Differently

If I were to start over, I might:

  1. Use a specialized game state synchronization library
  2. Implement more aggressive client-side prediction
  3. Build a custom drawing library optimized for this specific use case

Try It Yourself!

doodlz.io is live and free to play! Gather some friends, join a game room, and experience the chaotic fun of collaborative drawing and caption creation.

The game works best with 4-8 players and each session typically lasts about 15-20 minutes. It's perfect for virtual hangouts, team building, or just a fun way to spend time with friends.

So what are you waiting for? Your artistic (or hilariously non-artistic) skills are needed at doodlz.io!