Files

8.9 KiB
Raw Permalink Blame History

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: 210 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

  1. Grob-Check (GPS + Kompass):

    • Gegner innerhalb rangeM?
    • Gegner innerhalb ±angleTolerance der Blickrichtung?
    • Gegner am Leben?
    • Nicht im selben Team?
    • Cooldown abgelaufen?
  2. 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.
  • bodyPart ist 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

  1. Phase 1 Scaffolding: Projektstruktur, Vite + React + TS, Tailwind, Socket.io-Server, TensorFlow.js.
  2. Phase 2 Geo & Orientierung: GPS-Tracking, Kompass, Turf.js.
  3. Phase 3 Backend & Lobby: Räume, Socket.io-Events, Match-State.
  4. Phase 4 Kamera-CV & Visier: MoveNet, Body-Part-Erkennung, Fadenkreuz.
  5. Phase 5 Trefferlogik & Gameplay: Server-autoritative Treffervalidierung, Schaden, Respawn, Score.
  6. Phase 6 Spielablauf & UI: Lobby, Settings, Spielgebiet, Timer, Scoreboard.
  7. Phase 7 PWA & Polish: Manifest, Service Worker, Permissions, Performance, Power-Saving, Hit-Feed.

Stand: 26.07.2026