create-airjam 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +11 -3
- package/package.json +6 -3
- package/templates/pong/.env.example +11 -0
- package/templates/pong/.env.local +10 -0
- package/templates/pong/AI_INSTRUCTIONS.md +44 -0
- package/templates/pong/README.md +111 -0
- package/templates/pong/airjam-docs/getting-started/architecture/page.md +165 -0
- package/templates/pong/airjam-docs/getting-started/game-ideas/page.md +114 -0
- package/templates/pong/airjam-docs/getting-started/introduction/page.md +122 -0
- package/templates/pong/airjam-docs/how-it-works/host-system/page.md +241 -0
- package/templates/pong/airjam-docs/sdk/hooks/page.md +403 -0
- package/templates/pong/airjam-docs/sdk/input-system/page.md +336 -0
- package/templates/pong/airjam-docs/sdk/networked-state/page.md +575 -0
- package/templates/pong/dist/assets/index-B9l0NKly.js +269 -0
- package/templates/pong/dist/assets/index-CHKqdIQG.css +1 -0
- package/templates/pong/dist/index.html +14 -0
- package/templates/pong/eslint.config.js +33 -0
- package/templates/pong/index.html +6 -1
- package/templates/pong/node_modules/.bin/air-jam-server +17 -0
- package/templates/pong/node_modules/.bin/eslint +17 -0
- package/templates/pong/node_modules/.bin/eslint-config-prettier +17 -0
- package/templates/pong/node_modules/.bin/jiti +17 -0
- package/templates/pong/node_modules/.bin/tsc +17 -0
- package/templates/pong/node_modules/.bin/tsserver +17 -0
- package/templates/pong/node_modules/.bin/tsx +17 -0
- package/templates/pong/node_modules/.bin/vite +17 -0
- package/templates/pong/node_modules/.vite/deps/@air-jam_sdk.js +66143 -0
- package/templates/pong/node_modules/.vite/deps/@air-jam_sdk.js.map +7 -0
- package/templates/pong/node_modules/.vite/deps/_metadata.json +73 -0
- package/templates/pong/node_modules/.vite/deps/chunk-3TUQC5ZT.js +292 -0
- package/templates/pong/node_modules/.vite/deps/chunk-3TUQC5ZT.js.map +7 -0
- package/templates/pong/node_modules/.vite/deps/chunk-DC5AMYBS.js +38 -0
- package/templates/pong/node_modules/.vite/deps/chunk-DC5AMYBS.js.map +7 -0
- package/templates/pong/node_modules/.vite/deps/chunk-QUPSG5AV.js +280 -0
- package/templates/pong/node_modules/.vite/deps/chunk-QUPSG5AV.js.map +7 -0
- package/templates/pong/node_modules/.vite/deps/chunk-TYOCAO5S.js +13810 -0
- package/templates/pong/node_modules/.vite/deps/chunk-TYOCAO5S.js.map +7 -0
- package/templates/pong/node_modules/.vite/deps/chunk-YG4BJP3V.js +1004 -0
- package/templates/pong/node_modules/.vite/deps/chunk-YG4BJP3V.js.map +7 -0
- package/templates/pong/node_modules/.vite/deps/package.json +3 -0
- package/templates/pong/node_modules/.vite/deps/react-dom.js +6 -0
- package/templates/pong/node_modules/.vite/deps/react-dom.js.map +7 -0
- package/templates/pong/node_modules/.vite/deps/react-dom_client.js +20217 -0
- package/templates/pong/node_modules/.vite/deps/react-dom_client.js.map +7 -0
- package/templates/pong/node_modules/.vite/deps/react-router-dom.js +13900 -0
- package/templates/pong/node_modules/.vite/deps/react-router-dom.js.map +7 -0
- package/templates/pong/node_modules/.vite/deps/react.js +5 -0
- package/templates/pong/node_modules/.vite/deps/react.js.map +7 -0
- package/templates/pong/node_modules/.vite/deps/react_jsx-dev-runtime.js +278 -0
- package/templates/pong/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
- package/templates/pong/node_modules/.vite/deps/react_jsx-runtime.js +6 -0
- package/templates/pong/node_modules/.vite/deps/react_jsx-runtime.js.map +7 -0
- package/templates/pong/node_modules/.vite/deps/zod.js +476 -0
- package/templates/pong/node_modules/.vite/deps/zod.js.map +7 -0
- package/templates/pong/package.json +12 -1
- package/templates/pong/src/App.tsx +2 -2
- package/templates/pong/src/controller-view.tsx +143 -0
- package/templates/pong/src/host-view.tsx +401 -0
- package/templates/pong/src/main.tsx +2 -1
- package/templates/pong/src/store.ts +80 -0
- package/templates/pong/tsconfig.json +3 -2
- package/templates/pong/vite.config.ts +3 -0
- package/templates/pong/src/ControllerView.tsx +0 -64
- package/templates/pong/src/HostView.tsx +0 -148
package/dist/index.js
CHANGED
|
@@ -49,7 +49,12 @@ async function main() {
|
|
|
49
49
|
console.log(kleur.cyan(`
|
|
50
50
|
Creating project in ${targetDir}...
|
|
51
51
|
`));
|
|
52
|
-
await fs.copy(templateDir, targetDir
|
|
52
|
+
await fs.copy(templateDir, targetDir, {
|
|
53
|
+
filter: (src) => {
|
|
54
|
+
const relativePath = path.relative(templateDir, src);
|
|
55
|
+
return !relativePath.includes("node_modules") && !relativePath.endsWith("pnpm-lock.yaml") && !relativePath.endsWith(".npmrc");
|
|
56
|
+
}
|
|
57
|
+
});
|
|
53
58
|
const pkgPath = path.join(targetDir, "package.json");
|
|
54
59
|
if (fs.existsSync(pkgPath)) {
|
|
55
60
|
const pkg = await fs.readJson(pkgPath);
|
|
@@ -59,8 +64,11 @@ Creating project in ${targetDir}...
|
|
|
59
64
|
console.log(kleur.green("\u2713 Project created successfully!\n"));
|
|
60
65
|
console.log("Next steps:\n");
|
|
61
66
|
console.log(kleur.cyan(` cd ${projectName}`));
|
|
62
|
-
console.log(kleur.cyan("
|
|
63
|
-
console.log(kleur.cyan("
|
|
67
|
+
console.log(kleur.cyan(" pnpm install"));
|
|
68
|
+
console.log(kleur.cyan(" cp env.example .env"));
|
|
69
|
+
console.log(kleur.cyan(" # Edit .env and set AIR_JAM_MASTER_KEY"));
|
|
70
|
+
console.log(kleur.cyan(" pnpm run dev:server # Terminal 1 - Start server"));
|
|
71
|
+
console.log(kleur.cyan(" pnpm run dev # Terminal 2 - Start game"));
|
|
64
72
|
console.log("");
|
|
65
73
|
console.log(
|
|
66
74
|
kleur.dim("Then open http://localhost:5173 and scan the QR code with your phone!")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-airjam",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "CLI to scaffold Air Jam game projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,8 +29,10 @@
|
|
|
29
29
|
"templates"
|
|
30
30
|
],
|
|
31
31
|
"scripts": {
|
|
32
|
-
"
|
|
33
|
-
"
|
|
32
|
+
"extract-docs": "tsx scripts/extract-docs.ts",
|
|
33
|
+
"build": "pnpm extract-docs && tsup",
|
|
34
|
+
"dev": "tsup --watch",
|
|
35
|
+
"prepublishOnly": "pnpm build"
|
|
34
36
|
},
|
|
35
37
|
"dependencies": {
|
|
36
38
|
"commander": "^14.0.0",
|
|
@@ -42,6 +44,7 @@
|
|
|
42
44
|
"@types/fs-extra": "^11.0.4",
|
|
43
45
|
"@types/prompts": "^2.4.9",
|
|
44
46
|
"tsup": "^8.5.1",
|
|
47
|
+
"tsx": "^4.19.2",
|
|
45
48
|
"typescript": "~5.9.3"
|
|
46
49
|
}
|
|
47
50
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# Game Configuration
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Server URL - defaults to localhost:4000 in development
|
|
5
|
+
# For production, set to your official Air Jam server URL: wss://api.air-jam.app
|
|
6
|
+
VITE_AIR_JAM_SERVER_URL=http://localhost:4000
|
|
7
|
+
|
|
8
|
+
# API Key - optional for local development (not required when using local server)
|
|
9
|
+
# Required for production - get from Air Jam platform
|
|
10
|
+
VITE_AIR_JAM_API_KEY=
|
|
11
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# Game Configuration
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Server URL - defaults to localhost:4000 in development
|
|
5
|
+
# For production, set to your official Air Jam server URL: wss://api.air-jam.app
|
|
6
|
+
VITE_AIR_JAM_SERVER_URL=http://localhost:4000
|
|
7
|
+
|
|
8
|
+
# API Key - optional for local development (not required when using local server)
|
|
9
|
+
# Required for production - get from Air Jam platform
|
|
10
|
+
VITE_AIR_JAM_API_KEY=aj_live_972f8d8063e5413fa23d2981906d24fe
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Universal project rules and tech stack standards
|
|
3
|
+
globs: "**/*.ts, **/*.tsx, **/*.js, **/*.jsx, **/*.json"
|
|
4
|
+
alwaysApply: true
|
|
5
|
+
---
|
|
6
|
+
# Identity & Goals
|
|
7
|
+
- You are a senior frontend architect and full-stack engineer.
|
|
8
|
+
- Focus on modern, type-safe, and functional programming patterns.
|
|
9
|
+
- Prioritize maintainability and strict type safety over brevity.
|
|
10
|
+
- Use airjam-docs for documentation on the Air Jam SDK.
|
|
11
|
+
|
|
12
|
+
# Tech Stack (Strict)
|
|
13
|
+
- **Manager**: pnpm (monorepo structure)
|
|
14
|
+
- **Framework**: React (Frontend), Vite (Build)
|
|
15
|
+
- **Language**: TypeScript (Strict mode)
|
|
16
|
+
- **Styling**: Tailwind CSS + Shadcn UI
|
|
17
|
+
- **State**: Zustand (Global)
|
|
18
|
+
|
|
19
|
+
# Coding Conventions
|
|
20
|
+
- **Naming**:
|
|
21
|
+
- `camelCase` for variables and functions.
|
|
22
|
+
- `PascalCase` for React components and Type definitions.
|
|
23
|
+
- `kebab-case` for all file names (e.g., `user-profile-card.tsx`).
|
|
24
|
+
- **Component Syntax**: Use `const Component = (props: Props) => {}`. Avoid `React.FC`.
|
|
25
|
+
- **Exports**: Use named exports. Do NOT use default exports.
|
|
26
|
+
- **Direct Access**: Do NOT pass props (prop drilling) if data is available via Zustand store or React Context.
|
|
27
|
+
|
|
28
|
+
# Type Safety & Linting & State Management
|
|
29
|
+
- **Zod First**: Define schema validation with Zod before implementing logic.
|
|
30
|
+
- **No Any**: `any` are strictly forbidden. Use generic types or specific interfaces.
|
|
31
|
+
- **TSC**: Code must pass `tsc` check without errors.
|
|
32
|
+
- **JSDoc**: Use JSDoc for utility functions to describe behavior (omit @param/@returns unless complex).
|
|
33
|
+
- **Zustand**: Use atomic selectors when consuming state to prevent unnecessary re-renders.
|
|
34
|
+
|
|
35
|
+
# File Structure & Refactoring
|
|
36
|
+
- **Co-location**:
|
|
37
|
+
- If a component is used only once, keep it in the same file as the parent.
|
|
38
|
+
- If a component is used >1 time, extract to `components/` folder.
|
|
39
|
+
- **Shared Code**: All shared schemas/types must reside in the `packages/shared` workspace.
|
|
40
|
+
|
|
41
|
+
# Agent Behavior
|
|
42
|
+
- **Dependencies**: Always use `pnpm add` to install new packages.
|
|
43
|
+
- **Verification**: Verify file paths before creation. Ensure imports match the `kebab-case` filename convention.
|
|
44
|
+
- **Cleanliness**: Remove unused imports and dead code immediately; do not comment them out.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Air Jam Game Template - Pong
|
|
2
|
+
|
|
3
|
+
A starter template for building multiplayer games with Air Jam. This template includes a simple Pong game that demonstrates the core Air Jam features.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
### Installation
|
|
8
|
+
|
|
9
|
+
1. Install dependencies:
|
|
10
|
+
```bash
|
|
11
|
+
pnpm install
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
2. Configure environment variables (optional - defaults work for local dev):
|
|
15
|
+
```bash
|
|
16
|
+
mv .env.example .env.local
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Then edit `.env.local` with your settings if needed.
|
|
20
|
+
|
|
21
|
+
## Local Development
|
|
22
|
+
|
|
23
|
+
### Option 1: Run Server and Game Separately (Recommended)
|
|
24
|
+
|
|
25
|
+
**Terminal 1 - Start the local server:**
|
|
26
|
+
```bash
|
|
27
|
+
pnpm run dev:server
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The server will start on `http://localhost:4000` in development mode (no authentication required).
|
|
31
|
+
|
|
32
|
+
**Terminal 2 - Start the game:**
|
|
33
|
+
```bash
|
|
34
|
+
pnpm run dev
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The game will be available at `http://localhost:5173` (or the port Vite assigns).
|
|
38
|
+
|
|
39
|
+
### Option 2: Use the Official Air Jam Server
|
|
40
|
+
|
|
41
|
+
If you prefer to use the official Air Jam server instead of running locally:
|
|
42
|
+
|
|
43
|
+
1. Get your API key from the [Air Jam Platform](https://air-jam.app)
|
|
44
|
+
2. Set in `.env.local`:
|
|
45
|
+
```bash
|
|
46
|
+
VITE_AIR_JAM_SERVER_URL=https://api.air-jam.app
|
|
47
|
+
VITE_AIR_JAM_API_KEY=your-api-key-here
|
|
48
|
+
```
|
|
49
|
+
3. Run only the game:
|
|
50
|
+
```bash
|
|
51
|
+
pnpm run dev
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Playing the Game
|
|
55
|
+
|
|
56
|
+
1. Open the game URL in your browser (host view)
|
|
57
|
+
2. Scan the QR code with your phone to open the controller / open the controller view in your browser
|
|
58
|
+
3. Use the controller to play Pong!
|
|
59
|
+
|
|
60
|
+
## Project Structure
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
src/
|
|
64
|
+
├── App.tsx # Main app component with routing
|
|
65
|
+
├── host-view.tsx # Game host view (runs the game)
|
|
66
|
+
├── controller-view.tsx # Controller view (mobile interface)
|
|
67
|
+
├── types.ts # Game input schema and types
|
|
68
|
+
├── store.ts # Shared networked state store
|
|
69
|
+
└── main.tsx # Entry point
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Environment Variables
|
|
73
|
+
|
|
74
|
+
See `.env.example` for all available environment variables. Key variables:
|
|
75
|
+
|
|
76
|
+
- `VITE_AIR_JAM_SERVER_URL` - Server URL (defaults to localhost:4000 for local dev)
|
|
77
|
+
- `VITE_AIR_JAM_API_KEY` - API key (optional for local dev, required for production)
|
|
78
|
+
|
|
79
|
+
## Production Deployment
|
|
80
|
+
|
|
81
|
+
### Deploying to Vercel
|
|
82
|
+
|
|
83
|
+
1. Set environment variables in Vercel:
|
|
84
|
+
- `VITE_AIR_JAM_SERVER_URL` - Your official Air Jam server URL
|
|
85
|
+
- `VITE_AIR_JAM_API_KEY` - Your API key from the Air Jam platform
|
|
86
|
+
|
|
87
|
+
2. Deploy:
|
|
88
|
+
```bash
|
|
89
|
+
vercel
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The game will connect to the official Air Jam server - no local server needed!
|
|
93
|
+
|
|
94
|
+
### Building for Production
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
pnpm run build
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The built files will be in the `dist/` directory.
|
|
101
|
+
|
|
102
|
+
## Learn More
|
|
103
|
+
|
|
104
|
+
- [Air Jam Documentation](https://air-jam.app/docs)
|
|
105
|
+
- [Platform](https://air-jam.app)
|
|
106
|
+
- [Examples](https://github.com/vucinatim/air-jam/tree/main/apps)
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
MIT
|
|
111
|
+
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
Air Jam consists of four main components in a monorepo structure. This page explains how they work together to enable multiplayer gaming with smartphone controllers.
|
|
4
|
+
|
|
5
|
+
## System Overview
|
|
6
|
+
|
|
7
|
+
## Components
|
|
8
|
+
|
|
9
|
+
### 1. Platform (`apps/platform`)
|
|
10
|
+
|
|
11
|
+
**Role:** Central hub for the Air Jam ecosystem
|
|
12
|
+
|
|
13
|
+
**Technology:** Next.js 15, TypeScript, tRPC, BetterAuth, PostgreSQL (Drizzle ORM)
|
|
14
|
+
|
|
15
|
+
**Key Features:**
|
|
16
|
+
- **Developer Portal** - Account management, API key generation, analytics
|
|
17
|
+
- **Game Catalog** - Submit, manage, and discover Air Jam games
|
|
18
|
+
- **Arcade Mode** - Browse and launch games from a unified interface
|
|
19
|
+
- **Controller Shell** - Persistent mobile wrapper that loads game controllers
|
|
20
|
+
|
|
21
|
+
**Arcade Mode Flow:**
|
|
22
|
+
```
|
|
23
|
+
1. Player scans QR on arcade screen
|
|
24
|
+
2. Platform loads controller shell on phone
|
|
25
|
+
3. Player browses games using phone as remote
|
|
26
|
+
4. Game launches → controller switches to game's joypad
|
|
27
|
+
5. Game ends → returns to arcade browser
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 2. Server (`packages/server`)
|
|
31
|
+
|
|
32
|
+
**Role:** Real-time communication backbone
|
|
33
|
+
|
|
34
|
+
**Technology:** Node.js, Express, Socket.IO, PostgreSQL
|
|
35
|
+
|
|
36
|
+
**Core Services:**
|
|
37
|
+
|
|
38
|
+
**Socket Events:**
|
|
39
|
+
|
|
40
|
+
### 3. SDK (`packages/sdk`)
|
|
41
|
+
|
|
42
|
+
**Role:** Developer toolkit for building Air Jam games
|
|
43
|
+
|
|
44
|
+
**Technology:** React, TypeScript, Socket.IO Client, Zustand, Zod
|
|
45
|
+
|
|
46
|
+
**Architecture:**
|
|
47
|
+
|
|
48
|
+
**Key Design Decisions:**
|
|
49
|
+
|
|
50
|
+
1. **Provider Pattern** - Single provider for configuration, multiple hooks for access
|
|
51
|
+
2. **Lightweight Hooks** - `useGetInput`, `useSendSignal` don't trigger re-renders
|
|
52
|
+
3. **Input Latching** - Ensures rapid button taps are never missed
|
|
53
|
+
4. **Schema Validation** - Type-safe input with runtime validation
|
|
54
|
+
|
|
55
|
+
### 4. Prototype Game (`apps/prototype-game`)
|
|
56
|
+
|
|
57
|
+
**Role:** Reference implementation showcasing SDK capabilities
|
|
58
|
+
|
|
59
|
+
**Technology:** React, Vite, React Three Fiber, Rapier Physics
|
|
60
|
+
|
|
61
|
+
**Demonstrates:**
|
|
62
|
+
- Host-side game logic with physics
|
|
63
|
+
- Controller UI with joystick and buttons
|
|
64
|
+
- Player spawning/despawning on join/leave
|
|
65
|
+
- Haptic feedback on collisions
|
|
66
|
+
- Multiple game modes (CTF, survival)
|
|
67
|
+
|
|
68
|
+
## Run Modes
|
|
69
|
+
|
|
70
|
+
The SDK automatically detects and adapts to different deployment scenarios:
|
|
71
|
+
|
|
72
|
+
### Standalone Mode
|
|
73
|
+
|
|
74
|
+
Your game runs independently with direct WebSocket connection.
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
78
|
+
│ Your Game │◀───▶│ AirJam │◀───▶│ Controller │
|
|
79
|
+
│ (Host) │ │ Server │ │ (Phone) │
|
|
80
|
+
└──────────────┘ └──────────────┘ └──────────────┘
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Use case:** Self-hosted games, development, custom deployments
|
|
84
|
+
|
|
85
|
+
### Arcade Mode
|
|
86
|
+
|
|
87
|
+
Game runs inside an iframe on the Air Jam Platform.
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
┌─────────────────────────────────────────┐
|
|
91
|
+
│ Air Jam Platform (Parent) │
|
|
92
|
+
│ ┌───────────────────────────────────┐ │
|
|
93
|
+
│ │ Your Game (iframe) │ │
|
|
94
|
+
│ │ • Receives join token │ │
|
|
95
|
+
│ │ • Controlled player routing │ │
|
|
96
|
+
│ └───────────────────────────────────┘ │
|
|
97
|
+
└─────────────────────────────────────────┘
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Use case:** Featured on Air Jam arcade, game discovery
|
|
101
|
+
|
|
102
|
+
### Bridge Mode
|
|
103
|
+
|
|
104
|
+
Controller runs inside platform's shell (iframe communication).
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
┌─────────────────────────────────────────┐
|
|
108
|
+
│ Platform Controller Shell (Parent) │
|
|
109
|
+
│ ┌───────────────────────────────────┐ │
|
|
110
|
+
│ │ Your Controller UI (iframe) │ │
|
|
111
|
+
│ │ • Input via postMessage │ │
|
|
112
|
+
│ │ • Seamless game switching │ │
|
|
113
|
+
│ └───────────────────────────────────┘ │
|
|
114
|
+
└─────────────────────────────────────────┘
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Use case:** Arcade mode controllers, persistent session
|
|
118
|
+
|
|
119
|
+
## Data Flow
|
|
120
|
+
|
|
121
|
+
### Input Path (Controller → Game)
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
1. Player moves joystick on phone
|
|
125
|
+
2. Controller calls sendInput({ vector: {x, y}, action: false })
|
|
126
|
+
3. SDK sends controller:input event to server
|
|
127
|
+
4. Server routes to host socket
|
|
128
|
+
5. Host SDK's InputManager receives input
|
|
129
|
+
6. InputManager validates with Zod schema
|
|
130
|
+
7. InputManager applies latching if configured
|
|
131
|
+
8. Game calls getInput(playerId) in game loop
|
|
132
|
+
9. Returns typed, validated, latched input
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Signal Path (Game → Controller)
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
1. Game detects collision
|
|
139
|
+
2. Host calls sendSignal("HAPTIC", { pattern: "heavy" }, playerId)
|
|
140
|
+
3. SDK sends host:signal event to server
|
|
141
|
+
4. Server routes to target controller(s)
|
|
142
|
+
5. Controller SDK receives signal
|
|
143
|
+
6. SDK triggers navigator.vibrate() with pattern
|
|
144
|
+
7. Player feels haptic feedback
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Security
|
|
148
|
+
|
|
149
|
+
### API Key Authentication
|
|
150
|
+
|
|
151
|
+
- Games must provide valid API key for production
|
|
152
|
+
- Keys are validated against platform database
|
|
153
|
+
- Rate limiting prevents abuse
|
|
154
|
+
|
|
155
|
+
### Room Isolation
|
|
156
|
+
|
|
157
|
+
- Each room has unique 4-character code
|
|
158
|
+
- Controllers can only join with correct code
|
|
159
|
+
- Input is only routed to the designated host
|
|
160
|
+
|
|
161
|
+
### Input Validation
|
|
162
|
+
|
|
163
|
+
- Zod schemas validate all incoming input
|
|
164
|
+
- Invalid input is rejected with console warning
|
|
165
|
+
- Protects game logic from malformed data
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Game Ideas for Air Jam
|
|
2
|
+
|
|
3
|
+
A collection of game concepts that leverage Air Jam's unique architecture: **High-Frequency Input Streams** (Socket.IO) + **Reliable Shared State** (AirJam Store).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Tier 1: Arcade Classics (Easy)
|
|
8
|
+
|
|
9
|
+
### Neon Tanks
|
|
10
|
+
|
|
11
|
+
- Top-down arena shooter (4-8 players)
|
|
12
|
+
- Dual stick controls: movement + turret aiming
|
|
13
|
+
- Tap to fire
|
|
14
|
+
- **Key Feature:** Demonstrates dual stick capability on touchscreen
|
|
15
|
+
|
|
16
|
+
### Super Sumo Balls
|
|
17
|
+
|
|
18
|
+
- Rolling balls on tilting platform
|
|
19
|
+
- Use phone gyroscope/accelerometer to tilt
|
|
20
|
+
- Haptic feedback on collisions
|
|
21
|
+
- **Key Feature:** Phone sensors + haptic signals
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Tier 2: Party & Social Games (Medium)
|
|
26
|
+
|
|
27
|
+
### The Traitor (Among Us style)
|
|
28
|
+
|
|
29
|
+
- 8 players: 6 crew, 2 traitors
|
|
30
|
+
- Hidden roles shown only on phone
|
|
31
|
+
- Task minigames on controller
|
|
32
|
+
- Voting UI on phone
|
|
33
|
+
- **Key Feature:** Secret information per player (killer app for phone controllers)
|
|
34
|
+
|
|
35
|
+
### Sketchy Business (Pictionary style)
|
|
36
|
+
|
|
37
|
+
- Draw prompts on phone canvas
|
|
38
|
+
- Drawings appear on TV
|
|
39
|
+
- **Key Feature:** Touchscreen as natural drawing pad
|
|
40
|
+
|
|
41
|
+
### Quiz Show Royale
|
|
42
|
+
|
|
43
|
+
- Fastest finger first trivia
|
|
44
|
+
- Personalized feedback (green/red + vibration)
|
|
45
|
+
- **Key Feature:** Input timestamping for speed
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Tier 3: Co-op Chaos (Medium-Hard)
|
|
50
|
+
|
|
51
|
+
### Starship Bridge Simulator
|
|
52
|
+
|
|
53
|
+
- 4 players with different roles
|
|
54
|
+
- Each player gets different UI on phone
|
|
55
|
+
- Asymmetrical gameplay
|
|
56
|
+
- **Key Feature:** Different React components per controller
|
|
57
|
+
|
|
58
|
+
### Ghost Hunter
|
|
59
|
+
|
|
60
|
+
- 1 vs 3 asymmetrical gameplay
|
|
61
|
+
- Ghost sees minimap on phone only
|
|
62
|
+
- Hunters use joysticks on TV
|
|
63
|
+
- **Key Feature:** Private screen advantage
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Tier 4: Strategy & Card Games (Hard)
|
|
68
|
+
|
|
69
|
+
### Poker Night / Blackjack
|
|
70
|
+
|
|
71
|
+
- Private cards on phone
|
|
72
|
+
- Betting UI on phone
|
|
73
|
+
- Public table on TV
|
|
74
|
+
- **Key Feature:** Hidden information per player
|
|
75
|
+
|
|
76
|
+
### Artillery Wars (Worms style)
|
|
77
|
+
|
|
78
|
+
- Turn-based tank battle
|
|
79
|
+
- Aiming interface on phone (angle/power sliders)
|
|
80
|
+
- Weapon selection on phone
|
|
81
|
+
- **Key Feature:** Complex menus on touchscreen
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Tier 5: Experimental (Advanced)
|
|
86
|
+
|
|
87
|
+
### The Crowd Pixel (r/place style)
|
|
88
|
+
|
|
89
|
+
- 50+ players
|
|
90
|
+
- Each controls a pixel/cursor
|
|
91
|
+
- Collaborative painting
|
|
92
|
+
- **Key Feature:** Tests scalability
|
|
93
|
+
|
|
94
|
+
### Minority Report UI
|
|
95
|
+
|
|
96
|
+
- Phone as trackpad
|
|
97
|
+
- Gesture controls
|
|
98
|
+
- **Key Feature:** Phone as input device
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Key Mechanics Enabled
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
| Mechanic | Example | Technical Feature |
|
|
106
|
+
|-------------------|-------------------|-----------------------------------------|
|
|
107
|
+
| Secret Roles | Mafia / Traitor | `useSharedState` (filter by `playerId`) |
|
|
108
|
+
| Private Inventory | RPG / Poker | Shared Store + React UI on Phone |
|
|
109
|
+
| Motion Steering | Racing / Rolling | Device Accelerometer → Input Stream |
|
|
110
|
+
| Fastest Finger | Quiz Show | Input Latching (Timestamps) |
|
|
111
|
+
| Physical Feedback | Fighting / Action | `host.sendSignal("HAPTIC")` |
|
|
112
|
+
| Drawing | Pictionary | Canvas API + Data Packet |
|
|
113
|
+
| Asymmetry | Ghost Hunter | Different Views per Role |
|
|
114
|
+
```
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Introduction
|
|
2
|
+
|
|
3
|
+
Air Jam is a platform for building **"AirConsole-style" multiplayer games** where a computer/TV acts as the host display and smartphones become game controllers. The platform enables developers to create interactive games with minimal setup while providing players with an intuitive, scan-and-play experience.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
|
|
7
|
+
- **Zero App Download**: Players join by scanning a QR code—no app store required
|
|
8
|
+
- **Instant Multiplayer**: Seamlessly connect up to 8 smartphones as controllers
|
|
9
|
+
- **Developer Friendly**: Built with modern web technologies (React, TypeScript)
|
|
10
|
+
- **Type Safe**: End-to-end type safety with Zod schema validation
|
|
11
|
+
- **Performance Optimized**: Latching system ensures no input is ever missed
|
|
12
|
+
- **Haptic Feedback**: Send vibration patterns to controllers for game events
|
|
13
|
+
|
|
14
|
+
## How It Works
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
### 1. Install the SDK
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm add @air-jam/sdk
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### 2. Wrap Your App
|
|
25
|
+
|
|
26
|
+
```tsx filename="src/App.tsx"
|
|
27
|
+
import { AirJamProvider } from "@air-jam/sdk";
|
|
28
|
+
import { z } from "zod";
|
|
29
|
+
|
|
30
|
+
// Define your input schema
|
|
31
|
+
const gameInputSchema = z.object({
|
|
32
|
+
vector: z.object({ x: z.number(), y: z.number() }),
|
|
33
|
+
action: z.boolean(),
|
|
34
|
+
ability: z.boolean(),
|
|
35
|
+
timestamp: z.number(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const App = () => (
|
|
39
|
+
<AirJamProvider
|
|
40
|
+
input={{
|
|
41
|
+
schema: gameInputSchema,
|
|
42
|
+
latch: {
|
|
43
|
+
booleanFields: ["action", "ability"],
|
|
44
|
+
vectorFields: ["vector"],
|
|
45
|
+
},
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
<Routes>
|
|
49
|
+
<Route path="/" element={<HostView />} />
|
|
50
|
+
<Route path="/joypad" element={<ControllerView />} />
|
|
51
|
+
</Routes>
|
|
52
|
+
</AirJamProvider>
|
|
53
|
+
);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 3. Create Your Host View
|
|
57
|
+
|
|
58
|
+
```tsx filename="src/components/HostView.tsx"
|
|
59
|
+
import { useAirJamHost } from "@air-jam/sdk";
|
|
60
|
+
import { QRCode } from "./QRCode";
|
|
61
|
+
|
|
62
|
+
const HostView = () => {
|
|
63
|
+
const host = useAirJamHost({
|
|
64
|
+
onPlayerJoin: (player) => {
|
|
65
|
+
console.log(`${player.label} joined!`);
|
|
66
|
+
spawnPlayer(player.id, player.color);
|
|
67
|
+
},
|
|
68
|
+
onPlayerLeave: (id) => {
|
|
69
|
+
console.log(`Player ${id} left`);
|
|
70
|
+
removePlayer(id);
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div>
|
|
76
|
+
<h1>Room: {host.roomId}</h1>
|
|
77
|
+
<QRCode value={host.joinUrl} />
|
|
78
|
+
<p>Players: {host.players.length}</p>
|
|
79
|
+
|
|
80
|
+
{/* Your game canvas */}
|
|
81
|
+
<GameCanvas players={host.players} getInput={host.getInput} />
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 4. Create Your Controller View
|
|
88
|
+
|
|
89
|
+
```tsx filename="src/components/ControllerView.tsx"
|
|
90
|
+
import { useAirJamController } from "@air-jam/sdk";
|
|
91
|
+
import { Joystick, Button } from "./ui";
|
|
92
|
+
|
|
93
|
+
const ControllerView = () => {
|
|
94
|
+
const controller = useAirJamController();
|
|
95
|
+
|
|
96
|
+
const handleInput = (vector: { x: number; y: number }, action: boolean) => {
|
|
97
|
+
controller.sendInput({
|
|
98
|
+
vector,
|
|
99
|
+
action,
|
|
100
|
+
ability: false,
|
|
101
|
+
timestamp: Date.now(),
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
if (controller.connectionStatus !== "connected") {
|
|
106
|
+
return <div>Connecting to room {controller.roomId}...</div>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<div>
|
|
111
|
+
<Joystick onMove={(x, y) => handleInput({ x, y }, false)} />
|
|
112
|
+
<Button onPress={() => handleInput({ x: 0, y: 0 }, true)}>Fire</Button>
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Next Steps
|
|
119
|
+
|
|
120
|
+
- [Architecture](/docs/getting-started/architecture) - Understand the system design
|
|
121
|
+
- [Hooks Reference](/docs/sdk/hooks) - Complete API documentation
|
|
122
|
+
- [Input System](/docs/sdk/input-system) - Learn about latching and validation
|