Documents WebSocket protocol, architecture, calibration math, and gameplay. Co-authored-by: Cursor <cursoragent@cursor.com>
7.4 KiB
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 (getestet mit 4.6.x)
- Ein WebSocket-Server auf
localhost:9090, Endpoint/ws - Nachrichtenformat (JSON, ca. alle 16 ms):
{"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
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)
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:
- Welche Achse (
x,yoderz) am stärksten auf horizontale Neigung reagiert, wenn der Nutzer dem Bildschirmanimation folgt. - Welches Vorzeichen „nach rechts neigen“ auf dem Bildschirm bedeutet.
- Ruhewert (
center) der gewählten Achse (Median aller Kalibrierungs-Samples). - 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
- WAITING – Verbindung zu
ws://localhost:9090/ws(optional manuell starten). - 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.
- LEFT (4,5 s) – Gleiche Animation 140 px nach links vom Zentrum; Nutzer neigt nach links.
- DONE –
AccelCalibration.analyze(); bei Erfolg Signalcalibration_finished→ Spielstart.
Visuelle Animation (calibration_overlay.gd):
- Position des Kreises:
viewport_center + guide_offset guide_offset.x = ±TRAVEL_PX * smoothstep(t)mitt ∈ [0, 1]überPHASE_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
# 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):
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 ( |
| 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 durchplatform_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.