Skip to content

Hosts, controllers, relay

Every PairKit session involves three things. Internalizing the model makes everything else click.

The three actors

Phone browsers Relay Unity Editor / build
(controllers) (router) (host)
┌──────────┐ ┌──────────────────┐
│ widget │ widget events │ PhoneController │
│ events │ ───────────► WebSocket ─────────────► │ Manager │
│ buttons, │ router │ OnPlayerJoin │
│ d-pads, │ ◄───────── (session + ◄──── │ OnInput │
│ etc. │ player state) │ SendToPlayer │
└──────────┘ │ Broadcast… │
└──────────────────┘

Host

The Unity scene running PhoneControllerManager. Exactly one host per session. The host owns the layout (which widgets the controllers display), receives all input events, and can send commands back to specific players (SendToPlayer) or all of them (BroadcastToAllPlayers).

The host is your game. Game logic, scoring, scene transitions, persistence — none of that is PairKit’s concern. PairKit only tells your code what the players are doing.

Controllers

The phones that have joined the session by scanning the QR code. There can be one or many; PairKit doesn’t impose a player cap, and real-world deployments have run with 20+ phones in one session. Each phone sees the host-defined widget layout in their browser and emits events when the player interacts.

A controller has no SDK. It’s HTML, CSS, and JavaScript served by the relay; the user’s browser is doing all the work.

Relay

A WebSocket router. Two of them ship today:

  • Cloud relay at wss://relay.pairkit.dev — hosted on Fly.io, default for any internet-connected setup.
  • LAN relay — runs in-process inside your Unity build. Phones connect over Wi-Fi; no internet needed. See Cloud vs LAN mode.

The relay holds session state (codes, player IDs, screen state for Flows), and forwards messages between the host and its controllers. It does not interpret game logic — that’s the host’s job.

How a session begins

  1. Unity starts; PhoneControllerManager connects to the relay and asks for a session.
  2. The relay assigns a 4-character code (e.g. WXYZ) and returns a join URL.
  3. Unity displays the code + a QR code via the custom Inspector.
  4. A player scans the QR. Their phone opens the join URL; the relay maps that URL to the session and serves the web controller.
  5. The phone sends session.join; the relay assigns a stable player_id and tells the host. OnPlayerJoin fires.

From this point forward: every press, every drag, every text submission flows through the relay to the host’s OnInput callback. Everything Unity sends back goes through the relay to a specific phone or all of them.

Why this shape

  • No phone-side SDK keeps the install funnel down to “scan a QR code.” Mobile builds, App Store review, and OS fragmentation never enter the picture.
  • A relay lets phones reach Unity hosts behind home routers without port-forwarding or hole-punching. Cellular phone, Unity on a laptop, no shared LAN — works.
  • An engine-agnostic protocol means the same controller bundle works for Unity today and for Unreal/Godot later without a rewrite.

Where to go next