Rework necromancer, archer, shaman and golem models with procedural animations
This commit is contained in:
+198
-39
@@ -17,6 +17,14 @@ export class Archer extends Enemy {
|
||||
private boneMat!: THREE.MeshToonMaterial;
|
||||
private darkMat!: THREE.MeshToonMaterial;
|
||||
private bowGroup!: THREE.Group;
|
||||
private legL!: THREE.Group;
|
||||
private legR!: THREE.Group;
|
||||
private walkPhase = 0;
|
||||
private walkAmp = 0;
|
||||
private handL!: THREE.Mesh;
|
||||
private arrowNock!: THREE.Mesh;
|
||||
private draw = 0; // 0 = Bogen entspannt, 1 = voll ausgezogen
|
||||
private recoil = 0; // klingt nach dem Abschuss ab
|
||||
private shootTimer = 1 + Math.random();
|
||||
private telegraphs: { mesh: THREE.Mesh; mat: THREE.MeshBasicMaterial; timer: number }[] = [];
|
||||
private time = Math.random() * Math.PI * 2;
|
||||
@@ -36,53 +44,177 @@ export class Archer extends Enemy {
|
||||
|
||||
this.boneMat = new THREE.MeshToonMaterial({ color: 0xe8dcc0 });
|
||||
this.darkMat = new THREE.MeshToonMaterial({ color: 0x8a7a5a });
|
||||
const eyeMat = new THREE.MeshBasicMaterial({ color: 0xff4422 });
|
||||
|
||||
// Torso
|
||||
const torso = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.55, 0.35), this.boneMat);
|
||||
torso.position.y = 1.0;
|
||||
torso.castShadow = true;
|
||||
this.registerFadeMesh(torso);
|
||||
this.model.add(torso);
|
||||
// --- Skelett (Blickrichtung +Z) ---
|
||||
|
||||
// Beine
|
||||
for (const side of [-1, 1]) {
|
||||
const leg = new THREE.Mesh(new THREE.CylinderGeometry(0.07, 0.09, 0.7, 6), this.boneMat);
|
||||
leg.position.set(side * 0.15, 0.35, 0);
|
||||
this.registerFadeMesh(leg);
|
||||
this.model.add(leg);
|
||||
// Becken + Gürtel
|
||||
const pelvis = new THREE.Mesh(new THREE.SphereGeometry(0.16, 10, 8), this.boneMat);
|
||||
pelvis.scale.set(1.2, 0.7, 0.9);
|
||||
pelvis.position.y = 0.78;
|
||||
this.registerFadeMesh(pelvis);
|
||||
this.model.add(pelvis);
|
||||
const belt = new THREE.Mesh(new THREE.TorusGeometry(0.17, 0.03, 6, 12), this.darkMat);
|
||||
belt.rotation.x = Math.PI / 2;
|
||||
belt.position.y = 0.82;
|
||||
this.registerFadeMesh(belt);
|
||||
this.model.add(belt);
|
||||
|
||||
// Wirbelsäule + Rippenbögen
|
||||
const spine = new THREE.Mesh(new THREE.CylinderGeometry(0.035, 0.045, 0.55, 6), this.boneMat);
|
||||
spine.position.y = 1.08;
|
||||
this.registerFadeMesh(spine);
|
||||
this.model.add(spine);
|
||||
for (const [y, r] of [[1.02, 0.2], [1.14, 0.23], [1.26, 0.19]] as const) {
|
||||
const rib = new THREE.Mesh(new THREE.TorusGeometry(r, 0.025, 6, 12), this.boneMat);
|
||||
rib.rotation.x = Math.PI / 2;
|
||||
rib.scale.set(1, 1, 0.8);
|
||||
rib.position.y = y;
|
||||
this.registerFadeMesh(rib);
|
||||
this.model.add(rib);
|
||||
}
|
||||
|
||||
// Kopf (Schädel)
|
||||
const head = new THREE.Mesh(new THREE.SphereGeometry(0.22, 10, 8), this.boneMat);
|
||||
head.position.y = 1.5;
|
||||
this.registerFadeMesh(head);
|
||||
this.model.add(head);
|
||||
|
||||
// Augen (+Z, rot)
|
||||
const eyeMat = new THREE.MeshBasicMaterial({ color: 0xff4422 });
|
||||
// Schädel mit Kiefer, Augenhöhlen und roten Augen (+Z)
|
||||
const skull = new THREE.Mesh(new THREE.SphereGeometry(0.22, 12, 10), this.boneMat);
|
||||
skull.scale.set(0.95, 1.05, 1);
|
||||
skull.position.y = 1.52;
|
||||
skull.castShadow = true;
|
||||
this.registerFadeMesh(skull);
|
||||
this.model.add(skull);
|
||||
const jaw = new THREE.Mesh(new THREE.BoxGeometry(0.16, 0.09, 0.16), this.boneMat);
|
||||
jaw.position.set(0, 1.38, 0.1);
|
||||
this.registerFadeMesh(jaw);
|
||||
this.model.add(jaw);
|
||||
for (const side of [-1, 1]) {
|
||||
const eye = new THREE.Mesh(new THREE.SphereGeometry(0.05, 6, 5), eyeMat);
|
||||
eye.position.set(side * 0.1, 1.53, 0.2);
|
||||
const socket = new THREE.Mesh(new THREE.SphereGeometry(0.055, 8, 6), this.darkMat);
|
||||
socket.position.set(side * 0.09, 1.55, 0.17);
|
||||
this.registerFadeMesh(socket);
|
||||
this.model.add(socket);
|
||||
const eye = new THREE.Mesh(new THREE.SphereGeometry(0.035, 6, 5), eyeMat);
|
||||
eye.position.set(side * 0.09, 1.55, 0.2);
|
||||
this.model.add(eye);
|
||||
}
|
||||
|
||||
// Bogen (hängt an Gruppe, wird beim Schuss angehoben)
|
||||
// Schultern + Arme (rechts hält den Bogen, links zieht die Sehne)
|
||||
for (const side of [-1, 1]) {
|
||||
const shoulder = new THREE.Mesh(new THREE.SphereGeometry(0.07, 8, 6), this.boneMat);
|
||||
shoulder.position.set(side * 0.28, 1.34, 0);
|
||||
this.registerFadeMesh(shoulder);
|
||||
this.model.add(shoulder);
|
||||
}
|
||||
const upperArmR = new THREE.Mesh(new THREE.CylinderGeometry(0.04, 0.05, 0.32, 6), this.boneMat);
|
||||
upperArmR.position.set(0.34, 1.26, 0.08);
|
||||
upperArmR.rotation.z = -1.0;
|
||||
this.registerFadeMesh(upperArmR);
|
||||
this.model.add(upperArmR);
|
||||
const foreArmR = new THREE.Mesh(new THREE.CylinderGeometry(0.035, 0.04, 0.3, 6), this.boneMat);
|
||||
foreArmR.position.set(0.4, 1.18, 0.25);
|
||||
foreArmR.rotation.x = -1.2;
|
||||
this.registerFadeMesh(foreArmR);
|
||||
this.model.add(foreArmR);
|
||||
const handR = new THREE.Mesh(new THREE.SphereGeometry(0.05, 8, 6), this.boneMat);
|
||||
handR.position.set(0.42, 1.15, 0.4);
|
||||
this.registerFadeMesh(handR);
|
||||
this.model.add(handR);
|
||||
|
||||
const upperArmL = new THREE.Mesh(new THREE.CylinderGeometry(0.04, 0.05, 0.32, 6), this.boneMat);
|
||||
upperArmL.position.set(-0.34, 1.24, 0.05);
|
||||
upperArmL.rotation.z = 1.1;
|
||||
this.registerFadeMesh(upperArmL);
|
||||
this.model.add(upperArmL);
|
||||
const foreArmL = new THREE.Mesh(new THREE.CylinderGeometry(0.035, 0.04, 0.3, 6), this.boneMat);
|
||||
foreArmL.position.set(-0.24, 1.25, 0.15);
|
||||
foreArmL.rotation.x = -1.6;
|
||||
foreArmL.rotation.z = 0.6;
|
||||
this.registerFadeMesh(foreArmL);
|
||||
this.model.add(foreArmL);
|
||||
this.handL = new THREE.Mesh(new THREE.SphereGeometry(0.05, 8, 6), this.boneMat);
|
||||
this.handL.position.set(-0.1, 1.3, 0.28);
|
||||
this.registerFadeMesh(this.handL);
|
||||
this.model.add(this.handL);
|
||||
|
||||
// Beine mit Kniegelenken und Füßen (Gruppen mit Hüft-Pivot für Laufanimation)
|
||||
for (const side of [-1, 1]) {
|
||||
const leg = new THREE.Group();
|
||||
leg.position.set(side * 0.15, 0.72, 0);
|
||||
this.model.add(leg);
|
||||
const thigh = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.07, 0.4, 6), this.boneMat);
|
||||
thigh.position.y = -0.17;
|
||||
this.registerFadeMesh(thigh);
|
||||
leg.add(thigh);
|
||||
const knee = new THREE.Mesh(new THREE.SphereGeometry(0.06, 8, 6), this.boneMat);
|
||||
knee.position.y = -0.37;
|
||||
this.registerFadeMesh(knee);
|
||||
leg.add(knee);
|
||||
const shin = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.06, 0.35, 6), this.boneMat);
|
||||
shin.position.y = -0.54;
|
||||
this.registerFadeMesh(shin);
|
||||
leg.add(shin);
|
||||
const foot = new THREE.Mesh(new THREE.BoxGeometry(0.12, 0.06, 0.22), this.boneMat);
|
||||
foot.position.set(0, -0.68, 0.06);
|
||||
this.registerFadeMesh(foot);
|
||||
leg.add(foot);
|
||||
if (side < 0) {
|
||||
this.legL = leg;
|
||||
} else {
|
||||
this.legR = leg;
|
||||
}
|
||||
}
|
||||
|
||||
// Zerfetzter Umhang auf dem Rücken
|
||||
const cape = new THREE.Mesh(new THREE.ConeGeometry(0.32, 0.75, 8, 1, true), this.darkMat);
|
||||
cape.position.set(0, 1.0, -0.2);
|
||||
cape.scale.set(1.1, 1, 0.5);
|
||||
this.registerFadeMesh(cape);
|
||||
this.model.add(cape);
|
||||
for (const [x, z] of [[-0.15, -0.28], [0, -0.32], [0.15, -0.28]] as const) {
|
||||
const tatter = new THREE.Mesh(new THREE.ConeGeometry(0.06, 0.25, 4), this.darkMat);
|
||||
tatter.rotation.x = Math.PI;
|
||||
tatter.position.set(x, 0.62, z);
|
||||
this.registerFadeMesh(tatter);
|
||||
this.model.add(tatter);
|
||||
}
|
||||
|
||||
// Köcher mit Pfeilen am Rücken
|
||||
const quiver = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.11, 0.5, 8), this.darkMat);
|
||||
quiver.position.set(0.12, 1.15, -0.28);
|
||||
quiver.rotation.x = 0.5;
|
||||
this.registerFadeMesh(quiver);
|
||||
this.model.add(quiver);
|
||||
for (const dx of [-0.03, 0.02, 0.06]) {
|
||||
const qArrow = new THREE.Mesh(new THREE.CylinderGeometry(0.012, 0.012, 0.28, 5), this.boneMat);
|
||||
qArrow.position.set(0.12 + dx, 1.42, -0.4);
|
||||
qArrow.rotation.x = 0.5;
|
||||
this.registerFadeMesh(qArrow);
|
||||
this.model.add(qArrow);
|
||||
}
|
||||
|
||||
// Bogen (hängt an Gruppe, wird beim Schuss angehoben):
|
||||
// Halbkreis-Bogen mit Sehne und eingelegtem Pfeil
|
||||
this.bowGroup = new THREE.Group();
|
||||
this.bowGroup.position.set(0.4, 1.05, 0.15);
|
||||
this.bowGroup.position.set(0.42, 1.15, 0.45);
|
||||
this.model.add(this.bowGroup);
|
||||
const bow = new THREE.Mesh(new THREE.TorusGeometry(0.28, 0.03, 6, 14, Math.PI * 1.6), this.darkMat);
|
||||
bow.rotation.y = -Math.PI / 2;
|
||||
bow.rotation.z = -0.8;
|
||||
const bowOrient = new THREE.Group();
|
||||
bowOrient.rotation.y = -Math.PI / 2; // Bogenebene in Blickrichtung
|
||||
this.bowGroup.add(bowOrient);
|
||||
const bow = new THREE.Mesh(new THREE.TorusGeometry(0.4, 0.028, 6, 16, Math.PI), this.darkMat);
|
||||
bow.rotation.z = -Math.PI / 2; // Bogenbauch zeigt nach +Z
|
||||
this.registerFadeMesh(bow);
|
||||
this.bowGroup.add(bow);
|
||||
const string = new THREE.Mesh(new THREE.BoxGeometry(0.03, 0.5, 0.02), this.darkMat);
|
||||
string.position.set(0.02, 0, 0);
|
||||
bowOrient.add(bow);
|
||||
const string = new THREE.Mesh(new THREE.CylinderGeometry(0.012, 0.012, 0.8, 5), this.darkMat);
|
||||
this.registerFadeMesh(string);
|
||||
this.bowGroup.add(string);
|
||||
const arm = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.06, 0.5, 6), this.boneMat);
|
||||
arm.position.set(0.02, -0.1, 0);
|
||||
this.registerFadeMesh(arm);
|
||||
this.bowGroup.add(arm);
|
||||
bowOrient.add(string);
|
||||
// Pfeil sitzt auf Sehne und Bogen: Schaft entlang lokaler X-Achse
|
||||
// (zeigt nach Welt-+Z), Nocke an der Sehne
|
||||
this.arrowNock = new THREE.Mesh(new THREE.CylinderGeometry(0.014, 0.014, 0.75, 5), this.boneMat);
|
||||
this.arrowNock.rotation.z = -Math.PI / 2;
|
||||
this.arrowNock.position.x = 0.28;
|
||||
this.registerFadeMesh(this.arrowNock);
|
||||
bowOrient.add(this.arrowNock);
|
||||
const arrowHead = new THREE.Mesh(new THREE.ConeGeometry(0.035, 0.1, 6), this.darkMat);
|
||||
arrowHead.position.y = 0.42;
|
||||
this.registerFadeMesh(arrowHead);
|
||||
this.arrowNock.add(arrowHead);
|
||||
}
|
||||
|
||||
playAnim() {
|
||||
@@ -112,6 +244,7 @@ export class Archer extends Enemy {
|
||||
toPlayer.y = 0;
|
||||
const dist = toPlayer.length();
|
||||
|
||||
let moved = false;
|
||||
if (dist > 0.1) {
|
||||
const dir = toPlayer.normalize();
|
||||
this.body.lookAt(
|
||||
@@ -138,9 +271,20 @@ export class Archer extends Enemy {
|
||||
this.body.position.x += move.x * dt;
|
||||
this.body.position.z += move.z * dt;
|
||||
game.clampToArena(this.body.position);
|
||||
moved = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Laufanimation: Beine gegengleich schwingen, Amplitude weich ein-/ausblenden
|
||||
this.walkAmp += ((moved ? 0.55 : 0) - this.walkAmp) * Math.min(1, dt * 8);
|
||||
if (this.walkAmp > 0.01) {
|
||||
this.walkPhase += dt * 11;
|
||||
}
|
||||
const swing = Math.sin(this.walkPhase) * this.walkAmp;
|
||||
this.legL.rotation.x = swing;
|
||||
this.legR.rotation.x = -swing;
|
||||
this.model.position.y = Math.abs(Math.cos(this.walkPhase)) * this.walkAmp * 0.12;
|
||||
|
||||
// Schuss mit Telegraf
|
||||
this.shootTimer -= dt;
|
||||
if (dist < SHOOT_RANGE && this.shootTimer <= 0 && game.player.health > 0) {
|
||||
@@ -163,8 +307,23 @@ export class Archer extends Enemy {
|
||||
}
|
||||
}
|
||||
|
||||
// Bogen-Schwung
|
||||
this.bowGroup.rotation.z = Math.sin(this.time * 2) * 0.1;
|
||||
// Schießanimation: während des Telegrafs Bogen heben und Sehne spannen,
|
||||
// beim Abschuss Recoil-Kick und schnelles Entspannen
|
||||
const aiming = this.telegraphs.length > 0;
|
||||
const drawRate = aiming ? 6 : this.recoil > 0 ? 25 : 10;
|
||||
this.draw += ((aiming ? 1 : 0) - this.draw) * Math.min(1, dt * drawRate);
|
||||
this.recoil = Math.max(0, this.recoil - dt);
|
||||
const aim = this.draw;
|
||||
this.bowGroup.position.set(
|
||||
0.42 - aim * 0.17,
|
||||
1.15 + aim * 0.2,
|
||||
0.45 + aim * 0.1
|
||||
);
|
||||
this.bowGroup.rotation.x = -aim * 0.15;
|
||||
this.bowGroup.rotation.z =
|
||||
Math.sin(this.time * 2) * 0.1 * (1 - aim) - this.recoil * 2;
|
||||
this.handL.position.z = 0.28 - aim * 0.16;
|
||||
this.arrowNock.position.x = 0.28 - aim * 0.14;
|
||||
}
|
||||
|
||||
private startShot(game: Game) {
|
||||
@@ -192,8 +351,8 @@ export class Archer extends Enemy {
|
||||
const target = targetPos.clone();
|
||||
target.y += 0.6;
|
||||
game.spawnEnemyProjectile(from, target, ARROW_DAMAGE);
|
||||
// Bogen anheben als Recoil
|
||||
this.bowGroup.rotation.z = -0.6;
|
||||
// Recoil-Kick, klingt in update() ab
|
||||
this.recoil = 0.25;
|
||||
}
|
||||
|
||||
override takeDamage(
|
||||
|
||||
+40
-1
@@ -4,7 +4,12 @@ import { Enemy } from './Enemy';
|
||||
|
||||
export class Brute extends Enemy {
|
||||
protected model: THREE.Group;
|
||||
protected legL!: THREE.Group;
|
||||
protected legR!: THREE.Group;
|
||||
protected armL: THREE.Group | null = null;
|
||||
protected armR: THREE.Group | null = null;
|
||||
private stepTime = 0;
|
||||
private walkAmp = 0;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -21,7 +26,11 @@ export class Brute extends Enemy {
|
||||
const rockMat = new THREE.MeshToonMaterial({ color: 0x6a6a78 });
|
||||
const darkMat = new THREE.MeshToonMaterial({ color: 0x3c3c46 });
|
||||
|
||||
// Torso
|
||||
// Becken + Torso
|
||||
const pelvis = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.35, 0.55), darkMat);
|
||||
pelvis.position.y = 0.62;
|
||||
this.registerFadeMesh(pelvis);
|
||||
this.model.add(pelvis);
|
||||
const torso = new THREE.Mesh(new THREE.BoxGeometry(1.3, 1.2, 0.95), rockMat);
|
||||
torso.position.y = 0.95;
|
||||
torso.castShadow = true;
|
||||
@@ -61,6 +70,26 @@ export class Brute extends Enemy {
|
||||
this.registerFadeMesh(eye);
|
||||
this.model.add(eye);
|
||||
}
|
||||
|
||||
// Beine (Gruppen mit Hüft-Pivot für die Laufanimation)
|
||||
for (const side of [-1, 1]) {
|
||||
const leg = new THREE.Group();
|
||||
leg.position.set(side * 0.35, 0.6, 0);
|
||||
this.model.add(leg);
|
||||
const thigh = new THREE.Mesh(new THREE.CylinderGeometry(0.14, 0.18, 0.35, 6), rockMat);
|
||||
thigh.position.y = -0.17;
|
||||
this.registerFadeMesh(thigh);
|
||||
leg.add(thigh);
|
||||
const foot = new THREE.Mesh(new THREE.BoxGeometry(0.26, 0.12, 0.4), darkMat);
|
||||
foot.position.set(0, -0.5, 0.06);
|
||||
this.registerFadeMesh(foot);
|
||||
leg.add(foot);
|
||||
if (side < 0) {
|
||||
this.legL = leg;
|
||||
} else {
|
||||
this.legR = leg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
playAnim() {
|
||||
@@ -81,6 +110,7 @@ export class Brute extends Enemy {
|
||||
.subVectors(game.player.body.position, this.body.position);
|
||||
toPlayer.y = 0;
|
||||
const dist = toPlayer.length();
|
||||
let moved = false;
|
||||
if (dist > 0.1) {
|
||||
const dir = toPlayer.normalize();
|
||||
this.body.lookAt(
|
||||
@@ -96,7 +126,16 @@ export class Brute extends Enemy {
|
||||
this.body.position.x += moveDir.x * dt;
|
||||
this.body.position.z += moveDir.z * dt;
|
||||
game.clampToArena(this.body.position);
|
||||
moved = true;
|
||||
}
|
||||
|
||||
// Laufanimation: Beine gegengleich im Takt der Schritte, Arme leicht mit
|
||||
this.walkAmp += ((moved ? 0.5 : 0) - this.walkAmp) * Math.min(1, dt * 8);
|
||||
const swing = Math.sin(this.stepTime * 3) * this.walkAmp;
|
||||
this.legL.rotation.x = swing;
|
||||
this.legR.rotation.x = -swing;
|
||||
if (this.armL) this.armL.rotation.x = -swing * 0.4;
|
||||
if (this.armR) this.armR.rotation.x = swing * 0.4;
|
||||
}
|
||||
|
||||
protected onDeath(_game: Game) {
|
||||
|
||||
+139
-2
@@ -15,15 +15,152 @@ export class Golem extends Brute {
|
||||
this.contactRadius = 3.4;
|
||||
this.guaranteedDrop = true;
|
||||
|
||||
// Brute-Standardmodell verwerfen und als Fels-Golem neu aufbauen
|
||||
this.model.clear();
|
||||
|
||||
const rockMat = new THREE.MeshToonMaterial({ color: 0x6a6a78 });
|
||||
const darkMat = new THREE.MeshToonMaterial({ color: 0x3c3c46 });
|
||||
const mossMat = new THREE.MeshToonMaterial({ color: 0x4a8a4a });
|
||||
const eyeMat = new THREE.MeshBasicMaterial({ color: 0xff6622 });
|
||||
// Facettierte Fels-Optik: flache Normalen auf der unindizierten Geometrie
|
||||
const rockGeom = (r: number) => {
|
||||
const g = new THREE.DodecahedronGeometry(r);
|
||||
g.computeVertexNormals();
|
||||
return g;
|
||||
};
|
||||
|
||||
// Becken + Rumpf (facettierte Felsbrocken)
|
||||
const pelvis = new THREE.Mesh(rockGeom(0.42), darkMat);
|
||||
pelvis.scale.set(1.2, 0.8, 0.9);
|
||||
pelvis.position.y = 0.75;
|
||||
this.registerFadeMesh(pelvis);
|
||||
this.model.add(pelvis);
|
||||
const torso = new THREE.Mesh(rockGeom(0.72), rockMat);
|
||||
torso.scale.set(1.25, 1.05, 0.95);
|
||||
torso.position.y = 1.4;
|
||||
torso.castShadow = true;
|
||||
this.registerFadeMesh(torso);
|
||||
this.model.add(torso);
|
||||
|
||||
// Glühender Kern in der Brust (+Z)
|
||||
const core = new THREE.Mesh(new THREE.BoxGeometry(0.28, 0.36, 0.08), eyeMat);
|
||||
core.position.set(0, 1.45, 0.66);
|
||||
this.model.add(core);
|
||||
|
||||
// Kopf mit Stirnplatte, Augen und Kiefer (+Z)
|
||||
const head = new THREE.Mesh(rockGeom(0.38), rockMat);
|
||||
head.position.y = 2.15;
|
||||
head.castShadow = true;
|
||||
this.registerFadeMesh(head);
|
||||
this.model.add(head);
|
||||
const brow = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.1, 0.12), darkMat);
|
||||
brow.position.set(0, 2.3, 0.3);
|
||||
this.registerFadeMesh(brow);
|
||||
this.model.add(brow);
|
||||
for (const side of [-1, 1]) {
|
||||
const eye = new THREE.Mesh(new THREE.BoxGeometry(0.13, 0.09, 0.05), eyeMat);
|
||||
eye.position.set(side * 0.15, 2.15, 0.33);
|
||||
this.model.add(eye);
|
||||
}
|
||||
const jaw = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.12, 0.2), darkMat);
|
||||
jaw.position.set(0, 1.93, 0.25);
|
||||
this.registerFadeMesh(jaw);
|
||||
this.model.add(jaw);
|
||||
|
||||
// Felsstacheln auf dem Rücken
|
||||
for (const side of [-1, 1]) {
|
||||
const spike = new THREE.Mesh(new THREE.ConeGeometry(0.14, 0.45, 5), darkMat);
|
||||
spike.position.set(side * 0.3, 1.75, -0.6);
|
||||
spike.rotation.x = 0.6;
|
||||
this.registerFadeMesh(spike);
|
||||
this.model.add(spike);
|
||||
}
|
||||
|
||||
// Moosflecken
|
||||
const mossSpots: [number, number, number, number][] = [
|
||||
[-0.9, 2.15, 0.1, 0.12],
|
||||
[0.5, 1.95, 0.3, 0.1],
|
||||
[0.3, 2.4, -0.1, 0.09],
|
||||
];
|
||||
for (const [x, y, z, r] of mossSpots) {
|
||||
const moss = new THREE.Mesh(rockGeom(r), mossMat);
|
||||
moss.position.set(x, y, z);
|
||||
this.registerFadeMesh(moss);
|
||||
this.model.add(moss);
|
||||
}
|
||||
|
||||
// Schultern + gegliederte Arme (Schulter-Pivot für Laufanimation)
|
||||
for (const side of [-1, 1]) {
|
||||
const shoulder = new THREE.Mesh(rockGeom(0.48), rockMat);
|
||||
shoulder.position.set(side * 0.95, 1.75, 0);
|
||||
shoulder.castShadow = true;
|
||||
this.registerFadeMesh(shoulder);
|
||||
this.model.add(shoulder);
|
||||
|
||||
const arm = new THREE.Group();
|
||||
arm.position.set(side * 0.95, 1.7, 0);
|
||||
this.model.add(arm);
|
||||
const upperArm = new THREE.Mesh(new THREE.CylinderGeometry(0.16, 0.2, 0.55, 6), rockMat);
|
||||
upperArm.position.y = -0.3;
|
||||
this.registerFadeMesh(upperArm);
|
||||
arm.add(upperArm);
|
||||
const elbow = new THREE.Mesh(rockGeom(0.18), darkMat);
|
||||
elbow.position.y = -0.6;
|
||||
this.registerFadeMesh(elbow);
|
||||
arm.add(elbow);
|
||||
const foreArm = new THREE.Mesh(new THREE.CylinderGeometry(0.14, 0.18, 0.5, 6), darkMat);
|
||||
foreArm.position.y = -0.85;
|
||||
this.registerFadeMesh(foreArm);
|
||||
arm.add(foreArm);
|
||||
const fist = new THREE.Mesh(rockGeom(0.24), rockMat);
|
||||
fist.scale.set(1, 0.8, 1.1);
|
||||
fist.position.y = -1.15;
|
||||
this.registerFadeMesh(fist);
|
||||
arm.add(fist);
|
||||
if (side < 0) {
|
||||
this.armL = arm;
|
||||
} else {
|
||||
this.armR = arm;
|
||||
}
|
||||
}
|
||||
|
||||
// Beine (Hüft-Pivot für Laufanimation)
|
||||
for (const side of [-1, 1]) {
|
||||
const leg = new THREE.Group();
|
||||
leg.position.set(side * 0.4, 0.75, 0);
|
||||
this.model.add(leg);
|
||||
const thigh = new THREE.Mesh(new THREE.CylinderGeometry(0.18, 0.24, 0.4, 6), rockMat);
|
||||
thigh.position.y = -0.18;
|
||||
this.registerFadeMesh(thigh);
|
||||
leg.add(thigh);
|
||||
const knee = new THREE.Mesh(rockGeom(0.16), darkMat);
|
||||
knee.position.y = -0.38;
|
||||
this.registerFadeMesh(knee);
|
||||
leg.add(knee);
|
||||
const shin = new THREE.Mesh(new THREE.CylinderGeometry(0.14, 0.18, 0.35, 6), darkMat);
|
||||
shin.position.y = -0.52;
|
||||
this.registerFadeMesh(shin);
|
||||
leg.add(shin);
|
||||
const foot = new THREE.Mesh(new THREE.BoxGeometry(0.32, 0.16, 0.5), rockMat);
|
||||
foot.position.set(0, -0.67, 0.08);
|
||||
this.registerFadeMesh(foot);
|
||||
leg.add(foot);
|
||||
if (side < 0) {
|
||||
this.legL = leg;
|
||||
} else {
|
||||
this.legR = leg;
|
||||
}
|
||||
}
|
||||
|
||||
this.model.scale.setScalar(2.1);
|
||||
|
||||
// Crown
|
||||
// Krone (Boss-Markierung)
|
||||
const crownMat = new THREE.MeshToonMaterial({ color: 0xffcc33 });
|
||||
const crown = new THREE.Mesh(
|
||||
new THREE.ConeGeometry(0.3, 0.4, 8, 1, true),
|
||||
crownMat
|
||||
);
|
||||
crown.position.y = 2.25;
|
||||
crown.position.y = 2.55;
|
||||
crown.castShadow = true;
|
||||
this.registerFadeMesh(crown);
|
||||
this.model.add(crown);
|
||||
|
||||
+153
-38
@@ -90,21 +90,66 @@ export class Necromancer extends Enemy {
|
||||
this.darkMat = new THREE.MeshToonMaterial({ color: 0x2a1445 });
|
||||
this.glowMat = new THREE.MeshBasicMaterial({ color: 0xbb66ff });
|
||||
this.eyeMat = new THREE.MeshBasicMaterial({ color: 0xdd88ff });
|
||||
const boneMat = new THREE.MeshToonMaterial({ color: 0xf0e6cf });
|
||||
const faceMat = new THREE.MeshBasicMaterial({ color: 0x0a0614 });
|
||||
let boneFadeRegistered = false;
|
||||
const registerBone = (mesh: THREE.Mesh) => {
|
||||
// Knochen-Material einmal fürs Fade-Death registrieren (wird geteilt)
|
||||
if (!boneFadeRegistered) {
|
||||
this.registerFadeMesh(mesh);
|
||||
boneFadeRegistered = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Robe (umgedrehter Kegel, schwingt beim Schweben)
|
||||
const robe = new THREE.Mesh(new THREE.ConeGeometry(0.9, 1.8, 12, 1, true), this.robeMat);
|
||||
robe.position.y = -0.2;
|
||||
robe.rotation.x = Math.PI;
|
||||
// --- Robe (Blickrichtung +Z, schwebt: unten offen und zerfetzt) ---
|
||||
const robeProfile = [
|
||||
new THREE.Vector2(0.75, -1.15), // Saum
|
||||
new THREE.Vector2(0.62, -0.75),
|
||||
new THREE.Vector2(0.45, -0.2),
|
||||
new THREE.Vector2(0.38, 0.25),
|
||||
new THREE.Vector2(0.5, 0.55), // Schulteransatz
|
||||
new THREE.Vector2(0.32, 0.78), // Hals
|
||||
];
|
||||
const robe = new THREE.Mesh(new THREE.LatheGeometry(robeProfile, 14), this.robeMat);
|
||||
robe.castShadow = true;
|
||||
this.registerFadeMesh(robe);
|
||||
this.model.add(robe);
|
||||
|
||||
// Rocksaum
|
||||
const hem = new THREE.Mesh(new THREE.CircleGeometry(0.95, 12), this.darkMat);
|
||||
hem.rotation.x = -Math.PI / 2;
|
||||
hem.position.y = -1.05;
|
||||
this.registerFadeMesh(hem);
|
||||
this.model.add(hem);
|
||||
// Innenschatten im Saum (von unten sichtbar)
|
||||
const hemInner = new THREE.Mesh(new THREE.CircleGeometry(0.7, 14), this.darkMat);
|
||||
hemInner.rotation.x = Math.PI / 2;
|
||||
hemInner.position.y = -1.08;
|
||||
this.registerFadeMesh(hemInner);
|
||||
this.model.add(hemInner);
|
||||
|
||||
// Zerfetzte Saumfetzen
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const a = (i / 8) * Math.PI * 2;
|
||||
const tatter = new THREE.Mesh(
|
||||
new THREE.ConeGeometry(0.08, 0.4, 4),
|
||||
i % 2 === 0 ? this.robeMat : this.darkMat
|
||||
);
|
||||
tatter.rotation.x = Math.PI;
|
||||
tatter.position.set(Math.sin(a) * 0.68, -1.28, Math.cos(a) * 0.68);
|
||||
this.registerFadeMesh(tatter);
|
||||
this.model.add(tatter);
|
||||
}
|
||||
|
||||
// Gürtelkordel + Quaste
|
||||
const belt = new THREE.Mesh(new THREE.TorusGeometry(0.42, 0.035, 6, 16), this.darkMat);
|
||||
belt.rotation.x = Math.PI / 2;
|
||||
belt.position.y = 0.12;
|
||||
this.registerFadeMesh(belt);
|
||||
this.model.add(belt);
|
||||
const tasselCord = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.02, 0.28, 5), this.darkMat);
|
||||
tasselCord.position.set(-0.3, -0.05, 0.36);
|
||||
this.registerFadeMesh(tasselCord);
|
||||
this.model.add(tasselCord);
|
||||
const tassel = new THREE.Mesh(new THREE.ConeGeometry(0.05, 0.14, 5), boneMat);
|
||||
tassel.rotation.x = Math.PI;
|
||||
tassel.position.set(-0.3, -0.24, 0.38);
|
||||
registerBone(tassel);
|
||||
this.model.add(tassel);
|
||||
|
||||
// Oberkörper
|
||||
const body = new THREE.Mesh(new THREE.SphereGeometry(0.55, 16, 12), this.robeMat);
|
||||
@@ -114,46 +159,116 @@ export class Necromancer extends Enemy {
|
||||
this.registerFadeMesh(body);
|
||||
this.model.add(body);
|
||||
|
||||
// Kapuze
|
||||
const hood = new THREE.Mesh(new THREE.ConeGeometry(0.42, 0.8, 12, 1, true), this.darkMat);
|
||||
hood.position.set(0, 1.1, 0.1);
|
||||
hood.rotation.x = 0.15;
|
||||
// Kragen
|
||||
const collar = new THREE.Mesh(new THREE.TorusGeometry(0.22, 0.045, 6, 14), this.darkMat);
|
||||
collar.rotation.x = Math.PI / 2 - 0.2;
|
||||
collar.position.set(0, 0.85, 0.08);
|
||||
this.registerFadeMesh(collar);
|
||||
this.model.add(collar);
|
||||
|
||||
// Schulterpanzer mit Spitzen
|
||||
for (const side of [-1, 1]) {
|
||||
const pauldron = new THREE.Mesh(new THREE.SphereGeometry(0.26, 12, 8), this.darkMat);
|
||||
pauldron.scale.set(1.25, 0.65, 1.25);
|
||||
pauldron.position.set(side * 0.45, 0.78, 0);
|
||||
this.registerFadeMesh(pauldron);
|
||||
this.model.add(pauldron);
|
||||
const spike = new THREE.Mesh(new THREE.ConeGeometry(0.06, 0.22, 5), this.darkMat);
|
||||
spike.position.set(side * 0.55, 0.95, 0);
|
||||
spike.rotation.z = side * -0.5;
|
||||
this.registerFadeMesh(spike);
|
||||
this.model.add(spike);
|
||||
}
|
||||
|
||||
// Kapuze mit Krempe und schwarzem Gesichts-Leerraum
|
||||
const hood = new THREE.Mesh(new THREE.ConeGeometry(0.5, 0.95, 12, 1, true), this.darkMat);
|
||||
hood.position.set(0, 1.18, 0.02);
|
||||
hood.rotation.x = 0.22;
|
||||
hood.castShadow = true;
|
||||
this.registerFadeMesh(hood);
|
||||
this.model.add(hood);
|
||||
|
||||
// Kapuzen-Öffnung (+Z, dunkel)
|
||||
const hoodFace = new THREE.Mesh(new THREE.CircleGeometry(0.36, 12), this.darkMat);
|
||||
hoodFace.position.set(0, 1.02, 0.42);
|
||||
hoodFace.rotation.y = Math.PI / 2 - 0.1;
|
||||
hoodFace.rotation.x = -0.35;
|
||||
this.registerFadeMesh(hoodFace);
|
||||
this.model.add(hoodFace);
|
||||
const hoodRim = new THREE.Mesh(new THREE.TorusGeometry(0.34, 0.05, 8, 16), this.darkMat);
|
||||
hoodRim.rotation.x = -0.5;
|
||||
hoodRim.position.set(0, 1.05, 0.34);
|
||||
this.registerFadeMesh(hoodRim);
|
||||
this.model.add(hoodRim);
|
||||
const face = new THREE.Mesh(new THREE.SphereGeometry(0.3, 12, 10), faceMat);
|
||||
face.scale.set(1, 1.1, 0.8);
|
||||
face.position.set(0, 1.04, 0.26);
|
||||
this.registerFadeMesh(face);
|
||||
this.model.add(face);
|
||||
|
||||
// Leuchtende Augen in der Kapuze (+Z)
|
||||
for (const side of [-1, 1]) {
|
||||
const eye = new THREE.Mesh(new THREE.SphereGeometry(0.07, 8, 6), this.eyeMat);
|
||||
eye.position.set(side * 0.13, 1.05, 0.44);
|
||||
const eye = new THREE.Mesh(new THREE.SphereGeometry(0.075, 8, 6), this.eyeMat);
|
||||
eye.position.set(side * 0.12, 1.08, 0.48);
|
||||
this.model.add(eye);
|
||||
}
|
||||
|
||||
// Glühender Amulett-Kern
|
||||
// Amulett + Runen auf der Robe (glühen, färben sich im Enrage)
|
||||
const amulet = new THREE.Mesh(new THREE.SphereGeometry(0.08, 8, 6), this.glowMat);
|
||||
amulet.position.set(0, 0.62, 0.52);
|
||||
amulet.position.set(0, 0.6, 0.5);
|
||||
this.model.add(amulet);
|
||||
const runeSpots: [number, number, number][] = [
|
||||
[0, -0.15, 0.5],
|
||||
[-0.2, -0.5, 0.5],
|
||||
[0.2, -0.5, 0.5],
|
||||
];
|
||||
for (const [x, y, z] of runeSpots) {
|
||||
const rune = new THREE.Mesh(new THREE.OctahedronGeometry(0.06), this.glowMat);
|
||||
rune.scale.set(1, 1.7, 0.5);
|
||||
rune.position.set(x, y, z);
|
||||
this.model.add(rune);
|
||||
}
|
||||
|
||||
// Arm mit Stab
|
||||
const arm = new THREE.Mesh(new THREE.CylinderGeometry(0.09, 0.12, 0.6, 8), this.darkMat);
|
||||
arm.position.set(0.55, 0.7, 0.15);
|
||||
arm.rotation.z = -0.6;
|
||||
this.registerFadeMesh(arm);
|
||||
this.model.add(arm);
|
||||
// Rechter Arm: Ärmel zum Stab
|
||||
const sleeveR = new THREE.Mesh(new THREE.CylinderGeometry(0.09, 0.16, 0.55, 8), this.robeMat);
|
||||
sleeveR.position.set(0.65, 0.84, 0.14);
|
||||
sleeveR.rotation.z = 1.2;
|
||||
this.registerFadeMesh(sleeveR);
|
||||
this.model.add(sleeveR);
|
||||
const handR = new THREE.Mesh(new THREE.SphereGeometry(0.1, 8, 6), boneMat);
|
||||
handR.position.set(0.85, 0.9, 0.23);
|
||||
registerBone(handR);
|
||||
this.model.add(handR);
|
||||
|
||||
const staff = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.05, 2.0, 8), this.darkMat);
|
||||
staff.position.set(0.85, 1.3, 0.25);
|
||||
staff.rotation.z = 0.35;
|
||||
staff.rotation.x = 0.2;
|
||||
// Linker Arm: Ärmel nach vorn (Beschwörungs-Geste) + Energiekugel
|
||||
const sleeveL = new THREE.Mesh(new THREE.CylinderGeometry(0.09, 0.18, 0.6, 8), this.robeMat);
|
||||
sleeveL.position.set(-0.55, 0.75, 0.32);
|
||||
sleeveL.rotation.x = -1.25;
|
||||
sleeveL.rotation.z = -0.35;
|
||||
this.registerFadeMesh(sleeveL);
|
||||
this.model.add(sleeveL);
|
||||
const handL = new THREE.Mesh(new THREE.SphereGeometry(0.1, 8, 6), boneMat);
|
||||
handL.position.set(-0.68, 0.72, 0.62);
|
||||
registerBone(handL);
|
||||
this.model.add(handL);
|
||||
const castOrb = new THREE.Mesh(new THREE.SphereGeometry(0.09, 8, 6), this.glowMat);
|
||||
castOrb.position.set(-0.7, 0.92, 0.68);
|
||||
this.model.add(castOrb);
|
||||
|
||||
// Stab mit Manschetten, Totenkopf und schwebendem Orb
|
||||
const staff = new THREE.Mesh(new THREE.CylinderGeometry(0.045, 0.045, 2.4, 8), this.darkMat);
|
||||
staff.position.set(0.8, 1.2, 0.28);
|
||||
staff.rotation.z = 0.18;
|
||||
staff.rotation.x = 0.15;
|
||||
this.registerFadeMesh(staff);
|
||||
this.model.add(staff);
|
||||
for (const [x, y, z] of [[0.88, 0.76, 0.21], [0.96, 0.31, 0.15]] as const) {
|
||||
const collarM = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.06, 0.07, 8), this.darkMat);
|
||||
collarM.position.set(x, y, z);
|
||||
this.registerFadeMesh(collarM);
|
||||
this.model.add(collarM);
|
||||
}
|
||||
const skull = new THREE.Mesh(new THREE.SphereGeometry(0.15, 10, 8), boneMat);
|
||||
skull.position.set(0.58, 2.42, 0.47);
|
||||
registerBone(skull);
|
||||
this.model.add(skull);
|
||||
for (const side of [-1, 1]) {
|
||||
const socket = new THREE.Mesh(new THREE.SphereGeometry(0.03, 6, 4), this.darkMat);
|
||||
socket.position.set(0.58 + side * 0.05, 2.44, 0.6);
|
||||
this.model.add(socket);
|
||||
}
|
||||
|
||||
const orbMat = new THREE.MeshBasicMaterial({
|
||||
color: 0xbb66ff,
|
||||
@@ -162,8 +277,8 @@ export class Necromancer extends Enemy {
|
||||
blending: THREE.AdditiveBlending,
|
||||
depthWrite: false,
|
||||
});
|
||||
const orb = new THREE.Mesh(new THREE.SphereGeometry(0.16, 10, 8), orbMat);
|
||||
orb.position.set(1.2, 1.95, 0.35);
|
||||
const orb = new THREE.Mesh(new THREE.SphereGeometry(0.14, 10, 8), orbMat);
|
||||
orb.position.set(0.58, 2.66, 0.47);
|
||||
this.model.add(orb);
|
||||
this.orbMat = orbMat;
|
||||
this.orb = orb;
|
||||
|
||||
+162
-23
@@ -33,40 +33,149 @@ export class Shaman extends Enemy {
|
||||
|
||||
this.robeMat = new THREE.MeshToonMaterial({ color: 0x33aa55, emissive: 0x0a1f10 });
|
||||
this.darkMat = new THREE.MeshToonMaterial({ color: 0x1c5c2e });
|
||||
const boneMat = new THREE.MeshToonMaterial({ color: 0xe8dcc0 });
|
||||
const faceMat = new THREE.MeshBasicMaterial({ color: 0x06120a });
|
||||
let boneFadeRegistered = false;
|
||||
const registerBone = (mesh: THREE.Mesh) => {
|
||||
// Knochen-Material einmal fürs Fade-Death registrieren (wird geteilt)
|
||||
if (!boneFadeRegistered) {
|
||||
this.registerFadeMesh(mesh);
|
||||
boneFadeRegistered = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Robe (Kegel)
|
||||
const robe = new THREE.Mesh(new THREE.ConeGeometry(0.45, 1.1, 10, 1, true), this.robeMat);
|
||||
robe.position.y = 0.5;
|
||||
// --- Robe (Blickrichtung +Z, bis zum Boden) ---
|
||||
const robeProfile = [
|
||||
new THREE.Vector2(0.52, 0.02), // Saum
|
||||
new THREE.Vector2(0.44, 0.35),
|
||||
new THREE.Vector2(0.32, 0.7),
|
||||
new THREE.Vector2(0.3, 0.95),
|
||||
new THREE.Vector2(0.38, 1.1), // Schulteransatz
|
||||
new THREE.Vector2(0.24, 1.25), // Hals
|
||||
];
|
||||
const robe = new THREE.Mesh(new THREE.LatheGeometry(robeProfile, 12), this.robeMat);
|
||||
robe.castShadow = true;
|
||||
this.registerFadeMesh(robe);
|
||||
this.model.add(robe);
|
||||
|
||||
// Oberkörper
|
||||
const body = new THREE.Mesh(new THREE.SphereGeometry(0.3, 12, 10), this.robeMat);
|
||||
body.position.y = 1.0;
|
||||
this.registerFadeMesh(body);
|
||||
this.model.add(body);
|
||||
// Zerfetzter Saum
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const a = (i / 6) * Math.PI * 2;
|
||||
const tatter = new THREE.Mesh(
|
||||
new THREE.ConeGeometry(0.06, 0.28, 4),
|
||||
i % 2 === 0 ? this.robeMat : this.darkMat
|
||||
);
|
||||
tatter.rotation.x = Math.PI;
|
||||
tatter.position.set(Math.sin(a) * 0.48, -0.02, Math.cos(a) * 0.48);
|
||||
this.registerFadeMesh(tatter);
|
||||
this.model.add(tatter);
|
||||
}
|
||||
|
||||
// Kopf mit Kapuze
|
||||
const hood = new THREE.Mesh(new THREE.ConeGeometry(0.26, 0.5, 10, 1, true), this.darkMat);
|
||||
hood.position.set(0, 1.4, 0.08);
|
||||
// Gürtel mit hängenden Knochen-Amuletten
|
||||
const belt = new THREE.Mesh(new THREE.TorusGeometry(0.33, 0.03, 6, 14), this.darkMat);
|
||||
belt.rotation.x = Math.PI / 2;
|
||||
belt.position.y = 0.75;
|
||||
this.registerFadeMesh(belt);
|
||||
this.model.add(belt);
|
||||
for (const [x, z] of [[-0.14, 0.3], [0, 0.34], [0.14, 0.3]] as const) {
|
||||
const charm = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.02, 0.16, 5), boneMat);
|
||||
charm.position.set(x, 0.62, z);
|
||||
registerBone(charm);
|
||||
this.model.add(charm);
|
||||
}
|
||||
|
||||
// Brust + Fellkragen mit Zacken
|
||||
const chest = new THREE.Mesh(new THREE.SphereGeometry(0.28, 12, 10), this.robeMat);
|
||||
chest.position.y = 1.12;
|
||||
chest.scale.set(1, 1.15, 1);
|
||||
this.registerFadeMesh(chest);
|
||||
this.model.add(chest);
|
||||
const collar = new THREE.Mesh(new THREE.TorusGeometry(0.26, 0.09, 8, 14), this.darkMat);
|
||||
collar.rotation.x = Math.PI / 2;
|
||||
collar.scale.set(1, 1, 0.8);
|
||||
collar.position.y = 1.28;
|
||||
this.registerFadeMesh(collar);
|
||||
this.model.add(collar);
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const a = (i / 8) * Math.PI * 2;
|
||||
const fur = new THREE.Mesh(new THREE.ConeGeometry(0.04, 0.16, 4), this.darkMat);
|
||||
fur.position.set(Math.sin(a) * 0.28, 1.36, Math.cos(a) * 0.24);
|
||||
fur.rotation.x = -0.4;
|
||||
fur.rotation.y = a;
|
||||
this.registerFadeMesh(fur);
|
||||
this.model.add(fur);
|
||||
}
|
||||
|
||||
// Knochenkette mit Zähnen auf der Brust
|
||||
const necklace = new THREE.Mesh(new THREE.TorusGeometry(0.16, 0.02, 6, 12), this.darkMat);
|
||||
necklace.rotation.x = Math.PI / 2 - 0.3;
|
||||
necklace.position.set(0, 1.2, 0.2);
|
||||
this.registerFadeMesh(necklace);
|
||||
this.model.add(necklace);
|
||||
for (const [x, z] of [[-0.08, 0.32], [0, 0.35], [0.08, 0.32]] as const) {
|
||||
const tooth = new THREE.Mesh(new THREE.ConeGeometry(0.03, 0.1, 4), boneMat);
|
||||
tooth.rotation.x = Math.PI;
|
||||
tooth.position.set(x, 1.1, z);
|
||||
registerBone(tooth);
|
||||
this.model.add(tooth);
|
||||
}
|
||||
|
||||
// Kapuze mit Krempe, schwarzem Gesicht und grünen Augen (+Z)
|
||||
const hood = new THREE.Mesh(new THREE.ConeGeometry(0.32, 0.6, 10, 1, true), this.darkMat);
|
||||
hood.position.set(0, 1.55, 0.02);
|
||||
hood.rotation.x = 0.2;
|
||||
hood.castShadow = true;
|
||||
this.registerFadeMesh(hood);
|
||||
this.model.add(hood);
|
||||
|
||||
// Leuchtende Augen (+Z)
|
||||
const hoodRim = new THREE.Mesh(new THREE.TorusGeometry(0.22, 0.04, 8, 14), this.darkMat);
|
||||
hoodRim.rotation.x = -0.5;
|
||||
hoodRim.position.set(0, 1.44, 0.22);
|
||||
this.registerFadeMesh(hoodRim);
|
||||
this.model.add(hoodRim);
|
||||
const face = new THREE.Mesh(new THREE.SphereGeometry(0.2, 12, 10), faceMat);
|
||||
face.scale.set(1, 1.1, 0.8);
|
||||
face.position.set(0, 1.44, 0.16);
|
||||
this.registerFadeMesh(face);
|
||||
this.model.add(face);
|
||||
const eyeMat = new THREE.MeshBasicMaterial({ color: 0x66ff88 });
|
||||
for (const side of [-1, 1]) {
|
||||
const eye = new THREE.Mesh(new THREE.SphereGeometry(0.05, 6, 5), eyeMat);
|
||||
eye.position.set(side * 0.11, 1.32, 0.26);
|
||||
const eye = new THREE.Mesh(new THREE.SphereGeometry(0.055, 6, 5), eyeMat);
|
||||
eye.position.set(side * 0.08, 1.47, 0.35);
|
||||
this.model.add(eye);
|
||||
}
|
||||
|
||||
// Stab mit Glühmund
|
||||
const staff = new THREE.Mesh(new THREE.CylinderGeometry(0.035, 0.035, 1.2, 6), this.darkMat);
|
||||
staff.position.set(0.45, 0.8, 0.1);
|
||||
staff.rotation.z = 0.35;
|
||||
this.registerFadeMesh(staff);
|
||||
this.model.add(staff);
|
||||
// Federn an der Kapuze (hängen nach hinten)
|
||||
for (const side of [-1, 1]) {
|
||||
const feather = new THREE.Mesh(new THREE.ConeGeometry(0.05, 0.4, 4), boneMat);
|
||||
feather.rotation.x = Math.PI + side * 0.15;
|
||||
feather.scale.set(1, 1, 0.3);
|
||||
feather.position.set(side * 0.14, 1.35, -0.22);
|
||||
registerBone(feather);
|
||||
this.model.add(feather);
|
||||
}
|
||||
|
||||
// Rechter Arm zum Stab
|
||||
const sleeveR = new THREE.Mesh(new THREE.CylinderGeometry(0.07, 0.12, 0.45, 8), this.robeMat);
|
||||
sleeveR.position.set(0.32, 1.05, 0.1);
|
||||
sleeveR.rotation.z = 1.0;
|
||||
this.registerFadeMesh(sleeveR);
|
||||
this.model.add(sleeveR);
|
||||
const handR = new THREE.Mesh(new THREE.SphereGeometry(0.07, 8, 6), boneMat);
|
||||
handR.position.set(0.45, 0.95, 0.18);
|
||||
registerBone(handR);
|
||||
this.model.add(handR);
|
||||
|
||||
// Linker Arm erhoben mit kleiner Energiekugel
|
||||
const sleeveL = new THREE.Mesh(new THREE.CylinderGeometry(0.07, 0.12, 0.45, 8), this.robeMat);
|
||||
sleeveL.position.set(-0.34, 1.12, 0.18);
|
||||
sleeveL.rotation.x = -1.1;
|
||||
sleeveL.rotation.z = -0.5;
|
||||
this.registerFadeMesh(sleeveL);
|
||||
this.model.add(sleeveL);
|
||||
const handL = new THREE.Mesh(new THREE.SphereGeometry(0.07, 8, 6), boneMat);
|
||||
handL.position.set(-0.4, 1.22, 0.34);
|
||||
registerBone(handL);
|
||||
this.model.add(handL);
|
||||
|
||||
const orbMat = new THREE.MeshBasicMaterial({
|
||||
color: 0x66ff88,
|
||||
@@ -75,9 +184,39 @@ export class Shaman extends Enemy {
|
||||
blending: THREE.AdditiveBlending,
|
||||
depthWrite: false,
|
||||
});
|
||||
const orb = new THREE.Mesh(new THREE.SphereGeometry(0.1, 8, 6), orbMat);
|
||||
orb.position.set(0.62, 1.45, 0.12);
|
||||
const castOrb = new THREE.Mesh(new THREE.SphereGeometry(0.07, 8, 6), orbMat);
|
||||
castOrb.position.set(-0.42, 1.38, 0.38);
|
||||
this.model.add(castOrb);
|
||||
|
||||
// Totem-Stab: Schädel mit Klauenkrone, Glüh-Orb, hängender Anhänger
|
||||
const staff = new THREE.Mesh(new THREE.CylinderGeometry(0.03, 0.04, 1.7, 6), this.darkMat);
|
||||
staff.position.set(0.5, 0.85, 0.2);
|
||||
staff.rotation.z = 0.15;
|
||||
staff.rotation.x = 0.1;
|
||||
this.registerFadeMesh(staff);
|
||||
this.model.add(staff);
|
||||
const skull = new THREE.Mesh(new THREE.SphereGeometry(0.11, 10, 8), boneMat);
|
||||
skull.position.set(0.37, 1.72, 0.29);
|
||||
registerBone(skull);
|
||||
this.model.add(skull);
|
||||
for (const side of [-1, 1]) {
|
||||
const claw = new THREE.Mesh(new THREE.ConeGeometry(0.03, 0.22, 5), boneMat);
|
||||
claw.position.set(0.37 + side * 0.1, 1.84, 0.29);
|
||||
claw.rotation.z = side * -0.5;
|
||||
registerBone(claw);
|
||||
this.model.add(claw);
|
||||
const socket = new THREE.Mesh(new THREE.SphereGeometry(0.025, 6, 4), this.darkMat);
|
||||
socket.position.set(0.37 + side * 0.04, 1.74, 0.39);
|
||||
this.model.add(socket);
|
||||
}
|
||||
const orb = new THREE.Mesh(new THREE.SphereGeometry(0.09, 8, 6), orbMat);
|
||||
orb.position.set(0.37, 1.94, 0.29);
|
||||
this.model.add(orb);
|
||||
const dangle = new THREE.Mesh(new THREE.CylinderGeometry(0.015, 0.015, 0.14, 5), boneMat);
|
||||
dangle.position.set(0.44, 1.58, 0.27);
|
||||
dangle.rotation.z = 0.4;
|
||||
registerBone(dangle);
|
||||
this.model.add(dangle);
|
||||
|
||||
// Heil-Aura-Ring am Boden
|
||||
this.healRingMat = new THREE.MeshBasicMaterial({
|
||||
|
||||
Reference in New Issue
Block a user