programmierhub/public/js/controls.js
Stefan Franke 502131f07c VR ProgrammierHub — begehbarer 3D-Lernraum für hybride Programmier-Lehre
Erste lauffähige Fassung: 3D-Raum, Echtzeit-Multiplayer (WebSocket),
Python-IDE via Pyodide im Browser, eigener Arbeitsplatz mit Live-Code-Monitor.
Modularer, self-hostbarer Aufbau (Three.js + Node/Express + ws), ohne CDN-
Abhängigkeit zur Laufzeit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 13:35:11 +00:00

90 lines
3.9 KiB
JavaScript

// controls.js — Desktop-Steuerung: WASD-Bewegung des Player-Rigs + Maus-Blick.
//
// Umsehen läuft über Pointer Lock — das Web-Äquivalent zum "relative mouse mode",
// den native Spiele (SDL) nutzen. Klick fängt die Maus, danach dreht die Maus die
// Blickrichtung frei, auch während WASD gehalten wird. Esc gibt die Maus frei.
//
// Bewusst KEIN Drag-Look (Taste halten): das erzeugte auf Touchpads ständig neue
// Berührungen und lief so in libinputs "disable-while-typing" (nur *neu startende*
// Berührungen werden beim Tippen unterdrückt). Bei gefangener Maus gleitet der Finger
// durchgehend — die Berührung ist bereits aktiv und bleibt erlaubt, exakt wie im Spiel.
//
// Reine Tastatur-Alternative zum Drehen: Pfeiltasten links/rechts (ohne Maus/Touchpad).
import * as THREE from 'three';
const BOUND = 28; // Raumgrenzen (halbe Bodengröße minus Rand)
const WALK = 4.5, RUN = 9; // m/s
const LOOK = 0.0022; // Maus-Empfindlichkeit
const TURN = 1.6; // Pfeiltasten-Drehung rad/s
export class Controls {
constructor(camera, player, domElement) {
this.camera = camera;
this.player = player;
this.dom = domElement;
this.keys = {};
this.enabled = true; // pausierbar, z. B. während die IDE offen ist
this.locked = false;
this._euler = new THREE.Euler(0, 0, 0, 'YXZ');
this._fwd = new THREE.Vector3();
this._right = new THREE.Vector3();
this._up = new THREE.Vector3(0, 1, 0);
this._world = new THREE.Vector3();
addEventListener('keydown', (e) => { this.keys[e.code] = true; });
addEventListener('keyup', (e) => { this.keys[e.code] = false; });
// Klick fängt die Maus (auch erneut, nachdem Esc sie freigegeben hat) — außer die Steuerung ist pausiert (IDE offen).
domElement.addEventListener('click', () => { if (!this.locked && this.enabled) this.lock(); });
document.addEventListener('pointerlockchange', () => { this.locked = document.pointerLockElement === domElement; });
document.addEventListener('mousemove', (e) => this._onMouseMove(e));
}
lock() { if (this.dom.requestPointerLock) { try { this.dom.requestPointerLock(); } catch { /* Browser lehnt ab; Pfeiltasten bleiben als Fallback */ } } }
/** Steuerung an-/ausschalten (z. B. während die IDE offen ist). */
setEnabled(v) { this.enabled = v; if (!v) this.keys = {}; }
_onMouseMove(e) {
if (!this.locked || !this.enabled) return;
const eu = this._euler;
eu.setFromQuaternion(this.camera.quaternion);
eu.y -= e.movementX * LOOK;
eu.x -= e.movementY * LOOK;
eu.x = Math.max(-Math.PI / 2 + 0.05, Math.min(Math.PI / 2 - 0.05, eu.x));
this.camera.quaternion.setFromEuler(eu);
}
/** Aktuelle horizontale Blickrichtung als Yaw-Winkel (für Netzwerk-Sync). */
get yaw() {
this.camera.getWorldDirection(this._world);
return Math.atan2(-this._world.x, -this._world.z);
}
/** Pro Frame aufrufen. @returns {boolean} ob sich der Player gerade bewegt. */
update(dt) {
if (!this.enabled) return false;
const k = this.keys;
if (k['ArrowLeft']) this.player.rotation.y += TURN * dt;
if (k['ArrowRight']) this.player.rotation.y -= TURN * dt;
const moving = k['KeyW'] || k['KeyS'] || k['KeyA'] || k['KeyD'];
if (moving) {
const speed = (k['ShiftLeft'] || k['ShiftRight']) ? RUN : WALK;
this.camera.getWorldDirection(this._fwd);
this._fwd.y = 0;
if (this._fwd.lengthSq() < 1e-6) this._fwd.set(0, 0, -1);
this._fwd.normalize();
this._right.crossVectors(this._fwd, this._up).normalize();
const step = speed * dt;
const p = this.player.position;
if (k['KeyW']) p.addScaledVector(this._fwd, step);
if (k['KeyS']) p.addScaledVector(this._fwd, -step);
if (k['KeyD']) p.addScaledVector(this._right, step);
if (k['KeyA']) p.addScaledVector(this._right, -step);
p.x = Math.max(-BOUND, Math.min(BOUND, p.x));
p.z = Math.max(-BOUND, Math.min(BOUND, p.z));
}
return moving;
}
}