Files
Wackelpeter/web/src/WebPatch.ts
T

106 lines
2.8 KiB
TypeScript

import * as THREE from 'three';
import type { Game } from './Game';
const ROOT_DURATION = 0.35;
const ROOT_TICK = 1.0;
const ROOT_COOLDOWN = 1.0;
const FADE_TIME = 0.6;
export class WebPatch {
body: THREE.Group;
private radius: number;
private life: number;
private rootTick = 0;
private meshes: THREE.Mesh[] = [];
constructor(position: THREE.Vector3, radius = 3, duration = 6) {
this.radius = radius;
this.life = duration;
this.body = new THREE.Group();
this.body.position.set(position.x, 0.08, position.z);
const mat = new THREE.MeshToonMaterial({
color: 0xdfe6ff,
transparent: true,
opacity: 0.55,
depthWrite: false,
});
const disc = new THREE.Mesh(new THREE.CircleGeometry(radius, 24), mat);
disc.rotation.x = -Math.PI / 2;
disc.position.y = 0.01;
this.body.add(disc);
this.meshes.push(disc);
// Radiale Fäden
const threadMat = new THREE.MeshToonMaterial({
color: 0xffffff,
transparent: true,
opacity: 0.7,
depthWrite: false,
});
for (let i = 0; i < 8; i++) {
const a = (i / 8) * Math.PI * 2;
const thread = new THREE.Mesh(
new THREE.BoxGeometry(radius * 2, 0.03, 0.05),
threadMat
);
thread.rotation.y = -a;
thread.position.y = 0.02;
this.body.add(thread);
this.meshes.push(thread);
}
// Konzentrische Ringe
for (let r = 1; r <= 3; r++) {
const ring = new THREE.Mesh(
new THREE.RingGeometry(r * radius / 4 - 0.04, r * radius / 4 + 0.04, 24),
threadMat
);
ring.rotation.x = -Math.PI / 2;
ring.position.y = 0.025;
this.body.add(ring);
this.meshes.push(ring);
}
}
update(dt: number, game: Game): boolean {
this.life -= dt;
if (this.life <= 0) return false;
const fade = Math.min(1, this.life / FADE_TIME);
for (const mesh of this.meshes) {
(mesh.material as THREE.MeshToonMaterial).opacity =
(mesh === this.meshes[0] ? 0.55 : 0.7) * fade;
}
if (game.player.health > 0) {
const dist = Math.hypot(
game.player.body.position.x - this.body.position.x,
game.player.body.position.z - this.body.position.z
);
if (dist < this.radius) {
// Gepulste Fessel: kurzer Root, dann Lücke zum Rauslaufen.
// rootCooldown verhindert Endlos-Fessel durch überlappende Netze.
this.rootTick -= dt;
if (this.rootTick <= 0 && game.player.rootCooldown <= 0) {
game.player.rootTime = ROOT_DURATION;
game.player.rootCooldown = ROOT_COOLDOWN;
this.rootTick = ROOT_TICK;
}
} else {
this.rootTick = 0;
}
}
return true;
}
dispose() {
this.body.removeFromParent();
for (const mesh of this.meshes) {
mesh.geometry.dispose();
(mesh.material as THREE.Material).dispose();
}
}
}