// First-Person-Spieler: Bewegung, Gravitation, AABB-Kollision, Springen, // Flug-Modus, Schwimmen. yaw/pitch steuern die Kamera (extern gesetzt). import { isSolid, BLOCK } from './constants.js'; import { PLAYER, MOUSE_SENSITIVITY } from './config.js'; // Werte aus config.js (PLAYER). Lokal gebunden, damit die Verwendungen kurz bleiben. const { HALF, HEIGHT, EYE, GRAVITY, JUMP_VEL, WALK, SPRINT, FLY_SPEED, MAX_FALL } = PLAYER; 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] + 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] + EYE * 0.6); const ez = Math.floor(this.pos[2]); return this.world.getBlock(ex, ey, ez) === BLOCK.WATER; } update(input, dt) { // --- Maus-Look --- const sens = 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 ? FLY_SPEED : (sprint ? SPRINT : 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] -= 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] -= GRAVITY * dt; if (this.vel[1] < -MAX_FALL) this.vel[1] = -MAX_FALL; if (input.down('Space') && this.onGround) { this.vel[1] = 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 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; } } } } } }