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>
90 lines
3.9 KiB
JavaScript
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;
|
|
}
|
|
}
|