Add six new enemies and spawn enemies within view
This commit is contained in:
+93
-11
@@ -17,6 +17,12 @@ import { Necromancer } from './Necromancer';
|
||||
import { Dragon } from './Dragon';
|
||||
import { Crab } from './Crab';
|
||||
import { Turret } from './Turret';
|
||||
import { Shaman } from './Shaman';
|
||||
import { Archer } from './Archer';
|
||||
import { Bomber } from './Bomber';
|
||||
import { Wisp } from './Wisp';
|
||||
import { Turtle } from './Turtle';
|
||||
import { SporeMushroom } from './SporeMushroom';
|
||||
import { EnemyProjectile } from './EnemyProjectile';
|
||||
import { Sword } from './Sword';
|
||||
import { Staff } from './Staff';
|
||||
@@ -82,6 +88,13 @@ export class Game {
|
||||
spawns = 0;
|
||||
killed = 0;
|
||||
|
||||
// Stationäre Gegner, die zu lange außerhalb des Bildschirms sind, werden
|
||||
// wieder in die Nähe des Spielers teleportiert (Off-Screen-Reset).
|
||||
private offscreenTimers = new Map<Enemy, number>();
|
||||
private static readonly OFFSCREEN_REPOSITION_TIME = 8;
|
||||
private static readonly SPAWN_VIEW_MIN = 10;
|
||||
private static readonly SPAWN_VIEW_MAX = 24;
|
||||
|
||||
// Boss-Wellen: Welle -> Boss (Name fuer Boss-Modus-UI, Factory fuer Spawn)
|
||||
private bossWaves = new Map<number, { name: string; spawn: () => Enemy }>([
|
||||
[10, { name: 'Golem', spawn: () => new Golem() }],
|
||||
@@ -765,6 +778,7 @@ export class Game {
|
||||
this.spawns = 0;
|
||||
this.killed = 0;
|
||||
this.bossFightActive = false;
|
||||
this.offscreenTimers.clear();
|
||||
this.shakeTime = 0;
|
||||
this.pendingPicks = 0;
|
||||
this.hideLevelUp();
|
||||
@@ -931,8 +945,13 @@ export class Game {
|
||||
}
|
||||
|
||||
private spawnEnemy() {
|
||||
const x = (Math.random() - 0.5) * 80;
|
||||
const z = (Math.random() - 0.5) * 80;
|
||||
// Immer im Sichtbereich spawnen (Ring um den Spieler), damit auch
|
||||
// stationäre Gegner nicht auf der Arena gesucht werden müssen.
|
||||
const p = this.player.body.position;
|
||||
const a = Math.random() * Math.PI * 2;
|
||||
const r = Game.SPAWN_VIEW_MIN + Math.random() * (Game.SPAWN_VIEW_MAX - Game.SPAWN_VIEW_MIN);
|
||||
const x = Math.max(-46, Math.min(46, p.x + Math.cos(a) * r));
|
||||
const z = Math.max(-46, Math.min(46, p.z + Math.sin(a) * r));
|
||||
|
||||
const weights = this.enemyWeights(this.spawns);
|
||||
let total = 0;
|
||||
@@ -957,22 +976,33 @@ export class Game {
|
||||
case 'brute': this.spawnEnemyInstance(new Brute(), x, z, 0.15); break;
|
||||
case 'crab': this.spawnEnemyInstance(new Crab(), x, z, 0.1); break;
|
||||
case 'turret': this.spawnEnemyInstance(new Turret(), x, z, 0.1); break;
|
||||
case 'shaman': this.spawnEnemyInstance(new Shaman(), x, z, 0.1); break;
|
||||
case 'archer': this.spawnEnemyInstance(new Archer(), x, z, 0.1); break;
|
||||
case 'bomber': this.spawnEnemyInstance(new Bomber(), x, z, 0.1); break;
|
||||
case 'wisp': this.spawnEnemyInstance(new Wisp(), x, z, 1.0); break;
|
||||
case 'turtle': this.spawnEnemyInstance(new Turtle(), x, z, 0.1); break;
|
||||
case 'spore': this.spawnEnemyInstance(new SporeMushroom(), x, z, 0.1); break;
|
||||
}
|
||||
}
|
||||
|
||||
private enemyWeights(wave: number): Map<string, number> {
|
||||
const w = new Map<string, number>();
|
||||
if (wave >= 7) {
|
||||
w.set('spider', 25).set('ghost', 15).set('frog', 15).set('slime', 10)
|
||||
.set('bat', 10).set('turret', 10).set('crab', 10).set('bee', 5);
|
||||
w.set('spider', 20).set('ghost', 12).set('frog', 12).set('slime', 8)
|
||||
.set('bat', 8).set('turret', 8).set('crab', 8).set('bee', 5)
|
||||
.set('archer', 6).set('bomber', 6).set('shaman', 4).set('wisp', 4)
|
||||
.set('turtle', 4).set('spore', 5);
|
||||
} else if (wave >= 6) {
|
||||
w.set('spider', 35).set('ghost', 20).set('frog', 15).set('slime', 10)
|
||||
.set('bat', 10).set('turret', 10);
|
||||
w.set('spider', 28).set('ghost', 16).set('frog', 12).set('slime', 8)
|
||||
.set('bat', 8).set('turret', 8)
|
||||
.set('archer', 7).set('bomber', 7).set('wisp', 4).set('spore', 5);
|
||||
} else if (wave >= 5) {
|
||||
w.set('spider', 45).set('ghost', 20).set('frog', 15).set('slime', 10)
|
||||
.set('bat', 10);
|
||||
w.set('spider', 35).set('ghost', 16).set('frog', 12).set('slime', 8)
|
||||
.set('bat', 8)
|
||||
.set('archer', 7).set('bomber', 7).set('spore', 5);
|
||||
} else if (wave >= 4) {
|
||||
w.set('spider', 55).set('ghost', 25).set('frog', 20);
|
||||
w.set('spider', 45).set('ghost', 20).set('frog', 16)
|
||||
.set('archer', 6).set('bomber', 6);
|
||||
} else if (wave >= 3) {
|
||||
w.set('spider', 75).set('ghost', 25);
|
||||
} else {
|
||||
@@ -989,8 +1019,18 @@ export class Game {
|
||||
const entry = this.bossWaves.get(wave);
|
||||
if (!entry) return;
|
||||
const enemy = entry.spawn();
|
||||
const x = enemy.spawnAtCenter ? 0 : (Math.random() - 0.5) * 80;
|
||||
const z = enemy.spawnAtCenter ? 0 : (Math.random() - 0.5) * 80;
|
||||
let x = 0;
|
||||
let z = 0;
|
||||
if (enemy.spawnAtCenter) {
|
||||
// Stationäre Bosse (Krake, Spinnenkönigin) spawnen in der Arena-Mitte
|
||||
} else {
|
||||
// Alle anderen Bosse im Sichtbereich spawnen
|
||||
const p = this.player.body.position;
|
||||
const a = Math.random() * Math.PI * 2;
|
||||
const r = Game.SPAWN_VIEW_MIN + Math.random() * (Game.SPAWN_VIEW_MAX - Game.SPAWN_VIEW_MIN);
|
||||
x = Math.max(-46, Math.min(46, p.x + Math.cos(a) * r));
|
||||
z = Math.max(-46, Math.min(46, p.z + Math.sin(a) * r));
|
||||
}
|
||||
this.spawnEnemyInstance(enemy, x, z, 0.15);
|
||||
playSound('spawn', 0.8);
|
||||
}
|
||||
@@ -1123,6 +1163,47 @@ export class Game {
|
||||
pos.z = Math.max(-47, Math.min(47, pos.z));
|
||||
}
|
||||
|
||||
// Stationäre Gegner (Turret, Sporen-Pilz, Krake, Spinnenkönigin), die zu
|
||||
// lange außerhalb des Bildschirms sind, werden wieder in Sicht teleportiert.
|
||||
private updateOffscreenReposition(dt: number) {
|
||||
for (const enemy of this.enemies) {
|
||||
if (enemy.dead) {
|
||||
this.offscreenTimers.delete(enemy);
|
||||
continue;
|
||||
}
|
||||
if (!enemy.immovable) continue;
|
||||
if (this.isOnScreen(enemy.body.position)) {
|
||||
this.offscreenTimers.delete(enemy);
|
||||
} else {
|
||||
const t = (this.offscreenTimers.get(enemy) ?? 0) + dt;
|
||||
if (t >= Game.OFFSCREEN_REPOSITION_TIME) {
|
||||
this.offscreenTimers.delete(enemy);
|
||||
this.repositionToView(enemy);
|
||||
} else {
|
||||
this.offscreenTimers.set(enemy, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NDC-Projektion mit Toleranz: |x|,|y| < 1.15 gilt als im Bild
|
||||
private isOnScreen(pos: THREE.Vector3): boolean {
|
||||
const v = pos.clone().project(this.camera);
|
||||
return v.x > -1.15 && v.x < 1.15 && v.y > -1.15 && v.y < 1.15 && v.z < 1;
|
||||
}
|
||||
|
||||
private repositionToView(enemy: Enemy) {
|
||||
const p = this.player.body.position;
|
||||
const a = Math.random() * Math.PI * 2;
|
||||
const r = Game.SPAWN_VIEW_MIN + Math.random() * (Game.SPAWN_VIEW_MAX - Game.SPAWN_VIEW_MIN);
|
||||
enemy.body.position.set(
|
||||
Math.max(-46, Math.min(46, p.x + Math.cos(a) * r)),
|
||||
enemy.body.position.y,
|
||||
Math.max(-46, Math.min(46, p.z + Math.sin(a) * r))
|
||||
);
|
||||
playSound('spawn', 0.5);
|
||||
}
|
||||
|
||||
private updateEnemies(dt: number) {
|
||||
for (const enemy of this.enemies) {
|
||||
if (enemy.dead || this.state !== 'playing') {
|
||||
@@ -1369,6 +1450,7 @@ export class Game {
|
||||
this.updateGroundPoint();
|
||||
this.updatePlayerMovement(dt);
|
||||
this.updateEnemies(dt);
|
||||
this.updateOffscreenReposition(dt);
|
||||
this.updateFireballs(dt);
|
||||
this.updateHitzones();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user