- panel.js: Drawer-Panel (P / ⚙), schema-getrieben, ●live/⟳reload-Badges - Werte direkt im Panel einstellbar statt in config.js grabben - Live-Apply über mutateNested (nur Plain-Objekte, nie Modul-Namespace) + registerApply-Callbacks für Three.js-Objekte (FOV, Belichtung, Fog, Licht, Schatten-Frustum, Bloom) - PLAYER.*/CONTROLS.* live (player.js liest jeden Frame neu) - AO jetzt live: debounced world.markAllDirty() remesht alle Chunks ohne F5 - Reload-Pflichtige Werte persistiert + blinkender Neu-Starten-Button (Seed, Meereshöhe, Spawn, MSAA, Tone-Mapping, Schatten an/Größe, AO-Level) - Persistenz: localStorage (debounced), config.js lädt beim Start - HUD/Overlay ausgeblendet, solange das Panel offen ist (Welt frei sichtbar) - Buttons: Neu starten, Auf Standard zurücksetzen, Werte kopieren (JSON) Co-Authored-By: Claude <noreply@anthropic.com>
147 lines
5.2 KiB
JavaScript
147 lines
5.2 KiB
JavaScript
// First-Person-Spieler: Bewegung, Gravitation, AABB-Kollision, Springen,
|
||
// Flug-Modus, Schwimmen. yaw/pitch steuern die Kamera (extern gesetzt).
|
||
//
|
||
// WICHTIG: PLAYER- und CONTROLS-Werte werden LIVE aus config.js gelesen
|
||
// (jeder Frame), nicht beim Laden destrukturiert. So kann das Settings-
|
||
// Panel Geschwindigkeit/Gravitation/Maus-Empfindlichkeit usw. zur
|
||
// Laufzeit ändern.
|
||
import { isSolid, BLOCK } from './constants.js';
|
||
import { PLAYER, CONTROLS } from './config.js';
|
||
|
||
export class Player {
|
||
constructor(world) {
|
||
this.world = world;
|
||
this.pos = new Float32Array(3); // x,y,z der Fuesse-Mittelpunkt
|
||
this.vel = new Float32Array(3);
|
||
this.onGround = false;
|
||
this.flying = false;
|
||
this.yaw = 0;
|
||
this.pitch = 0;
|
||
}
|
||
|
||
setSpawn(x, y, z) {
|
||
this.pos[0] = x; this.pos[1] = y; this.pos[2] = z;
|
||
this.vel[0] = this.vel[1] = this.vel[2] = 0;
|
||
}
|
||
|
||
eyePosition(out) {
|
||
out.set(this.pos[0], this.pos[1] + PLAYER.EYE, this.pos[2]);
|
||
return out;
|
||
}
|
||
|
||
inWater() {
|
||
// Pruefe den Block auf Augen- und Fuss-Hoehe
|
||
const ex = Math.floor(this.pos[0]);
|
||
const ey = Math.floor(this.pos[1] + PLAYER.EYE * 0.6);
|
||
const ez = Math.floor(this.pos[2]);
|
||
return this.world.getBlock(ex, ey, ez) === BLOCK.WATER;
|
||
}
|
||
|
||
update(input, dt) {
|
||
// --- Maus-Look (Empfindlichkeit live aus config) ---
|
||
const sens = CONTROLS.MOUSE_SENSITIVITY;
|
||
this.yaw -= input.mouseDX * sens;
|
||
this.pitch -= input.mouseDY * sens;
|
||
const lim = Math.PI / 2 - 0.001;
|
||
if (this.pitch > lim) this.pitch = lim;
|
||
if (this.pitch < -lim) this.pitch = -lim;
|
||
|
||
// --- Flug-Toggle ---
|
||
if (input.keys.has('KeyF') && !this._fHeld) {
|
||
this._fHeld = true;
|
||
this.flying = !this.flying;
|
||
this.vel[1] = 0;
|
||
}
|
||
if (!input.keys.has('KeyF')) this._fHeld = false;
|
||
|
||
// --- Wunschrichtung im XZ-Plane (yaw=0 -> Blickrichtung ist -Z) ---
|
||
const fwdX = -Math.sin(this.yaw);
|
||
const fwdZ = -Math.cos(this.yaw);
|
||
const rightX = -fwdZ; // = cos(yaw)
|
||
const rightZ = fwdX; // = -sin(yaw)
|
||
let mx = 0, mz = 0;
|
||
if (input.down('KeyW')) { mx += fwdX; mz += fwdZ; }
|
||
if (input.down('KeyS')) { mx -= fwdX; mz -= fwdZ; }
|
||
if (input.down('KeyD')) { mx += rightX; mz += rightZ; }
|
||
if (input.down('KeyA')) { mx -= rightX; mz -= rightZ; }
|
||
const len = Math.hypot(mx, mz);
|
||
if (len > 0) { mx /= len; mz /= len; }
|
||
|
||
// Sprint: Shift (Ctrl ist im Browser unbequem – Browser-Shortcuts).
|
||
// ShiftLeft/Right dient im Fly-Modus gleichzeitig als "Runter" – kein
|
||
// Konflikt, da Sprint nur beim Gehen greift (Fly nutzt konstante FLY_SPEED).
|
||
const sprint = input.down('ShiftLeft') || input.down('ShiftRight');
|
||
const speed = this.flying ? PLAYER.FLY_SPEED : (sprint ? PLAYER.SPRINT : PLAYER.WALK);
|
||
|
||
if (this.flying) {
|
||
this.vel[0] = mx * speed;
|
||
this.vel[2] = mz * speed;
|
||
let vy = 0;
|
||
if (input.down('Space')) vy += 1;
|
||
if (input.down('ShiftLeft')) vy -= 1;
|
||
this.vel[1] = vy * speed;
|
||
} else {
|
||
const water = this.inWater();
|
||
const move = water ? speed * 0.5 : speed;
|
||
this.vel[0] = mx * move;
|
||
this.vel[2] = mz * move;
|
||
// Gravitation / Schwimmen
|
||
if (water) {
|
||
this.vel[1] -= PLAYER.GRAVITY * 0.28 * dt;
|
||
if (this.vel[1] < -6) this.vel[1] = -6;
|
||
if (input.down('Space')) this.vel[1] = 4.0; // nach oben schwimmen
|
||
} else {
|
||
this.vel[1] -= PLAYER.GRAVITY * dt;
|
||
if (this.vel[1] < -PLAYER.MAX_FALL) this.vel[1] = -PLAYER.MAX_FALL;
|
||
if (input.down('Space') && this.onGround) {
|
||
this.vel[1] = PLAYER.JUMP_VEL;
|
||
this.onGround = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- Bewegung mit per-Achsen-Kollision ---
|
||
this.onGround = false;
|
||
this._moveAxis(0, this.vel[0] * dt);
|
||
this._moveAxis(2, this.vel[2] * dt);
|
||
this._moveAxis(1, this.vel[1] * dt);
|
||
}
|
||
|
||
// axis: 0=x,1=y,2=z ; amount = Verschiebung auf dieser Achse
|
||
_moveAxis(axis, amount) {
|
||
this.pos[axis] += amount;
|
||
if (amount === 0) return;
|
||
|
||
const HALF = PLAYER.HALF, HEIGHT = PLAYER.HEIGHT;
|
||
const x = this.pos[0], y = this.pos[1], z = this.pos[2];
|
||
const minX = Math.floor(x - HALF), maxX = Math.floor(x + HALF);
|
||
const minY = Math.floor(y), maxY = Math.floor(y + HEIGHT - 0.001);
|
||
const minZ = Math.floor(z - HALF), maxZ = Math.floor(z + HALF);
|
||
|
||
for (let bx = minX; bx <= maxX; bx++) {
|
||
for (let by = minY; by <= maxY; by++) {
|
||
for (let bz = minZ; bz <= maxZ; bz++) {
|
||
if (!isSolid(this.world.getBlock(bx, by, bz))) continue;
|
||
if (axis === 0) {
|
||
if (amount > 0) this.pos[0] = Math.min(this.pos[0], bx - HALF - 1e-4);
|
||
else this.pos[0] = Math.max(this.pos[0], bx + 1 + HALF + 1e-4);
|
||
this.vel[0] = 0;
|
||
} else if (axis === 2) {
|
||
if (amount > 0) this.pos[2] = Math.min(this.pos[2], bz - HALF - 1e-4);
|
||
else this.pos[2] = Math.max(this.pos[2], bz + 1 + HALF + 1e-4);
|
||
this.vel[2] = 0;
|
||
} else {
|
||
if (amount > 0) { // nach oben gegen Decke
|
||
this.pos[1] = Math.min(this.pos[1], by - HEIGHT - 1e-4);
|
||
} else { // gelandet
|
||
this.pos[1] = Math.max(this.pos[1], by + 1 + 1e-4);
|
||
this.onGround = true;
|
||
}
|
||
this.vel[1] = 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|