DEV Community

Cover image for Turning Your Living Room into a Couch Co-op Arena with TouchCoop
Remo H. Jansen
Remo H. Jansen

Posted on

Turning Your Living Room into a Couch Co-op Arena with TouchCoop

Hey everyone πŸ‘‹,

Today I want to share something fun. Imagine this: you're at home with friends, the big TV is on, and instead of everyone fighting over Bluetooth controllers or passing around one gamepad… everyone just pulls out their phone and instantly becomes a player.

No accounts. No installs. Just scan a QR code and start mashing buttons.

That's exactly what TouchCoop enables β€” a tiny TypeScript library that turns this vision into reality with almost zero server hassle.

  • Host runs the game on a laptop/TV-connected browser
  • Up to 4 players join via QR code on their mobiles
  • Touch buttons on phone β†’ real-time input to the game via WebRTC
  • Perfect for casual games: platformers, party games, local puzzles
  • Not suited for low-latency games like FPS (WebRTC latency is good but not esports-level)

Quick architecture overview

Your game needs two distinct entry points (URLs):

  1. The Match page (TV/Laptop): Creates a Match instance, shows QR codes for joining, and receives player events.

  2. The Gamepad page (Phone): Opened via QR code, creates a Player instance, connects, and sends touch events.

Both are just static web pages β€” no backend required beyond the signaling phase.

Getting started in 60 seconds

Install the library:

npm install touch-coop
Enter fullscreen mode Exit fullscreen mode

1. The Match side (your game)

import { Match, PlayerEvent } from "touch-coop";

const gamePadURL = "https://your-domain.com/gamepad";  // Must be absolute URL for QR

function handlePlayerEvent(event: PlayerEvent) {
  switch (event.action) {
    case "JOIN":
      console.log(`Player ${event.playerId} joined πŸŽ‰`);
      // Maybe spawn player avatar, play sound, etc.
      break;
    case "LEAVE":
      console.log(`Player ${event.playerId} left 😒`);
      break;
    case "MOVE":
      console.log(`Player ${event.playerId} β†’ ${event.button}`);
      // Here you map "up", "A", "X" etc. to game actions
      if (event.button === "up") jumpPlayer(event.playerId);
      break;
  }
}

const match = new Match(gamePadURL, handlePlayerEvent);

// Optional: custom PeerJS server for better reliability
// const match = new Match(gamePadURL, handlePlayerEvent, {
//   host: 'your-peerjs-server.com',
//   port: 9000,
//   path: '/peerjs'
// });

// Later: match.requestNewPlayerToJoin() β†’ returns promise with QR data/URL
Enter fullscreen mode Exit fullscreen mode

Call match.requestNewPlayerToJoin() each time you want to add a slot β€” it gives you the join URL to turn into a QR code (many libraries can help with that).

2. The Gamepad side (React example)

import React, { useEffect, useState } from "react";
import { Player } from "touch-coop";

const player = new Player(); // Can pass custom PeerJS config too

export default function GamePad() {
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    (async () => {
      try {
        await player.joinMatch(); // Parses peer ID from URL query params
        setLoading(false);
      } catch (err) {
        console.error("Failed to join", err);
      }
    })();
  }, []);

  if (loading) return <div className="text-2xl">Connecting...</div>;

  return (
    <div className="grid grid-cols-3 gap-4 p-8 h-screen bg-black text-white">
      <button className="btn" onClick={() => player.sendMove("up")}>↑</button>
      <button className="btn" onClick={() => player.sendMove("left")}>←</button>
      <button className="btn" onClick={() => player.sendMove("right")}>β†’</button>
      <button className="btn col-start-2" onClick={() => player.sendMove("down")}>↓</button>

      <div className="col-span-3 flex justify-around mt-8">
        <button className="btn bg-green-600" onClick={() => player.sendMove("A")}>A</button>
        <button className="btn bg-blue-600" onClick={() => player.sendMove("B")}>B</button>
        <button className="btn bg-red-600" onClick={() => player.sendMove("X")}>X</button>
        <button className="btn bg-yellow-600" onClick={() => player.sendMove("Y")}>Y</button>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Live demo & try it yourself

The original project has a nice little demo:

β†’ https://SlaneyEE.github.io/touch-coop/demos/match.html

Final thoughts

TouchCoop is a beautiful example of how far browser APIs have come: WebRTC + TypeScript + modern build tools = couch co-op without native apps or complex backends.

If you're building casual multiplayer experiences or party games give it a try.

Have you built (or are you planning to build) a couch co-op game? Drop a comment below β€” I'd love to hear your multiplayer war stories or see links to your projects!

Happy coding, and see you in the comments ✌️

Top comments (0)