Suck into pipe effect for goos
This commit is contained in:
parent
e1b1ab2bab
commit
d8296940e5
|
|
@ -138,6 +138,7 @@ export default class GooTowerGame extends Phaser.Scene {
|
|||
this.debugZoomText = null;
|
||||
this.levelBounds = null; // { top, bottom }, set per level in startLevel()
|
||||
this.bgImage = null;
|
||||
this.suckFX = []; // short-lived cosmetic tails for balls the pipe just collected
|
||||
}
|
||||
|
||||
async create() {
|
||||
|
|
@ -212,6 +213,7 @@ export default class GooTowerGame extends Phaser.Scene {
|
|||
this.terrainG = null;
|
||||
this.board = null;
|
||||
this.panFrom = null;
|
||||
this.suckFX = [];
|
||||
this.needText = null;
|
||||
this.hudText = null;
|
||||
if (this.introAnim) { this.tweens.killTweensOf(this.introAnim); this.introAnim = null; }
|
||||
|
|
@ -672,6 +674,11 @@ export default class GooTowerGame extends Phaser.Scene {
|
|||
const events = stepSim(this.state, dt);
|
||||
this.handleEvents(events);
|
||||
|
||||
if (this.suckFX.length) {
|
||||
for (const fx of this.suckFX) fx.t += dt;
|
||||
this.suckFX = this.suckFX.filter((fx) => fx.t < fx.dur);
|
||||
}
|
||||
|
||||
this.drawDynamic();
|
||||
this.refreshHud();
|
||||
|
||||
|
|
@ -703,7 +710,24 @@ export default class GooTowerGame extends Phaser.Scene {
|
|||
handleEvents(events) {
|
||||
for (const e of events) {
|
||||
if (e.type === 'strandBreak') playSound(this, SFX.SQUASH);
|
||||
else if (e.type === 'collect') playSound(this, SFX.UI_CHIME);
|
||||
else if (e.type === 'collect') {
|
||||
playSound(this, SFX.UI_CHIME);
|
||||
// The sim deletes the ball this same tick; spawn a short cosmetic
|
||||
// tail continuing the pre-collect pull tendril so it reads as being
|
||||
// swallowed rather than popping out of existence.
|
||||
const b = this.state.balls[e.ball];
|
||||
const pipe = this.state.pipe;
|
||||
if (b && pipe) {
|
||||
const ddx = pipe.x - b.x;
|
||||
const ddy = pipe.y - b.y;
|
||||
const dlen = Math.hypot(ddx, ddy) || 1;
|
||||
this.suckFX.push({
|
||||
x: b.x, y: b.y, dirX: ddx / dlen, dirY: ddy / dlen,
|
||||
r: b.r, color: GOO_COLOR[b.type] ?? GOO_COLOR.common,
|
||||
t: 0, dur: 0.18,
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (e.type === 'ballDie') playSound(this, SFX.SQUISH);
|
||||
else if (e.type === 'pipeOpen') playSound(this, SFX.GEM_DROP);
|
||||
else if (e.type === 'stick') playSound(this, SFX.PIECE_CLICK);
|
||||
|
|
@ -796,6 +820,7 @@ export default class GooTowerGame extends Phaser.Scene {
|
|||
this.drawBall(g, b);
|
||||
}
|
||||
|
||||
this.drawSuckFX(g);
|
||||
this.drawPipe(g);
|
||||
this.drawGhost();
|
||||
}
|
||||
|
|
@ -810,6 +835,25 @@ export default class GooTowerGame extends Phaser.Scene {
|
|||
const vy = b.y - b.py;
|
||||
const sp = Math.min(1, Math.hypot(vx, vy) / 6);
|
||||
|
||||
// A walking ball nearing an open pipe grows a tapering tendril reaching
|
||||
// toward the mouth, so the last stretch of its walk visibly pulls it in
|
||||
// rather than the sim's instant collect reading as a random disappearance.
|
||||
// Drawn UNDER the body circles below so it merges seamlessly into the ball.
|
||||
const pipe = this.state.pipe;
|
||||
if (b.walking && pipe && pipe.open) {
|
||||
const pdx = pipe.x - x;
|
||||
const pdy = pipe.y - y;
|
||||
const pdist = Math.hypot(pdx, pdy);
|
||||
const PULL_RANGE = b.r * 7;
|
||||
if (pdist < PULL_RANGE) {
|
||||
const pullAmt = Math.pow(1 - pdist / PULL_RANGE, 1.6);
|
||||
const inv = pdist > 0.001 ? 1 / pdist : 0;
|
||||
const pullX = pdx * inv;
|
||||
const pullY = pdy * inv;
|
||||
this.drawPullTendril(g, x, y, pullX, pullY, b.r, pullAmt, base);
|
||||
}
|
||||
}
|
||||
|
||||
g.fillStyle(shade(base, 0.55), 1);
|
||||
g.fillCircle(x, y + 2, b.r);
|
||||
g.fillStyle(base, 1);
|
||||
|
|
@ -878,6 +922,53 @@ export default class GooTowerGame extends Phaser.Scene {
|
|||
}
|
||||
}
|
||||
|
||||
// A tapering blob stretching from (x, y) toward unit direction (dirX, dirY),
|
||||
// built the same way strands are (a perpendicular-offset polygon) rather
|
||||
// than a canvas rotate/scale, so it works for any pull direction without
|
||||
// having to un-rotate the body's fixed-light-source highlight. `amt` in
|
||||
// [0,1] drives both how far it reaches and how much it thins toward the
|
||||
// tip; `alpha` lets the post-collect fade dim it out as it finishes.
|
||||
drawPullTendril(g, x, y, dirX, dirY, r, amt, color, alpha = 1) {
|
||||
const len = r * (0.5 + 3.2 * amt);
|
||||
const nx = -dirY;
|
||||
const ny = dirX;
|
||||
const baseW = r * 2 * (1 - 0.2 * amt);
|
||||
const tipW = r * 2 * Math.max(0.05, 0.5 - 0.45 * amt);
|
||||
const steps = 6;
|
||||
const verts = [];
|
||||
for (let i = 0; i <= steps; i += 1) {
|
||||
const t = i / steps;
|
||||
const w = baseW + (tipW - baseW) * t;
|
||||
const cx = x + dirX * len * t;
|
||||
const cy = y + dirY * len * t;
|
||||
verts.push({ x: cx + nx * (w / 2), y: cy + ny * (w / 2) });
|
||||
}
|
||||
for (let i = steps; i >= 0; i -= 1) {
|
||||
const t = i / steps;
|
||||
const w = baseW + (tipW - baseW) * t;
|
||||
const cx = x + dirX * len * t;
|
||||
const cy = y + dirY * len * t;
|
||||
verts.push({ x: cx - nx * (w / 2), y: cy - ny * (w / 2) });
|
||||
}
|
||||
g.fillStyle(color, alpha);
|
||||
g.fillPoints(verts, true);
|
||||
}
|
||||
|
||||
// Short-lived cosmetic tail spawned when a ball is instantly collected by
|
||||
// the pipe (see handleEvents 'collect'). The sim has already deleted the
|
||||
// ball by the time this plays, so it stands in for a few frames to turn
|
||||
// that hard cut into a continuation of the pre-collect pull tendril above,
|
||||
// shrinking and reaching further in as it fades.
|
||||
drawSuckFX(g) {
|
||||
if (!this.suckFX.length) return;
|
||||
for (const fx of this.suckFX) {
|
||||
const t = Math.min(1, fx.t / fx.dur);
|
||||
const fade = 1 - t;
|
||||
if (fade <= 0.02) continue;
|
||||
this.drawPullTendril(g, fx.x, fx.y, fx.dirX, fx.dirY, fx.r * fade, 0.6 + 0.4 * t, fx.color, fade);
|
||||
}
|
||||
}
|
||||
|
||||
drawPipe(g) {
|
||||
const p = this.state.pipe;
|
||||
if (!p) return;
|
||||
|
|
|
|||
Loading…
Reference in New Issue