139 lines
5.0 KiB
JavaScript
139 lines
5.0 KiB
JavaScript
import * as THREE from 'three';
|
|
import { PointerLockControls } from 'three/examples/jsm/controls/PointerLockControls.js';
|
|
|
|
export class Player {
|
|
constructor(camera, colliders) {
|
|
this.camera = camera;
|
|
this.colliders = colliders;
|
|
|
|
// Player stats
|
|
this.speed = 5.0;
|
|
this.height = 1.7; // Eyes height
|
|
|
|
// Init controls
|
|
this.controls = new PointerLockControls(camera, document.body);
|
|
|
|
// Movement state
|
|
this.moveForward = false;
|
|
this.moveBackward = false;
|
|
this.moveLeft = false;
|
|
this.moveRight = false;
|
|
this.velocity = new THREE.Vector3();
|
|
this.direction = new THREE.Vector3();
|
|
this.flashlightOn = true; // Started as ON
|
|
|
|
this.setupInput();
|
|
this.setupFlashlight();
|
|
}
|
|
|
|
setupFlashlight() {
|
|
this.flashlight = new THREE.SpotLight(0xffffff, 10);
|
|
this.flashlight.angle = Math.PI / 6;
|
|
this.flashlight.penumbra = 0.3;
|
|
this.flashlight.decay = 2;
|
|
this.flashlight.distance = 15;
|
|
|
|
this.camera.add(this.flashlight);
|
|
this.flashlight.position.set(0, 0, 0);
|
|
this.flashlight.target.position.set(0, 0, -1);
|
|
this.camera.add(this.flashlight.target);
|
|
}
|
|
|
|
setupInput() {
|
|
const onKeyDown = (event) => {
|
|
switch (event.code) {
|
|
case 'KeyW': this.moveForward = true; break;
|
|
case 'KeyA': this.moveLeft = true; break;
|
|
case 'KeyS': this.moveBackward = true; break;
|
|
case 'KeyD': this.moveRight = true; break;
|
|
case 'KeyF': this.toggleFlashlight(); break;
|
|
}
|
|
};
|
|
|
|
const onKeyUp = (event) => {
|
|
switch (event.code) {
|
|
case 'KeyW': this.moveForward = false; break;
|
|
case 'KeyA': this.moveLeft = false; break;
|
|
case 'KeyS': this.moveBackward = false; break;
|
|
case 'KeyD': this.moveRight = false; break;
|
|
}
|
|
};
|
|
|
|
document.addEventListener('keydown', onKeyDown);
|
|
document.addEventListener('keyup', onKeyUp);
|
|
}
|
|
|
|
toggleFlashlight() {
|
|
if (!this.controls.isLocked) return; // Only toggle when game is active
|
|
this.flashlightOn = !this.flashlightOn;
|
|
if (this.flashlight) {
|
|
this.flashlight.visible = this.flashlightOn;
|
|
}
|
|
}
|
|
|
|
lockControls() {
|
|
this.controls.lock();
|
|
}
|
|
|
|
getObject() {
|
|
return this.controls.getObject();
|
|
}
|
|
|
|
update(dt) {
|
|
if (!this.controls.isLocked) return;
|
|
|
|
// Friction-like dampening
|
|
// Simple direct velocity for now
|
|
this.velocity.x = 0;
|
|
this.velocity.z = 0;
|
|
|
|
this.direction.z = Number(this.moveForward) - Number(this.moveBackward);
|
|
this.direction.x = Number(this.moveRight) - Number(this.moveLeft);
|
|
this.direction.normalize(); // Ensure consistent speed in all directions
|
|
|
|
if (this.moveForward || this.moveBackward) this.velocity.z -= this.direction.z * this.speed * dt;
|
|
if (this.moveLeft || this.moveRight) this.velocity.x -= this.direction.x * this.speed * dt;
|
|
|
|
// Apply movement
|
|
this.controls.moveRight(-this.velocity.x);
|
|
this.controls.moveForward(-this.velocity.z);
|
|
|
|
// Simple Collision: Push back
|
|
const playerPos = this.controls.getObject().position;
|
|
const playerRadius = 0.5; // approximated radius
|
|
|
|
for (const collider of this.colliders) {
|
|
// Assume colliders are BoxGeometry meshes aligned with axes for now
|
|
const box = new THREE.Box3().setFromObject(collider);
|
|
|
|
// Check if player is inside the box (expanded by radius)
|
|
// We only check X/Z for walls
|
|
if (playerPos.x > box.min.x - playerRadius && playerPos.x < box.max.x + playerRadius &&
|
|
playerPos.z > box.min.z - playerRadius && playerPos.z < box.max.z + playerRadius) {
|
|
|
|
// Very simple resolution: determine closest edge and push out
|
|
// This is a naive implementation but works for static orthogonal walls
|
|
const dx1 = Math.abs(playerPos.x - (box.min.x - playerRadius));
|
|
const dx2 = Math.abs(playerPos.x - (box.max.x + playerRadius));
|
|
const dz1 = Math.abs(playerPos.z - (box.min.z - playerRadius));
|
|
const dz2 = Math.abs(playerPos.z - (box.max.z + playerRadius));
|
|
|
|
const minOverlap = Math.min(dx1, dx2, dz1, dz2);
|
|
|
|
if (minOverlap === dx1) playerPos.x = box.min.x - playerRadius;
|
|
else if (minOverlap === dx2) playerPos.x = box.max.x + playerRadius;
|
|
else if (minOverlap === dz1) playerPos.z = box.min.z - playerRadius;
|
|
else if (minOverlap === dz2) playerPos.z = box.max.z + playerRadius;
|
|
}
|
|
}
|
|
|
|
// Keep player on ground
|
|
playerPos.y = this.height;
|
|
|
|
// Flashlight flicker effect (subtle)
|
|
if (this.flashlight && this.flashlightOn) {
|
|
this.flashlight.intensity = 10 + Math.random() * 0.5;
|
|
}
|
|
}
|
|
}
|