OKONAONLINE

Developer Docs

Unity SDK · v2

OkonaPad

The Unity SDK for Okona Console games. Import it and every player's controller shows up as a standard Unity Gamepad — your existing Input System bindings just work. No setup, no custom device, no networking code.

Introduction

OkonaPad is the Unity SDK for games published to the Okona Console — a living-room device that boots into a library, lets a player pick a game, and runs it as a Unity WebGL build with local USB/Bluetooth gamepads. The SDK's whole job is to make those controllers appear inside your game as ordinary Unity Gamepad devices.

OkonaPad is just a Gamepad. OkonaPadDevice derives from UnityEngine.InputSystem.Gamepad, so Gamepad.all, buttonSouth, leftStick, rightTrigger, the <Gamepad>/… binding paths, and PlayerInputManager all work with zero Okona-specific code. Build your game against the standard Input System and it runs on Okona.

Key Features

  • Controllers arrive as standard Unity Gamepad devices
  • Up to 6 players, each in their own slot
  • Full controller surface: face buttons, bumpers, both sticks (incl. L3/R3), D-pad, triggers
  • Zero-allocation input via WebAssembly memory (no GC hitches)
  • Standard rumble through Gamepad.SetMotorSpeeds()
  • Self-bootstrapping — no GameObject, no component, no scene wiring
  • Works in the Editor with any pad; on the console with the real controllers

What changed in v2

SDK v2 is a clean break from v1. If you integrated against v1, re-import the package and re-bind to the standard Gamepad paths — it's simpler now.

Areav1 (retired)v2
Device typeCustom OkonaPadControllerDevice (not a Gamepad)OkonaPadDevice : Gamepad
Binding paths<OkonaPad>/buttonA<Gamepad>/buttonSouth
TransportSendMessage + JSON + Base64 per frameBinary snapshot read from HEAPU8 (zero alloc)
SetupAdd an OkonaInputBridge GameObject named exactlyNone — the bridge self-bootstraps
ControlsA/B/X/Y, Start/Select, D-pad, left stickEverything a Gamepad has, both sticks, analog triggers
RumbleCustom static eventStandard Gamepad.SetMotorSpeeds()
Don't mix native gamepads into your join logic on WebGL. In a WebGL build the SDK disables Unity's own browser-gamepad backend so a physical pad is never seen twice (once natively, once as an OkonaPad). You keep <Gamepad>/ bindings for Editor testing, but on the console every pad arrives as an OkonaPadDevice — which is a Gamepad, so your bindings are unaffected.

Requirements

Unity

  • Unity 6 (6000.3.x) or newer
  • Input System package (com.unity.inputsystem) 1.17+
  • Build target: WebGL

Project Settings

  1. Install the Input System package via Package Manager.
  2. Edit > Project Settings > Player > Other Settings → set Active Input Handling to Input System (New) (or Both).
  3. That's it. The SDK registers its layout and starts pumping input automatically at load — there is nothing to add to a scene.

Quick Start

1

Import the SDK

Download OkonaPad.unitypackage and import it (Assets > Import Package > Custom Package…). It drops a single Assets/OkonaPad/ folder into your project. There is no component to add and no GameObject to create — the bridge self-bootstraps via [RuntimeInitializeOnLoadMethod].

2

Bind to the standard Gamepad

In your Input Actions asset, bind to <Gamepad>/ paths exactly as you would for any Unity game — <Gamepad>/buttonSouth, <Gamepad>/leftStick, <Gamepad>/rightTrigger, and so on. An OkonaPadDevice is a Gamepad, so these match on the console and in the Editor with any pad you plug in.

Bind <Gamepad>, not <OkonaPad>. A control scheme that requires <OkonaPad> will reject native pads in the Editor, so a joined player is silently dropped. Use <Gamepad> and everything works in both places.
3

Read input

Use the Input System exactly as normal — PlayerInput / PlayerInputManager (recommended, see below), an InputActions asset, or direct polling:

using UnityEngine;
using UnityEngine.InputSystem;

public class Player : MonoBehaviour
{
    void Update()
    {
        var pad = Gamepad.current;        // an OkonaPadDevice on the console
        if (pad == null) return;

        if (pad.buttonSouth.wasPressedThisFrame) Jump();
        Vector2 move = pad.leftStick.ReadValue();
        float gas = pad.rightTrigger.ReadValue();
    }
}
4

Build for WebGL & publish

  1. File > Build Settings > WebGL → Build.
  2. Upload the build through the Developer Portal and submit it for review.
  3. Once published, the console loads it and feeds your game the connected controllers.

API Reference

Everything lives in the OkonaPad namespace. For most games you never touch these types directly — you read input through the standard Gamepad / PlayerInput API. They're here for when you want the Okona slot, manual access, or deterministic tests.

OkonaPadDevice

OkonaPad.OkonaPadDevice : UnityEngine.InputSystem.Gamepad

One connected Okona controller, surfaced as a standard Unity Gamepad. Every normal Gamepad control and binding works out of the box. The only addition is the player slot.

Members

MemberTypeDescription
okonaSlotint0-based player slot (P1 = 0 … P6 = 5); -1 until assigned
SetMotorSpeeds(low, high)voidStandard Gamepad rumble; forwarded to the physical pad
PauseHaptics() / ResetHaptics()voidStop rumble (sends 0,0)

Inherited from Gamepad: buttonSouth/East/West/North, leftShoulder, rightShoulder, leftTrigger, rightTrigger, select, start, leftStick, rightStick, leftStickButton, rightStickButton, dpad, plus Gamepad.all and Gamepad.current.

OkonaInputBridge

static class OkonaPad.OkonaInputBridge

The static driver that pulls the input snapshot from the web layer each frame and adds/removes OkonaPadDevices as players connect and disconnect. It bootstraps itself at load — you do not create or reference it for normal use.

Members

// The connected device for a 0-based slot, or null.
public static OkonaPadDevice GetDevice(int slot);

// Pull the current snapshot and push it to the Input System. Runs every
// frame automatically (InputSystem.onBeforeUpdate); also callable directly
// for deterministic tests.
public static void Pump();

// Editor/test only: inject a snapshot as if it came from the web layer.
#if UNITY_EDITOR
public static void SimulateSnapshot(byte[] snapshot);
#endif
In the Editor the bridge is dormant. There's no web layer (no window.__okonaPad), so no OkonaPadDevices are created and Pump() no-ops. You test with native Unity gamepads, which work because your bindings target <Gamepad>. In a WebGL build the bridge manufactures OkonaPadDevices from the live snapshot.

Controls & Bindings

OkonaPad maps the standard controller to the standard Unity Gamepad controls. Bind to the <Gamepad>/ paths below.

ControlBinding pathNotes
A (bottom)<Gamepad>/buttonSouth
B (right)<Gamepad>/buttonEast
X (left)<Gamepad>/buttonWest
Y (top)<Gamepad>/buttonNorth
Left bumper<Gamepad>/leftShoulder
Right bumper<Gamepad>/rightShoulder
Left trigger<Gamepad>/leftTriggerAnalog 0–1
Right trigger<Gamepad>/rightTriggerAnalog 0–1
Select / View<Gamepad>/select
Start / Menu<Gamepad>/start
Left stick<Gamepad>/leftStickVector2
Right stick<Gamepad>/rightStickVector2
L3 (stick press)<Gamepad>/leftStickPress
R3 (stick press)<Gamepad>/rightStickPress
D-pad<Gamepad>/dpad/dpad/up etc.
The Home / Guide button is reserved. Pressing it on the console opens the in-game Okona menu (Continue / Exit). It is intercepted by the shell and never delivered to your game, so don't bind it.
Analog triggers are Values, not Buttons. Bind <Gamepad>/leftTrigger and /rightTrigger to actions typed Value / Axis (with Initial State Check on), then read the live 0–1 pull with ReadValue<float>() and apply your own threshold. If you type the action as a Button, ReadValue<float>() stays 0 until the pull crosses the press point (default 0.5) — so light presses do nothing and some pads (e.g. an Xbox controller over Bluetooth) may never register at all. A face button mapped to the same action still fires, which hides the bug. If you reuse a trigger Value action as a menu “confirm,” subscribe to started (one edge), not performedperformed re-fires every frame the trigger is held.

Input Snapshot advanced

You never read this directly — it's how input physically reaches your game, documented for the curious. The shell keeps a fixed-layout binary snapshot of every player's current state in window.__okonaPad. Each frame the SDK copies it straight out of WebAssembly memory (HEAPU8) into a reused buffer and decodes it — no SendMessage, no JSON, no Base64, zero per-frame allocation.

Layout is little-endian, 106 bytes (a 4-byte header + 6 × 17-byte pad records). It is defined identically in bridge/okona-padstate.js (web) and OkonaPad/OkonaPadState.cs (Unity).

Header (4 bytes)
  [0]      magic        0x4F ('O')
  [1]      version      1
  [2]      maxPads      6
  [3]      reserved     0

Per pad (17 bytes), one per slot 0..5
  [0]      flags        bit0 = connected
  [1..4]   buttons      uint32 LE — bit i = button i pressed
  [5..6]   leftX        int16  LE  (-32767..32767 => -1..1)
  [7..8]   leftY        int16  LE
  [9..10]  rightX       int16  LE
  [11..12] rightY       int16  LE
  [13..14] leftTrigger  uint16 LE  (0..65535 => 0..1)
  [15..16] rightTrigger uint16 LE

The buttons bit positions are the standard Web Gamepad indices:

BitButton
0–3A, B, X, Y
4–5LB, RB
6–7LT, RT (digital)
8–9Select, Start
10–11L3, R3
12–15D-pad Up, Down, Left, Right
16Guide (reserved)

How Input Flows

On the Okona Console, a USB or Bluetooth pad is paired at the OS level and surfaced into the game page; the shell tracks it in one of six player slots and writes its state into the snapshot; the SDK reads the snapshot from WebAssembly memory and drives a Gamepad for it.

USB / BT Pad
Game Shell
window.__okonaPad
OkonaInputBridge (HEAPU8)
Gamepad in your game

Slots & reconnection

The shell manages six slots. A pad is adopted into the lowest free slot the moment a button is pressed on it (a single pad attached at load is adopted automatically). If a pad disconnects — including a brief Bluetooth blip — its slot is held for 30 seconds and the same player resumes when it returns, so a momentary drop never turns into a new join. After the reserve expires the slot is freed and the OkonaPadDevice is removed.

Rumble

Call SetMotorSpeeds(low, high) on the player's Gamepad. The SDK forwards it to the web layer, which vibrates the physical pad (best-effort — silently ignored on pads that don't support haptics). No Okona-specific API to call.

Reference Project

The SDK ships with a Unity 6 reference project that demonstrates the recommended setup end-to-end:

  • Input Tester — a PlayerInputManager scene that spawns one on-screen controller widget per joined pad and lights up every input (face buttons, bumpers, sticks, L3/R3, D-pad, analog triggers). Drop in up to six pads and watch them light independently.
  • Player prefab + Input Actions — a bare PlayerInput prefab and an actions asset with a <Gamepad> control scheme you can copy straight into your game.

It's the fastest way to confirm the SDK works with your controllers — open the Input Tester scene, press Play, and press a button on each pad.

Testing Your Game for Okona

The Okona Dev Harness runs your WebGL build on your own machine exactly as the console runs it — the same game shell, the same controller input path, the same in-game menu — so you can validate your game before you publish it. Download the harness — it's a small tool that needs only Python 3.7+, nothing to install.

Run it

  1. Build your game for WebGL with Brotli compression (see Requirements).
  2. Download the harness and unzip it anywhere.
  3. From the unzipped okona-harness/ folder, point it at your build — the folder Unity produced, containing index.html and a Build/ subfolder:
    python devtools/okona-test/okona-serve.py <path-to-your-WebGL-build>
  4. A browser opens with your game running. Plug in a USB or Bluetooth controller and play. Press ` (backtick) to toggle the diagnostics overlay.

The serve script sets the HTTP headers Unity WebGL requires — the reason a plain static server usually fails to load a Brotli build.

Confirm your controls

The strip along the bottom shows one card per controller slot (P1–P6), decoded live from the input snapshot the SDK reads: pressed buttons, stick positions, trigger pull. Use it to confirm every control maps the way you expect, and that multiple pads each drive their own player.

Check performance against the console

The Okona Console is a Raspberry Pi 5. A game that hits 60 fps on your development machine can run far slower on the Pi, so the harness does not rely on desktop framerate. Instead it grades your game's GPU workload — which predicts console behaviour no matter how fast your machine is — against budgets measured on real console hardware:

Metric (per frame)BudgetWhy it matters
Draw calls≤ 80Each has fixed CPU cost; the Pi's CPU is the bottleneck
Buffer uploads≤ 2 MBPer-frame dynamic vertex/index data stalls the GPU
Vertices≤ 100KRaw geometry throughput
Render-target switches≤ 8Tiler GPUs pay heavily per pass (shadows, post stacks)

Green is comfortable, amber is at budget, red is over. If you're in the red, reduce draw calls (batch/atlas), cut per-frame mesh rebuilds, and avoid realtime shadows and full-screen post-processing.

The framerate shown is your desktop's, not the Pi's. The workload budgets are the real signal in the harness — but the final word on performance is always running your published game on a real console.

Pre-submit checklist

  • Game loads and is playable in the harness (Brotli build).
  • Every control reads correctly in the input visualizer.
  • Multiple controllers each drive their own player (P1–P6).
  • Home opens the Game Menu; Continue resumes, Exit leaves.
  • Workload metrics are green or amber — not red.
  • Rumble fires on supported pads (if your game uses it).

Troubleshooting

No controller in the Editor

  • In the Editor the OkonaPad bridge is dormant — you test with native Unity gamepads, so plug one in and press a button.
  • Confirm Active Input Handling is Input System (New) (or Both) in Player settings.
  • Check the pad shows up: Window > Analysis > Input Debugger should list a Gamepad.

Input doesn't respond on the console

  • Make sure your actions bind to <Gamepad>/ paths (not a custom device). An OkonaPadDevice is a Gamepad.
  • If you use PlayerInputManager with C# join/leave events, set its Notification Behavior to Invoke C# Events — the events don't fire otherwise.
  • Make sure your control scheme requires <Gamepad>, not <OkonaPad>.

A pad controls two players (or joins twice)

  • This is the native-gamepad-double-up problem. The SDK disables Unity's browser-gamepad backend in WebGL automatically; don't re-enable it or join players from a backend you've re-added.
  • Never build join logic off InputSystem.devices that you've manually re-enabled on WebGL — let PlayerInputManager handle joins.

Rumble not working

  • Call SetMotorSpeeds(low, high) on the player's Gamepad (values 0–1).
  • Not every physical pad supports haptics; the request is silently ignored on those.

WebGL build issues

  • Ensure Input System (New) is enabled in Project Settings.
  • Don't include server-side / networking code — WebGL can't run sockets, and the SDK needs none.

Use with AI Assistants

If you build with an AI coding assistant, connect it to the Okona docs MCP server and it can read this documentation directly — the full SDK reference, the testing guide, and the dev-harness manual — instead of guessing at the OkonaPad API. It's a public, read-only Model Context Protocol endpoint; no account or API key needed.

https://okonaonline.com/mcp

Claude Code

claude mcp add --transport http okona-docs https://okonaonline.com/mcp

Cursor

Add to .cursor/mcp.json in your project (or the global one):

{
  "mcpServers": {
    "okona-docs": { "url": "https://okonaonline.com/mcp" }
  }
}

Other clients

Any MCP client that supports the Streamable HTTP transport works — point it at the URL above with no authentication. The server exposes three tools:

  • list_doc_sections — the documentation catalog
  • read_doc_section — one section (or everything) as markdown
  • search_docs — keyword search with snippets
Always current. The server reads this page live, so your assistant sees the same documentation you do — including updates published after you configured it.

Build Your Own Console

The Okona Console is a Raspberry Pi 5 appliance. We publish the exact pre-provisioned image we flash ourselves, so you can build your own: flash one SD card, plug it into a TV, and it boots straight into Okona — the same setup, library, and gameplay as a retail unit. No install steps, no command line.

Loading the latest image…

What you'll need

  • Raspberry Pi 5 (recommended). A Pi 4 also works; Pi 3 and older can't run Unity WebGL.
  • microSD card, 16 GB or larger.
  • Official USB-C power supply — 27 W for the Pi 5, 15 W for the Pi 4. (Underpowered supplies cause hard-to-diagnose instability.)
  • micro-HDMI → HDMI cable, into the HDMI port next to the USB-C jack (HDMI0).
  • A TV with an HDMI input.
  • A USB or Bluetooth gamepad (Xbox, PlayStation, 8BitDo, Switch Pro…). A phone on the same Wi-Fi can also act as a controller.

Flash the card

  1. Download the image above — it's a .img.gz. Don't unzip it; the flashing tool reads it directly.
  2. Install Raspberry Pi Imager (or balenaEtcher).
  3. In Imager: Choose Device (your Pi) → Choose OSUse custom → pick the downloaded .img.gzChoose Storage (your microSD) → Write.
Skip OS customization. This image already has its user, services, and settings provisioned. If Imager offers to apply OS customization settings (Wi-Fi, username, SSH), choose No — the console configures everything itself on first boot.

Verify the download optional

Confirm the file arrived intact by checking its SHA-256 against the published value ():

# Windows (PowerShell)
Get-FileHash -Algorithm SHA256 okona-console.img.gz

# macOS / Linux
shasum -a 256 okona-console.img.gz

First boot

Insert the card, connect HDMI and power, and the Pi boots into the Okona setup screen.

  • Wi-Fi: the console raises its own Okona-XXXX hotspot and shows a QR code on the TV. Scan it with your phone, pick your home network, and type its password. The TV flips to the game library once it's online.
  • Controllers: hold a Bluetooth pad's pairing button to pair it — there's no pairing screen to find. USB pads just work when plugged in.
  • Pick a game and play. Subsequent boots skip setup and land on the library in seconds.
This is an appliance image. There's no desktop and no SSH — it's the living-room console, not a development board, and it keeps itself up to date over the air. If you're here to make games rather than build hardware, grab the SDK and dev harness above instead.

Ready to build?

Download the SDK, bind to the standard Gamepad, and ship to the Okona Console.

Back to Okona