Die the way people die in the films
The old death was a red tint and a shake for half a second, then a puff of gore. That reads as the sprite having a moment, not as being eaten. Now it takes four and a half seconds and it is a crowd doing it. You are dragged to your knees fighting it (new frames, p_die1/p_die2, alternating), blood coming out of you the whole time — a constant spray and a stain that keeps widening, with the wet work on a loop underneath. Every body on court drops what it was doing and converges: each claims a spot in a ring around you, kept from wherever it was standing, so they crowd in from all sides instead of stacking on one pixel. Then you are pressed flat into the ground while the ring closes over you — you are drawn INSIDE the crowd's z order, so the ones between you and the camera hide you exactly as they arrive — and what is left is the mound, with them still feeding over it when the summary finally fades in. Also: the wounded shamblers were stepping their two frames at half the cadence of the healthy four, which reads as frozen next to the crowd they walk in. Same clock now. (They were animating — verified frame by frame — just too slowly.) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b7899f122c
commit
4d6b47dadd
8 files changed
+88
-24
No files matched your search
+37
-1
@@ -3,7 +3,7 @@
|
||||
// your baseline — one crossing it is a point for THEM.
|
||||
// (zombies <-> gameplay is a runtime-only import cycle, same pattern as input.)
|
||||
|
||||
import { TUNE, UI, COURT, VOLLEY, CONTACT, BOSS_ART } from './config.js';
|
||||
import { TUNE, UI, COURT, VOLLEY, CONTACT, BOSS_ART, EATEN_BURST, EATEN_FEED_R } from './config.js';
|
||||
import { G, P, ball, sendBoy, setupPoint } from './state.js';
|
||||
import { splat, moan, pock, voice, boom, swish, shriek, aww, sfx } from './audio.js';
|
||||
import { splatPoint, cheer, spentBall, resolveInterlude, showModal, endBossFight, aside } from './gameplay.js';
|
||||
@@ -280,6 +280,21 @@ function burnTick(zb, i, dt) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Phase A, every frame: they are tearing at you and it shows. A thin constant spray out of the
|
||||
// pile plus a stain that keeps widening — "blood and gore flowing out" is a CONTINUOUS thing, not
|
||||
// one burst, and the burst at EATEN_BURST lands on top of it.
|
||||
export function eatenBleed(dt) {
|
||||
const at = G.eatenAt; if (!at) return;
|
||||
for (let k = 0; k < 2; k++) parts.push({
|
||||
x: at.x + (Math.random() * 2 - 1) * 0.35, y: 0.5 + Math.random() * 0.8, z: at.z + (Math.random() * 2 - 1) * 0.3,
|
||||
vx: (Math.random() * 2 - 1) * 3.2, vy: 1.5 + Math.random() * 3.5, vz: (Math.random() * 2 - 1) * 3.2,
|
||||
t: 0.4 + Math.random() * 0.5, dark: Math.random() < 0.45 });
|
||||
const g = gore.find(d => d.spreading);
|
||||
if (g) g.r = Math.min(2.1, g.r + dt * 0.7);
|
||||
else { gore.push({ x: at.x, z: at.z, r: 0.7, a: 0.9, spreading: true }); if (gore.length > 40) gore.shift(); }
|
||||
if ((G.eatSfxT = (G.eatSfxT || 0) - dt) <= 0) { splat(0.45); G.eatSfxT = 0.28; } // wet work, on a loop
|
||||
}
|
||||
|
||||
// phase B of the death animation (EATEN_BURST, config.js): the player IS the gore now. Reuses killZombie's machinery —
|
||||
// the biggest mound on the court, and it stays visible under the summary scrim. That's the point.
|
||||
export function playerGore() {
|
||||
@@ -555,6 +570,27 @@ export function updateZombies(dt) {
|
||||
pock(0.3, 1500); // the tell: a mechanical click as it loads
|
||||
}
|
||||
}
|
||||
if (G.mode === 'eaten' && G.eatenAt && zb.variant !== 'm') {
|
||||
// You are down. Nothing else matters to them: every body on court converges on the spot and
|
||||
// packs into a ring around it, and the ones already there feed (the bob is in drawZombie).
|
||||
// This is the whole point of the death — you go under a CROWD, not under a puff of gore.
|
||||
// Each one claims a SPOT around the body, kept from wherever it happened to be standing, so
|
||||
// they form a ring and crowd in from all sides. Sending them all at the one point instead
|
||||
// stacked every body on the same pixel and read as one fat zombie, not a swarm.
|
||||
if (zb.eatA === undefined) {
|
||||
zb.eatA = Math.atan2(zb.z - G.eatenAt.z, zb.x - G.eatenAt.x) + (Math.random() * 2 - 1) * 0.4;
|
||||
zb.eatR = EATEN_FEED_R * (0.8 + Math.random() * 0.7);
|
||||
}
|
||||
const tx = G.eatenAt.x + Math.cos(zb.eatA) * zb.eatR;
|
||||
const tz = G.eatenAt.z + Math.sin(zb.eatA) * zb.eatR * 0.55; // squashed in depth: the camera is low
|
||||
const dx = tx - zb.x, dz = tz - zb.z, d = Math.hypot(dx, dz);
|
||||
zb.feeding = d < 0.12;
|
||||
if (!zb.feeding) {
|
||||
const sp = zb.v * 1.8 * dt; // they hurry for a kill
|
||||
zb.x += dx / d * Math.min(sp, d); zb.z += dz / d * Math.min(sp, d);
|
||||
}
|
||||
continue; // no shambling, no eating you twice, no crossing the baseline for a point
|
||||
}
|
||||
if (zb.variant === 'm') { // the machine wheels itself around instead of being bolted down
|
||||
if (!held && G.mode === 'splatter' && !G.interlude) {
|
||||
if (zb.rollTo !== undefined) {
|
||||
|
||||
Reference in new issue
Block a user