Feature: Spiders

This commit is contained in:
Dejvino 2025-11-16 16:46:47 +01:00
parent 2e1e6bddfa
commit 42d78e49eb
4 changed files with 140 additions and 0 deletions

View File

@ -1,5 +1,6 @@
import { DustEffect } from './dust.js';
import { FliesEffect } from './flies.js';
import { SpiderEffect } from './spider.js';
export class EffectsManager {
constructor(scene) {
@ -12,6 +13,7 @@ export class EffectsManager {
// This is now the single place to manage which effects are active.
this.addEffect(new DustEffect(scene));
this.addEffect(new FliesEffect(scene));
this.addEffect(new SpiderEffect(scene));
}
addEffect(effect) {

View File

@ -0,0 +1,131 @@
import * as THREE from 'three';
import { state } from '../state.js';
const SPIDER_COUNT = 5;
const SPIDER_SPEED = 0.0001;
const SPIDER_TURN_SPEED = 0.02;
const SPIDER_WAIT_MIN = 200; // frames
const SPIDER_WAIT_MAX = 500; // frames
export class SpiderEffect {
constructor(scene) {
this.spiders = [];
this._setupSpiders(scene);
}
_getRandomPointOnWall(wall) {
const position = new THREE.Vector3();
const width = wall.geometry.parameters.width;
const height = wall.geometry.parameters.height;
position.x = (Math.random() - 0.5) * width;
position.y = (Math.random() - 0.5) * height;
position.z = 0; // Local z is 0 for a plane
// Convert local position to world position
return wall.localToWorld(position);
}
_createSpiderMesh() {
const spiderGroup = new THREE.Group();
const spiderMaterial = new THREE.MeshPhongMaterial({ color: 0x919191, shininess: 50 });
// Body
const bodyGeometry = new THREE.SphereGeometry(0.01, 6, 5);
const body = new THREE.Mesh(bodyGeometry, spiderMaterial);
body.scale.z = 0.6; // Flatten the sphere
body.castShadow = true;
spiderGroup.add(body);
// Head
const headGeometry = new THREE.SphereGeometry(0.005, 5, 4);
const head = new THREE.Mesh(headGeometry, spiderMaterial);
head.position.y = 0.015;
head.castShadow = true;
spiderGroup.add(head);
spiderGroup.userData = {
state: 'crawling', // 'crawling', 'waiting'
waitTimer: 0,
t: 0,
curve: null,
currentWall: null,
};
return spiderGroup;
}
_findNewTarget(spider) {
if (!spider.userData.currentWall) {
// First time, pick a random wall
const walls = state.crawlSurfaces;
if (walls.length === 0) return;
spider.userData.currentWall = walls[Math.floor(Math.random() * walls.length)];
spider.position.copy(this._getRandomPointOnWall(spider.userData.currentWall));
}
const startPoint = spider.position.clone();
const endPoint = this._getRandomPointOnWall(spider.userData.currentWall);
// Create a curved path on the wall
const midPoint = new THREE.Vector3().lerpVectors(startPoint, endPoint, 0.5);
const direction = new THREE.Vector3().subVectors(endPoint, startPoint).normalize();
const wallNormal = spider.userData.currentWall.getWorldDirection(new THREE.Vector3()).negate();
// Get a perpendicular vector on the plane of the wall
const perpendicular = new THREE.Vector3().crossVectors(direction, wallNormal).normalize();
const offsetMagnitude = startPoint.distanceTo(endPoint) * (Math.random() * 0.4 - 0.2); // Random offset left or right
const controlPoint = midPoint.clone().add(perpendicular.multiplyScalar(offsetMagnitude));
spider.userData.curve = new THREE.QuadraticBezierCurve3(startPoint, controlPoint, endPoint);
spider.userData.t = 0;
spider.userData.state = 'crawling';
}
_setupSpiders(scene) {
for (let i = 0; i < SPIDER_COUNT; i++) {
const spider = this._createSpiderMesh();
scene.add(spider);
this.spiders.push(spider);
this._findNewTarget(spider); // Initial placement
}
}
update() {
this.spiders.forEach(spider => {
const data = spider.userData;
if (data.state === 'crawling') {
if (!data.curve) {
this._findNewTarget(spider);
return;
}
data.t += SPIDER_SPEED;
if (data.t >= 1) {
spider.position.copy(data.curve.v2);
data.state = 'waiting';
data.waitTimer = SPIDER_WAIT_MIN + Math.random() * (SPIDER_WAIT_MAX - SPIDER_WAIT_MIN);
} else {
spider.position.copy(data.curve.getPoint(data.t));
// Smoothly turn the spider towards the tangent of the curve
const tangent = data.curve.getTangent(data.t);
const up = data.currentWall.getWorldDirection(new THREE.Vector3()).negate();
const targetQuaternion = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), tangent).multiply(
new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 0, 1), up)
);
spider.quaternion.slerp(targetQuaternion, SPIDER_TURN_SPEED);
}
} else if (data.state === 'waiting') {
data.waitTimer--;
if (data.waitTimer <= 0) {
this._findNewTarget(spider);
}
}
});
}
}

View File

@ -18,24 +18,28 @@ export function createRoomWalls() {
const backWall = new THREE.Mesh(new THREE.PlaneGeometry(state.roomSize, state.roomHeight), wallMaterial);
backWall.position.set(0, state.roomHeight / 2, -state.roomSize / 2);
backWall.receiveShadow = true;
backWall.name = 'backWall';
state.scene.add(backWall);
// 2. Front Wall (behind the camera)
const frontWall = new THREE.Mesh(new THREE.PlaneGeometry(state.roomSize, state.roomHeight), wallMaterial);
frontWall.position.set(0, state.roomHeight / 2, state.roomSize / 2);
frontWall.rotation.y = Math.PI;
frontWall.name = 'frontWall';
frontWall.receiveShadow = true;
state.scene.add(frontWall);
// 3. Left Wall
const leftWall = new THREE.Mesh(new THREE.PlaneGeometry(state.roomSize, state.roomHeight), wallMaterial);
leftWall.rotation.y = Math.PI / 2;
leftWall.name = 'leftWall';
leftWall.position.set(-state.roomSize / 2, state.roomHeight / 2, 0);
leftWall.receiveShadow = true;
state.scene.add(leftWall);
// 4. Right Wall
const rightWall = new THREE.Mesh(new THREE.PlaneGeometry(state.roomSize, state.roomHeight), wallMaterial);
rightWall.name = 'rightWall';
rightWall.rotation.y = -Math.PI / 2;
rightWall.position.set(state.roomSize / 2, state.roomHeight / 2, 0);
rightWall.receiveShadow = true;
@ -58,6 +62,8 @@ export function createRoomWalls() {
ceiling.position.set(0, state.roomHeight, 0);
ceiling.receiveShadow = true;
state.scene.add(ceiling);
state.crawlSurfaces.push(backWall, frontWall, leftWall, rightWall);
// --- 6. Add a Window to the Back Wall ---
const windowWidth = 1.5;

View File

@ -44,6 +44,7 @@ export function initState() {
// Utilities
loader: new THREE.TextureLoader(),
landingSurfaces: [],
crawlSurfaces: [], // Surfaces for spiders to crawl on
bookLevitation: {
state: 'resting', // 'resting', 'levitating', 'returning'
timer: 0,