fea: 优化死亡特效

This commit is contained in:
cancerai committed 2026-10-24 02:35:03 +08:00
1 parent afe2f64b9b
commit e7200989ab
9 files changed
+373 -94

No files matched your search

+37 -14
View File
@@ -50,6 +50,7 @@ export class Player {
this.alive = true;
this.dead = false;
this.deadT = 0;
this.deathX = 0; this.deathY = 0; // 死亡动画圆心(掉坑死亡时 clamp 到关卡内)
this.inWater = false;
this.animT = 0;
this.shootCool = 0;
@@ -67,15 +68,36 @@ export class Player {
get cx() { return this.x + this.w / 2; }
get cy() { return this.y + this.h / 2; }
// 死亡:不播任何死亡动画(无等待、无镜头演出),只留音效、死亡计数,
// 以及在死亡地点落一处 IWBTG 风格的血迹(由 Level 常驻渲染,复活后仍在)。
kill() {
// 纯死亡演出(不进死亡状态):粒子爆开 + 屏幕轻震 + 死亡点扩散冲击环(Level 层,见 addDeathFX)。
// 主动按重生键时直接调它再当帧 respawn——特效留在死亡位置自然播完。
// 圆心 clamp 到关卡内:掉坑死亡时玩家已在屏幕外,保证红叉/粒子/圆环可见
deathBurst() {
const L = this.level;
const dx = clamp(this.cx, 10, Math.max(10, L.pxW - 10));
const dy = clamp(this.cy, 10, Math.max(10, L.pxH - 10));
sfx.death();
L.particles.spawn(dx, dy, 16, {
colors: ['#ff5c7a', '#ff8095', '#fff', '#ffc63a'], speed: 3.4, life: 34, size: 3, grav: 0.16,
});
L.shake = Math.max(L.shake || 0, 6);
L.addDeathFX(dx, dy);
return { dx, dy };
}
// 死亡:定格在原地,等玩家手动按重生键(R / 触屏重生键)。
// 视觉:① 死亡地点留下常驻红叉(IWBTG 血迹,复活后仍在;opts.mark=false 时不留)
// ② deathBurst() 的"小"死亡动画(粒子+轻震+冲击环)
// opts:
// mark=false → 不留红叉
// count=false → 不触发 onDeath 计数(不计入死亡数)
kill(opts = {}) {
if (this.dead || !this.alive) return;
this.dead = true; this.alive = false; this.deadT = 0;
this.vx = 0; this.vy = 0; this.sliding = false;
sfx.death();
this.level.addDeathMark(this.cx, this.cy);
if (this.onDeath) this.onDeath();
const { dx, dy } = this.deathBurst();
this.deathX = dx; this.deathY = dy;
if (opts.mark !== false) this.level.addDeathMark(dx, dy);
if (opts.count !== false && this.onDeath) this.onDeath();
}
respawn() {
@@ -90,7 +112,7 @@ export class Player {
// 安全兜底:万一重生点被地形/消失块堵住,向上顶出,别一复活就卡死
let guard = 0;
while (L.solidRect(this.x, this.y, this.w, this.h) && guard++ < 48) this.y -= 2;
L.softReset();
L.softReset(); // 不清粒子/震屏:死亡特效留在死亡位置自然播完
this.justRespawned = true; // 供 main.js 把镜头直接吸附到重生点
}
@@ -100,8 +122,9 @@ export class Player {
this.animT++;
if (this.shootCool > 0) this.shootCool--;
// 上一帧/外部(R 键自杀、假存档)触发的死亡:立刻复活,不留死亡动画
if (this.dead) this.respawn();
// 死亡:定格在原地等玩家手动重生(R 键 / 触屏重生键),
// 世界继续运转(敌人/子弹/粒子照走),但玩家不响应任何输入
if (this.dead) { this.deadT++; return; }
const inWater = L.waterRect(this.x + 2, this.y + 4, this.w - 4, this.h - 4);
if (inWater && !this.inWater) sfx.splash();
@@ -252,11 +275,11 @@ export class Player {
// ---- 边界 ----
this.x = clamp(this.x, 0, L.pxW - this.w);
if (this.y > L.pxH + 200) { this.kill(); if (this.dead) { this.respawn(); return; } }
if (this.y > L.pxH + 200) { this.kill(); return; }
if (this.y < -300) { this.y = -300; this.vy = 0; }
// ---- 致死:同帧立刻复活,画面上不留任何死亡状态 ----
if (L.deadlyRect(this.x, this.y, this.w, this.h)) { this.kill(); if (this.dead) { this.respawn(); return; } }
if (L.deadlyRect(this.x, this.y, this.w, this.h)) { this.kill(); return; }
// ---- 射击 ----
if (this.hasGun && Input.hit('shoot') && this.shootCool === 0 && L.bullets.length < 5) {
@@ -278,8 +301,6 @@ export class Player {
}
}
// 兜底:本帧内任何环节致死(如假存档)都立刻复活
if (this.dead) this.respawn();
}
shoot() {
@@ -314,7 +335,7 @@ export class Player {
L.particles.spawn(e.x + e.w / 2, e.y + e.h / 2, 18,
{ colors: ['#b06bff', '#fff', '#e5d0ff'], speed: 2.6, life: 30 });
// 假存档:立刻复活回上一个存档点,不播死亡动画
if (e.fake) { L.showToast('假的。', 90); this.kill(); if (this.dead) { this.respawn(); return; } }
if (e.fake) { L.showToast('假的。', 90); this.kill(); return; }
}
break;
case 'goal':
@@ -368,6 +389,8 @@ export class Player {
}
draw(ctx) {
// 死亡中:kid 已爆开,不画本体(粒子/冲击环由 Level 层渲染,重生后仍能播完)
if (this.dead) return;
S.drawKid(ctx, this, this.animT);
}
}