ctrllr-sdk
v0.0.0
Published
CTRLLR - Mobile game controller SDK
Readme
CTRLLR SDK
Turn any smartphone into a game controller. CTRLLR provides WebRTC-powered low-latency input for multiplayer games.
Example: https://ctrllr-sdk-vanilla.vercel.app/
Packages
| Package | Description | Status | | ------------------------------- | ---------------------------------------------- | -------------- | | @ctrllr/core | Core SDK with WebRTC, events, state management | ✅ Ready | | @ctrllr/react | React hooks and components | 🔜 Coming soon |
Architecture
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Game App │ │ Signaling Server │ │ Mobile App │
│ (@ctrllr/core) │ │ (socket.io) │ │ (Controller) │
└────────┬────────┘ └────────┬─────────┘ └────────┬────────┘
│ │ │
│◄──── 1. Connect ─────►│◄──── 2. Connect ──────►│
│ │ │
│ 3. Get socket ID │ │
│ 4. Show QR code ──────┼───────► 5. Scan QR │
│ │ │
│◄─────────────── 6. WebRTC Signaling ──────────►│
│ │ │
│◄═══════════════ 7. WebRTC DataChannel ════════►│
│ │ │
│◄─────────── 8. Controller Input ──────────────│Connection Flow:
- Game app connects to your signaling server → receives socket ID → displays QR code
- Mobile controller app connects to signaling server → opens camera, ready to scan
- Player scans QR code → controller reads socket ID → tells server "connect me to this game"
- Signaling server brokers WebRTC handshake (offer/answer/ICE)
- Direct WebRTC DataChannel established for low-latency input
- SDK emits input events to your game logic
Quick Start
# Install
pnpm add @ctrllr/coreimport { CtrllrManager } from '@ctrllr/core';
const ctrllr = new CtrllrManager({
signalingUrl: 'wss://your-signaling-server.com',
});
await ctrllr.connect();
// Show QR code
document.getElementById('qr').src = await ctrllr.getQRCodeDataURL();
// Handle controllers
ctrllr.on('controllerconnected', (e) => {
const controller = e.controller;
console.log(`Player ${controller.index} joined: ${controller.username}`);
// Read input in game loop
function gameLoop() {
const { joystick } = controller.state;
player.move(joystick.x, joystick.y);
requestAnimationFrame(gameLoop);
}
gameLoop();
// Or use events for button presses
controller.on('buttondown', (e) => {
if (e.input === 'a') player.jump();
});
});Controller Layout
┌─────────────────────────────────┐
│ │
│ ┌───┐ [Y] │
│ │ │ │
│ │ J │ [X] [Z] │
│ │ │ │
│ └───┘ [A] │
│ │
│ Joystick Aimable Buttons │
│ (movement) (with direction)│
└─────────────────────────────────┘All inputs share the same interface:
interface InputState {
pressed: boolean; // Is the input active
x: number; // Horizontal axis (-1 to 1)
y: number; // Vertical axis (-1 to 1)
}Events
Manager Events:
controllerconnected- New controller connectedcontrollerdisconnected- Controller disconnectedstatechange- Any controller input changedbuttondown- Button pressed on any controllerbuttonup- Button released on any controller
Controller Events:
statechange- This controller's input changedbuttondown- Button pressed (edge detected)buttonup- Button released (edge detected)
Development
# Install dependencies
pnpm install
# Build all packages
pnpm build
# Development mode
pnpm devProject Structure
ctrllr-sdk/
├── packages/
│ ├── core/ # @ctrllr/core - Base SDK
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ ├── CtrllrManager.ts
│ │ │ ├── Controller.ts
│ │ │ ├── EventEmitter.ts
│ │ │ ├── QRCode.ts
│ │ │ ├── types.ts
│ │ │ └── signaling/
│ │ │ ├── SignalingAdapter.ts
│ │ │ └── SocketIOAdapter.ts
│ │ └── package.json
│ │
│ └── react/ # @ctrllr/react (coming soon)
│
├── examples/ # Demo apps
├── package.json # Workspace root
├── pnpm-workspace.yaml
└── turbo.jsonLicense
MIT
