106 lines
2.8 KiB
TypeScript
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();
|
|
}
|
|
}
|
|
}
|