Files
minecraft-clone/js/player.js
T
minecraft-cloneandClaude 1049253d54 feat: Minecraft Clone MVP – prozedurale Welt, Blöcke, Abbauen/Setzen
- Weltgenerierung: FBM-Terrain, 3 Biome (Plains/Wald/Wüste), Höhlen, Wasser, Bäume
- Blöcke: Grass, Dirt, Stein, Sand, Holz, Blätter, Wasser (+Bedrock)
- Interaktion: DDA-Voxel-Raycast, Abbauen (LMB) & Setzen (RMB), Hotbar 1-6
- Spieler: First-Person, Gravitation, AABB-Kollision, Springen, Fly-Mode (F)
- Engine: Chunk-Streaming (16x16x64), Face-Culling, opaker+transparenter Pass
- Stack: Three.js + simplex-noise via CDN, prozedurale Texturen, kein Build-Step

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-20 23:55:12 +02:00

146 lines
4.8 KiB
JavaScript

// First-Person-Spieler: Bewegung, Gravitation, AABB-Kollision, Springen,
// Flug-Modus, Schwimmen. yaw/pitch steuern die Kamera (extern gesetzt).
import { isSolid, BLOCK } from './constants.js';
const HALF = 0.3; // halbe Spielerbreite (X/Z)
const HEIGHT = 1.8; // Spielerhoehe
const EYE = 1.62; // Augenposition ueber den Fuessen
const GRAVITY = 26;
const JUMP_VEL = 8.6;
const WALK = 4.7;
const SPRINT = 7.2;
const FLY_SPEED = 9.5;
const MAX_FALL = 50;
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 = 0.0022;
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; }
const speed = this.flying ? FLY_SPEED : (input.down('ControlLeft') ? 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;
}
}
}
}
}
}