8.9 KiB
8.9 KiB
Mobile Tag – Projektplan
GPS-basiertes Outdoor-Lasertag für jedes Smartphone als Progressive Web App (PWA).
1. Konzept & Spielprinzip
- Plattform: Webanwendung (PWA), installierbar auf iOS/Android, keine App-Store-Downloads nötig.
- Spielort: Überall im Freien. Das Spielfeld wird vor dem Match definiert.
- Treffermodell: Hybrid aus GPS/Kompass (Grob-Check) und Kamera-Computer-Vision (Fein-Check).
- GPS + Kompass prüfen, ob ein Gegner in Reichweite und Blickrichtung liegt.
- Die Kamera zeigt ein Fadenkreuz über dem Live-Bild.
- TensorFlow.js (MoveNet) erkennt Körperteile. Das Fadenkreuz entscheidet über Kopf, Brust, Arm oder Bein.
- Jeder Körperteil verursacht unterschiedlichen Schaden.
- Nachlade-Mechanik: Kein Dauerfeuer. Cooldown einstellbar, Standard ~3 s.
- Spielmodi: Free-for-All und Teams.
- Spieleranzahl: 2–10 Spieler pro Match (MVP).
2. Architektur
┌─────────────────┐ ┌──────────────────────┐
│ Client PWA │◄───────►│ Node.js Game Server │
│ React + Vite │ WebSocket│ Express + Socket.io │
└─────────────────┘ └──────────────────────┘
│
▼
Handy-Sensoren:
- GPS (Geolocation API)
- Kompass (DeviceOrientationEvent)
- Kamera (getUserMedia)
- TensorFlow.js MoveNet (Pose Estimation)
3. Tech-Stack
| Schicht | Technologie |
|---|---|
| Frontend | React 18 + Vite + TypeScript |
| PWA | vite-plugin-pwa (Workbox) |
| Styling | Tailwind CSS |
| Geo-Logik | Turf.js |
| Kamera | getUserMedia + <video> + Canvas/SVG-Overlay |
| Computer Vision | TensorFlow.js + MoveNet (@tensorflow-models/pose-detection) |
| Backend | Node.js + Express + Socket.io |
| Hosting Frontend | Vercel / Netlify |
| Hosting Backend | Render / Railway / Fly.io |
4. Projektstruktur
Mobil_Tag/
├── README.md
├── PLAN.md
├── package.json
├── client/ # Frontend PWA
│ ├── package.json
│ ├── vite.config.ts
│ ├── tsconfig.json
│ ├── index.html
│ ├── public/
│ │ ├── manifest.webmanifest
│ │ └── icons/
│ └── src/
│ ├── main.tsx
│ ├── App.tsx
│ ├── components/
│ │ ├── CameraView.tsx
│ │ ├── Crosshair.tsx
│ │ ├── HUD.tsx
│ │ ├── Lobby.tsx
│ │ ├── Radar.tsx
│ │ └── Scoreboard.tsx
│ ├── hooks/
│ │ ├── useGeolocation.ts
│ │ ├── useDeviceOrientation.ts
│ │ ├── useCamera.ts
│ │ ├── usePoseDetection.ts
│ │ └── useSocket.ts
│ ├── game/
│ │ ├── targeting.ts
│ │ ├── bodyParts.ts
│ │ └── geo.ts
│ ├── store/
│ │ └── gameStore.ts
│ ├── types/
│ │ └── game.ts
│ └── styles/
│ └── index.css
└── server/ # Game Server
├── package.json
├── tsconfig.json
└── src/
├── index.ts
├── rooms.ts
├── game.ts
├── geo.ts
└── types.ts
5. Datenmodell
type BodyPart = 'head' | 'chest' | 'arm' | 'leg';
interface BodyPartDamage {
head: number; // 100
chest: number; // 40
leg: number; // 25
arm: number; // 15
}
interface Player {
id: string;
name: string;
team?: 'A' | 'B';
position: { lat: number; lng: number };
heading: number; // 0-359 Grad
health: number; // 0-100
alive: boolean;
cooldownUntil: number; // Timestamp
score: number;
kills: number;
deaths: number;
}
interface Match {
id: string;
code: string; // 6-stelliger Raumcode
mode: 'ffa' | 'teams';
status: 'lobby' | 'running' | 'ended';
boundary: Circle | Polygon;
settings: MatchSettings;
players: Map<string, Player>;
startTime: number;
endTime?: number;
}
interface MatchSettings {
maxPlayers: number; // 2-10
reloadMs: number; // Standard 3000
rangeM: number; // Standard 20
angleTolerance: number; // Standard 5
damageByBodyPart: BodyPartDamage;
respawnMs: number; // Standard 5000
matchDurationMs: number; // Standard 600000
startHealth: number; // Standard 100
powerSaveDimMs: number; // Standard 10000
powerSaveSleepMs: number; // Standard 30000
}
interface Shot {
shooterId: string;
timestamp: number;
shooterPos: { lat: number; lng: number };
heading: number;
bodyPart: BodyPart;
}
interface HitEvent {
shooterId: string;
shooterName?: string;
targetId: string;
targetName?: string;
bodyPart: BodyPart;
damage: number;
targetHealth: number;
}
6. Trefferlogik
6.1 Zwei-Stufen-Validierung
-
Grob-Check (GPS + Kompass):
- Gegner innerhalb
rangeM? - Gegner innerhalb
±angleToleranceder Blickrichtung? - Gegner am Leben?
- Nicht im selben Team?
- Cooldown abgelaufen?
- Gegner innerhalb
-
Fein-Check (Kamera-CV):
- MoveNet erkennt mindestens eine Person im Kamerabild.
- Das Fadenkreuz liegt innerhalb der erkannten Körperregion.
- Bestimmtes Körperteil wird ermittelt.
6.2 Body-Part-Mapping (MoveNet)
MoveNet liefert 17 Keypoints. Daraus werden Regionen gebildet:
| Körperteil | Keypoints | Schaden |
|---|---|---|
| Kopf | nose, eyes, ears | 100 |
| Brust | shoulders, hips | 40 |
| Arm | elbows, wrists | 15 |
| Bein | knees, ankles | 25 |
6.3 Power-Saving
Während des Spiels dimmt sich der Bildschirm nach powerSaveDimMs (Standard 10 s) und geht nach powerSaveSleepMs (Standard 30 s) in den Tiefschlaf. Kamera und MoveNet werden pausiert, GPS und Kompass laufen weiter. Ein Touch reaktiviert den aktiven Modus sofort.
6.4 Server-Validierung
Der Server empfängt shot und validiert:
- Spieler am Leben und Cooldown abgelaufen.
- GPS-Plausibilität (keine unrealistischen Positionssprünge).
- Gegner innerhalb Reichweite + Blickkegel.
bodyPartist gültig.- Trefferziel ist der GPS-nächste Gegner in Blickrichtung (Konsistenzcheck).
Bei Treffer: Schaden anwenden, Score aktualisieren, hit-Event broadcasten.
7. Spielablauf
Lobby (Code teilen) → Spieler joinen → Host konfiguriert Match →
Spielstart → 10 Hz Positions-Updates → Clients zeigen Visier →
Spieler feuert → Server validiert → Hit/Miss → Respawn →
Spielende (Timer oder letzter Spieler) → Scoreboard
8. Wichtige Socket.io-Events
| Event | Richtung | Beschreibung |
|---|---|---|
room:create |
C→S | Raum erstellen |
room:created |
S→C | Raumcode zurück |
room:join |
C→S | Raum beitreten |
room:state |
S→C | Aktueller Raumzustand |
match:start |
C→S | Spiel starten (Host) |
match:started |
S→C | Spiel gestartet |
player:update |
C→S | Position + Heading (10 Hz) |
state:sync |
S→C | Spieler-Positionen broadcast |
player:shoot |
C→S | Schussabgabe mit BodyPart |
player:hit |
S→C | Treffer mit Körperteil + Schaden |
player:death |
S→C | Spieler ausgeschaltet |
player:respawn |
S→C | Spieler wiederbelebt |
match:end |
S→C | Match beendet + Scoreboard |
9. PWA-Setup
- Manifest mit Name, Icons,
display: fullscreen, Theme. - Service Worker via
vite-plugin-pwa. - Install-Prompt auf Home-Screen.
- HTTPS erforderlich (Kamera, Geolocation, Orientation).
10. Risiken & Mitigation
| Risiko | Mitigation |
|---|---|
| GPS-Ungenauigkeit | Glättung, großzügige Toleranzen, serverseitige Plausibilität |
| Kompass-Kalibrierung | Anleitung, Live-Heading-Anzeige |
| Akkuverbrauch | Bildschirm an lassen, einstellbare Update-Raten |
| iOS-Safari-Quirks | HTTPS, Permission-Requests, fallbacks |
| Kamera-CV-Performance | MoveNet Lightning, WebGL-Backend |
| Netz im Freien | Reconnect-Logik, Positionscache |
| GPS-Spoofing | Server-Plausibilität, Casual-Play akzeptabel |
11. Implementierungsphasen
- Phase 1 – Scaffolding: Projektstruktur, Vite + React + TS, Tailwind, Socket.io-Server, TensorFlow.js.
- Phase 2 – Geo & Orientierung: GPS-Tracking, Kompass, Turf.js.
- Phase 3 – Backend & Lobby: Räume, Socket.io-Events, Match-State.
- Phase 4 – Kamera-CV & Visier: MoveNet, Body-Part-Erkennung, Fadenkreuz.
- Phase 5 – Trefferlogik & Gameplay: Server-autoritative Treffervalidierung, Schaden, Respawn, Score.
- Phase 6 – Spielablauf & UI: Lobby, Settings, Spielgebiet, Timer, Scoreboard.
- Phase 7 – PWA & Polish: Manifest, Service Worker, Permissions, Performance, Power-Saving, Hit-Feed.
Stand: 26.07.2026