Add README with project overview and calibration technical docs.
Documents WebSocket protocol, architecture, calibration math, and gameplay. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
bc25226a31
commit
84b1a89f75
199
README.md
Normal file
199
README.md
Normal file
@ -0,0 +1,199 @@
|
||||
# alox_test_game
|
||||
|
||||
Ein einfaches 2D-Pong-Spiel in **Godot 4.6**, gesteuert über Beschleunigungsdaten eines externen Geräts per **WebSocket**. Die Plattform unten wird horizontal bewegt; ein Ball prallt an Wänden, am Boden und an der Plattform ab.
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- [Godot Engine 4.6](https://godotengine.org/) (getestet mit 4.6.x)
|
||||
- Ein WebSocket-Server auf `localhost:9090`, Endpoint `/ws`
|
||||
- Nachrichtenformat (JSON, ca. alle 16 ms):
|
||||
|
||||
```json
|
||||
{"type":"accel","t":1779991116528186251,"success":true,"x":6772,"y":-2675,"z":14704}
|
||||
```
|
||||
|
||||
Nur Nachrichten mit `"type": "accel"` und `"success": true` werden ausgewertet. Die Werte `x`, `y`, `z` sind ganzzahlige Rohwerte des Sensors (keine SI-Einheiten im Spiel).
|
||||
|
||||
## Start
|
||||
|
||||
```bash
|
||||
godot --path /pfad/zu/alox-test-game
|
||||
```
|
||||
|
||||
Beim Start läuft zuerst die **Kalibrierung**, danach das Spiel. Über **„Kalibrierung neu starten“** (oben rechts im Spiel) oder **„Kalibrierung starten“** (im Kalibrierungs-Overlay) kann die Kalibrierung jederzeit wiederholt werden.
|
||||
|
||||
## Projektstruktur
|
||||
|
||||
| Pfad | Rolle |
|
||||
|------|--------|
|
||||
| `scenes/main.tscn` | Hauptszene (UI, Plattform, Ball, Kalibrierungs-Overlay) |
|
||||
| `scripts/main.gd` | WebSocket, Spielzustand, Plattformsteuerung, Ball-Update |
|
||||
| `scripts/ball.gd` | Ballphysik (Wände, Boden, Plattformkollision) |
|
||||
| `scripts/calibration_overlay.gd` | Kalibrierungs-UI und Phasenablauf |
|
||||
| `scripts/accel_calibration.gd` | Auswertung der Messdaten → Achse, Vorzeichen, Schwellwert |
|
||||
| `project.godot` | Projekteinstellungen, Viewport 1152×648 |
|
||||
|
||||
## Architektur (Überblick)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
WS[WebSocket Server :9090] -->|JSON accel| Main[main.gd]
|
||||
Main -->|Roh-Vector3i| CalOverlay[calibration_overlay.gd]
|
||||
CalOverlay -->|Samples L/R| AccelCal[AccelCalibration]
|
||||
AccelCal -->|axis sign center threshold| Main
|
||||
Main -->|effektive Steuerung| Platform[Plattform]
|
||||
Main --> Ball[ball.gd]
|
||||
```
|
||||
|
||||
**Zustände in `main.gd`:**
|
||||
|
||||
- `CALIBRATION` – Overlay sichtbar, Plattform/Ball/Slider ausgeblendet, Messwerte werden nur für die Kalibrierung gesammelt.
|
||||
- `PLAYING` – normales Pong mit kalibrierter Achse und einstellbaren Slidern.
|
||||
|
||||
---
|
||||
|
||||
## Kalibrierung (technisch)
|
||||
|
||||
### Ziel
|
||||
|
||||
Das Gerät kann beliebig gehalten werden; Gravitation und Sensorausrichtung liefern oft große, konstante Anteile auf **x**, **y** und **z**. Die Kalibrierung ermittelt automatisch:
|
||||
|
||||
1. **Welche Achse** (`x`, `y` oder `z`) am stärksten auf *horizontale* Neigung reagiert, wenn der Nutzer dem Bildschirmanimation folgt.
|
||||
2. **Welches Vorzeichen** „nach rechts neigen“ auf dem Bildschirm bedeutet.
|
||||
3. **Ruhewert (`center`)** der gewählten Achse (Median aller Kalibrierungs-Samples).
|
||||
4. **Vorgeschlagenen Schwellwert** für die Deadzone im Spiel (aus Streuung der Samples).
|
||||
|
||||
Ohne Kalibrierung wäre feste Nutzung von `accel.x` oft falsch oder würde in Ruhe dauerhaft eine Seitenrichtung erzeugen.
|
||||
|
||||
### Ablauf für den Nutzer
|
||||
|
||||
1. **WAITING** – Verbindung zu `ws://localhost:9090/ws` (optional manuell starten).
|
||||
2. **RIGHT** (4,5 s) – Ein Kreis mit Nadel (Strich nach oben) bewegt sich mit Smoothstep von der Bildschirmmitte **140 px nach rechts**. Der Nutzer neigt das Gerät passend nach rechts.
|
||||
3. **LEFT** (4,5 s) – Gleiche Animation **140 px nach links** vom Zentrum; Nutzer neigt nach links.
|
||||
4. **DONE** – `AccelCalibration.analyze()`; bei Erfolg Signal `calibration_finished` → Spielstart.
|
||||
|
||||
Visuelle Animation (`calibration_overlay.gd`):
|
||||
|
||||
- Position des Kreises: `viewport_center + guide_offset`
|
||||
- `guide_offset.x = ±TRAVEL_PX * smoothstep(t)` mit `t ∈ [0, 1]` über `PHASE_DURATION`
|
||||
- Smoothstep: `t² * (3 - 2t)` (kein lineares Ruckeln)
|
||||
|
||||
Während RIGHT/LEFT ruft `main.gd` bei jedem empfangenen Accel-Paket `feed_accel(_accel)` auf; Samples landen in getrennten Arrays.
|
||||
|
||||
### Datensammlung
|
||||
|
||||
```gdscript
|
||||
# accel_calibration.gd
|
||||
record_right(accel) → _samples_right
|
||||
record_left(accel) → _samples_left
|
||||
```
|
||||
|
||||
Mindestens **20 Samples pro Phase** (`is_ready()`), typisch deutlich mehr bei ~60 Hz und 4,5 s Laufzeit.
|
||||
|
||||
### Auswertung (`analyze()`)
|
||||
|
||||
**Schritt 1 – Mittelwerte pro Phase**
|
||||
|
||||
\[
|
||||
\bar{a}_{\text{right}} = \frac{1}{N_r}\sum_{i} a_i,\quad
|
||||
\bar{a}_{\text{left}} = \frac{1}{N_l}\sum_{i} a_i
|
||||
\]
|
||||
|
||||
mit \(a_i = (x_i, y_i, z_i)\) als `Vector3`.
|
||||
|
||||
**Schritt 2 – Differenzvektor**
|
||||
|
||||
\[
|
||||
\Delta = \bar{a}_{\text{right}} - \bar{a}_{\text{left}}
|
||||
\]
|
||||
|
||||
Die Achse mit dem **größten Betrag** in \(\Delta\) wird gewählt:
|
||||
|
||||
\[
|
||||
\text{axis} = \arg\max_{c \in \{x,y,z\}} |\Delta_c|
|
||||
\]
|
||||
|
||||
**Schritt 3 – Vorzeichen**
|
||||
|
||||
\[
|
||||
\text{sign} = \begin{cases}
|
||||
+1 & \text{if } \Delta_{\text{axis}} \geq 0 \\
|
||||
-1 & \text{if } \Delta_{\text{axis}} < 0
|
||||
\end{cases}
|
||||
\]
|
||||
|
||||
Damit „rechts neigen“ im Kalibrierungsrecht zu **positiver** Projektion auf die Steuerachse wird.
|
||||
|
||||
**Schritt 4 – Projektion (kalibrierter Skalar)**
|
||||
|
||||
Für jeden Rohvektor \(a\):
|
||||
|
||||
\[
|
||||
p(a) = \text{sign} \cdot a_{\text{axis}}
|
||||
\]
|
||||
|
||||
Implementiert als `project(accel)`.
|
||||
|
||||
**Schritt 5 – Ruhewert `center`**
|
||||
|
||||
Alle Samples aus RIGHT- und LEFT-Phase werden projiziert, sortiert; **`center` = Median** dieser Werte. Der Median ist robuster gegen Ausreißer als der Mittelwert, wenn der Nutzer in einer Phase ungleichmäßig neigt.
|
||||
|
||||
**Schritt 6 – Schwellwert-Vorschlag**
|
||||
|
||||
Für jede Projektion \(p_i\):
|
||||
|
||||
\[
|
||||
d_i = |p_i - \text{center}|
|
||||
\]
|
||||
|
||||
Median von \(d_i\) → `spread`; vorgeschlagener Schwellwert:
|
||||
|
||||
\[
|
||||
\text{threshold} = \mathrm{clamp}(\text{spread} \cdot 1.35,\ 200,\ 12000)
|
||||
\]
|
||||
|
||||
Der Faktor 1.35 lässt etwas Spielraum über die typische Ruhestreuung; Grenzen verhindern extremes Verhalten.
|
||||
|
||||
### Nutzung im Spiel
|
||||
|
||||
Nach der Kalibrierung in `_effective_accel()` (`main.gd`):
|
||||
|
||||
```gdscript
|
||||
raw = calibration.project(_accel) # p(a)
|
||||
centered = raw - calibration.center
|
||||
if abs(centered) <= threshold_slider:
|
||||
return 0.0 # Deadzone
|
||||
return sign(centered) * (abs(centered) - threshold)
|
||||
```
|
||||
|
||||
Die Plattformgeschwindigkeit folgt dem effektiven Wert mit Skalierung über den Slider **Bewegungsstärke** (1–100 % → Faktor 0.01–1.0 auf die Zielgeschwindigkeit), Reibung in Ruhe, Begrenzung an den Bildschirmrändern.
|
||||
|
||||
### Grenzen und Annahmen
|
||||
|
||||
| Thema | Verhalten |
|
||||
|--------|-----------|
|
||||
| Nur eine Steuerachse | Es wird genau **eine** von x/y/z genutzt; kombinierte Neigung auf mehreren Achsen wird nicht modelliert. |
|
||||
| Korrelation statt Regression | Achsenwahl über \(|\Delta_x|, |\Delta_y|, |\Delta_z|\), keine Kovarianzmatrix / PCA. |
|
||||
| Symmetrische Phasen | Rechts- und Links-Phase gleich lang; Nutzer soll in beiden Phasen ähnlich stark neigen. |
|
||||
| Keine Persistenz | Kalibrierung gilt nur bis Neustart der Anwendung (nicht in Datei gespeichert). |
|
||||
| WebSocket optional in WAITING | „Kalibrierung starten“ ohne Live-Daten liefert keine sinnvollen Samples → `analyze()` schlägt fehl (< 20 Samples). |
|
||||
|
||||
### Kalibrierung neu starten
|
||||
|
||||
`main.gd` → `_restart_calibration()`:
|
||||
|
||||
- Zustand zurück auf `CALIBRATION`
|
||||
- Spiel-UI ausblenden, `calibration_overlay.start()`
|
||||
- Bei offenem WebSocket automatisch `notify_connected()` → Phasen starten
|
||||
|
||||
---
|
||||
|
||||
## Spielmechanik (Kurz)
|
||||
|
||||
- **Plattform:** horizontale Geschwindigkeit aus kalibriertem Accel (siehe oben).
|
||||
- **Ball** (`ball.gd`): konstante Geschwindigkeit, Reflexion an linken/rechten/oben Wänden und am **unteren Spielfeldrand** (Viewport-Unterkante minus Rand). Kollision mit der **Oberseite** der Plattform; seitlicher Schwung durch `platform_vx * 0.45`.
|
||||
- **Slider:** Schwellwert (Deadzone auf kalibrierter Achse), Bewegungsstärke (Empfindlichkeit).
|
||||
|
||||
## Lizenz / Hinweise
|
||||
|
||||
Privates Testprojekt; Server-Implementierung für `localhost:9090` ist nicht Teil dieses Repositories.
|
||||
Loading…
x
Reference in New Issue
Block a user