// 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; } }