A real-time, browser-native first-person shooter built entirely with Next.js, React Three Fiber, and a Bun WebSocket server. No Unity, no game engine downloads — runs in any modern browser tab.
| Layer | Technology |
|---|---|
| Frontend Framework | Next.js 16 (App Router) |
| 3D Engine | Three.js + React Three Fiber (@react-three/fiber) |
| 3D Helpers | @react-three/drei (Sky, PointerLockControls, Text) |
| Game Server | Bun WebSocket server (server.ts) |
| Binary Protocol | msgpackr (efficient binary encoding) |
| Styling | Tailwind CSS v4 |
| Language | TypeScript (strict) |
┌───────────────────────────────────────────────────────────┐
│ Browser Tab (Next.js / React Three Fiber) │
│ │
│ ┌──────────────┐ WebSocket (binary/msgpack) ┌───────┐ │
│ │ GameCanvas │ ◄────────────────────────── │ Bun │ │
│ │ (R3F Scene) │ ──── InputData frame ──────► │ WS │ │
│ └──────────────┘ │ 8080 │ │
│ └───┬───┘ │
└────────────────────────────────────────────────────┼──────┘
│
┌──────────▼──────────┐
│ SarsMatchManager │
│ ┌───────────────┐ │
│ │ Human Players │ │
│ │ Bot Players │ │
│ └───────────────┘ │
│ SarsPhysicsEngine │
│ ┌───────────────┐ │
│ │ Cylinder AABB │ │
│ │ 2D Hitscan │ │
│ └───────────────┘ │
└─────────────────────┘
-
Client → Server (30 Hz): Each browser tab sends a packed
InputDataframe every render tick viauseFrame:{ w, a, s, d: boolean, rotY: number, shoot: boolean }
-
Server → Client (30 Hz): The Bun server broadcasts a packed
ServerFrameto thesars-matchpub/sub channel:{ players: Player[], shots: [ox, oz, dx, dz][] }
-
Bot AI (30 Hz): Bots seek the nearest player, rotate toward them, walk forward, and fire probabilistically.
sars/
├── app/
│ ├── layout.tsx # Root layout with Tailwind, fonts, suppressHydrationWarning
│ ├── page.tsx # Home page — nav bar + GameCanvas
│ └── play/
│ └── page.tsx # (Legacy Unity bridge page — unused)
│
├── components/
│ └── GameCanvas.tsx # Full R3F game scene, HUD, bullet traces, network
│
├── server/
│ ├── engine.ts # SarsPhysicsEngine — cylinder collision + hitscan
│ └── game-state.ts # SarsMatchManager — players, bots, input processing
│
├── server.ts # Bun WebSocket server entry point
├── next.config.ts # transpilePackages for Three.js ESM
└── package.json
npm installnpm run dev:allThis uses concurrently to start:
- Next.js dev server on
http://localhost:3000 - Bun game server on
ws://localhost:8080
Or run them separately in two terminals:
# Terminal 1 — Next.js frontend
npm run dev
# Terminal 2 — Bun game server
npm run dev:server- Open
http://localhost:3000in your browser - Click the canvas to lock the cursor
- WASD to move, mouse to aim, left-click to shoot
Multi-tab: Open multiple tabs — each generates a new unique player. Bots automatically fill the remaining slots up to 8 total.
| Mechanic | Detail |
|---|---|
| Max players | 8 (bots fill empty slots) |
| Player shape | Cylinder (radius 0.8, height 2.0) |
| Damage per hit | 20 HP |
| Respawn | Instant at a random position |
| Scoring | +1 per kill |
| Bullet traces | Yellow streak fades over 300ms |
| Bot AI | Seek nearest -> rotate -> walk -> shoot (3% chance/tick) |
Checks XZ-plane circle overlap and Y-axis interval overlap for cylindrical bounding boxes.
PLAYER_RADIUS = 0.8
PLAYER_HEIGHT = 2.0
XZ: sqrt((x2-x1)^2 + (z2-z1)^2) < RADIUS * 2
Y: pos1.y < pos2.y + HEIGHT && pos1.y + HEIGHT > pos2.y
Projects a 2D ray on the XZ plane and checks if the perpendicular distance from the target center to the ray is within PLAYER_RADIUS.
dir = [sin(rotY), cos(rotY)]
perpDist = |cross(toTarget, dir)| (2D cross product magnitude)
hit = dot(toTarget, dir) > 0 && perpDist <= PLAYER_RADIUS
This is a proof-of-concept prototype. The following are not yet implemented:
- No authentication — player IDs are anonymous UUIDs
- No lag compensation — server authority only; fast clients feel input lag
- No anti-cheat — all inputs are trusted
- No persistent scores — state lives in memory; restarting the server resets everything
- Bot pathfinding — bots walk in straight lines (no obstacle avoidance)
- Single arena — no map selection or rotation
- WebSocket only — no HTTPS/WSS config for production
- Lag compensation (client-side prediction + server reconciliation)
- Map editor / multiple arenas
- Weapon variety (rifle, shotgun, sniper)
- Persistent leaderboard (Postgres / Redis)
- WSS + HTTPS for production deployment
- Better bot AI (pathfinding around cover boxes)
- Sound effects (Web Audio API)
- Mobile touch controls
MIT — prototype, use freely.