Add boss hit feedback with flash and floating damage numbers

This commit is contained in:
2026-08-19 14:05:24 +02:00
parent 496c4af50e
commit 7d41f6db6c
4 changed files with 101 additions and 2 deletions
+53
View File
@@ -0,0 +1,53 @@
import * as THREE from 'three';
const LIFE = 0.8;
export class DamageNumber {
sprite: THREE.Sprite;
private mat: THREE.SpriteMaterial;
private life = LIFE;
constructor(
scene: THREE.Scene,
text: string,
pos: THREE.Vector3,
color = '#ffffff'
) {
const canvas = document.createElement('canvas');
canvas.width = 128;
canvas.height = 64;
const ctx = canvas.getContext('2d')!;
ctx.font = 'bold 40px monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.lineWidth = 6;
ctx.strokeStyle = '#000';
ctx.strokeText(text, 64, 32);
ctx.fillStyle = color;
ctx.fillText(text, 64, 32);
this.mat = new THREE.SpriteMaterial({
map: new THREE.CanvasTexture(canvas),
transparent: true,
depthWrite: false,
});
this.sprite = new THREE.Sprite(this.mat);
this.sprite.position.copy(pos);
this.sprite.position.y += 2.6;
this.sprite.scale.set(2, 1, 1);
scene.add(this.sprite);
}
update(dt: number): boolean {
this.life -= dt;
this.sprite.position.y += dt * 1.6;
this.mat.opacity = this.life < 0.3 ? this.life / 0.3 : 1;
return this.life > 0;
}
dispose(scene: THREE.Scene) {
scene.remove(this.sprite);
this.mat.map?.dispose();
this.mat.dispose();
}
}
+1 -1
View File
@@ -97,7 +97,7 @@ export abstract class Enemy {
}
playSound('damage', 0.7);
game.onDamageDealt(damage);
game.onDamageDealt(damage, this);
if (this.health <= 0) {
this.die(game);
+23 -1
View File
@@ -27,6 +27,7 @@ import { Boomerang } from './Boomerang';
import { Trap } from './Trap';
import { GasCloud } from './GasCloud';
import { Trapper } from './Trapper';
import { DamageNumber } from './DamageNumber';
const SPAWN_TIME = 10;
@@ -50,6 +51,7 @@ export class Game {
projectiles: EnemyProjectile[] = [];
traps: Trap[] = [];
gasClouds: GasCloud[] = [];
private damageNumbers: DamageNumber[] = [];
private aura: Aura | null = null;
private boomerang: Boomerang | null = null;
private trapper = new Trapper();
@@ -687,6 +689,9 @@ export class Game {
for (const cloud of this.gasClouds) cloud.dispose();
this.gasClouds = [];
for (const num of this.damageNumbers) num.dispose(this.scene);
this.damageNumbers = [];
if (this.aura) {
this.aura.dispose(this.scene);
this.aura = null;
@@ -743,10 +748,19 @@ export class Game {
this.updateUI();
}
onDamageDealt(damage: number) {
onDamageDealt(damage: number, target?: Enemy) {
if (this.state === 'playing' && this.player.stats.lifesteal > 0) {
this.player.heal(damage * this.player.stats.lifesteal);
}
// Treffer-Feedback: Schadenszahl bei Bossen einblenden
if (target?.isBoss && !target.dead) {
this.spawnDamageNumber(Math.round(damage), target.body.position);
}
}
spawnDamageNumber(damage: number, pos: THREE.Vector3, color?: string) {
const num = new DamageNumber(this.scene, `${damage}`, pos, color);
this.damageNumbers.push(num);
}
private openLevelUp() {
@@ -1314,6 +1328,14 @@ export class Game {
this.updateBloodParticles(dt);
// Schadenszahlen aktualisieren
for (let i = this.damageNumbers.length - 1; i >= 0; i--) {
if (!this.damageNumbers[i].update(dt)) {
this.damageNumbers[i].dispose(this.scene);
this.damageNumbers.splice(i, 1);
}
}
if (this.aura) this.aura.update(dt, this);
if (this.boomerang) this.boomerang.update(dt, this);
this.updateTraps(dt);
+24
View File
@@ -15,6 +15,7 @@ const PULL_CHARGE_TIME = 2.5;
const PULL_DAMAGE = 25;
const PULL_TIME = 0.6;
const SLAM_TIME = 0.5;
const HIT_FLASH_TIME = 0.14;
const SWEEP_RADIUS = 11;
const SWEEP_DURATION = 3.6;
const SWEEP_HIT_WIDTH = 1.2;
@@ -56,6 +57,7 @@ export class Krake extends Enemy {
private puddles: InkPuddle[] = [];
private skinMat!: THREE.MeshToonMaterial;
private darkMat!: THREE.MeshToonMaterial;
private hitFlash = 0;
constructor() {
super();
@@ -212,6 +214,18 @@ export class Krake extends Enemy {
this.ensureEffects(game.scene);
this.waveTime += dt;
// Hit-Feedback: kurz weiss aufblitzen + leichtes Pulsieren
if (this.hitFlash > 0) {
this.hitFlash -= dt;
this.skinMat.color.set(0xffffff);
this.darkMat.color.set(0xffffff);
this.model.scale.setScalar(1 + (this.hitFlash / HIT_FLASH_TIME) * 0.05);
} else {
this.skinMat.color.set(this.enraged ? 0x8a2233 : 0x8a4a9a);
this.darkMat.color.set(this.enraged ? 0x551520 : 0x5a2a6a);
this.model.scale.setScalar(1);
}
// Tentakel wippen
for (let i = 0; i < this.tentacles.length; i++) {
this.tentacles[i].rotation.z = Math.sin(this.waveTime * 2.2 + i * 0.9) * 0.25;
@@ -469,6 +483,16 @@ export class Krake extends Enemy {
}
}
override takeDamage(
damage: number,
game: Game,
withKnockback = true,
sourcePos?: THREE.Vector3
) {
super.takeDamage(damage, game, withKnockback, sourcePos);
this.hitFlash = HIT_FLASH_TIME;
}
protected onDeath(_game: Game) {
this.cleanupEffects();
this.beginFadeDeath();