commit ebdfc6c300fdb1df587bed1308a8ab371f3857da
parent 5cd1a313cc114847f206db35eb7c5a2c503c42ac
Author: Kyle Barlow <kb@kylebarlow.com>
Date: Sat, 2 May 2026 11:33:53 -0700
fire-truck: aim water at building center, add spray sound
Water spray target was using fireCell (road) + CELL_SIZE/2 offset,
landing it half a cell off from the building. Changed target to
fireBuildingCell center which is where the flame animation renders.
Added looping white-noise (bandpass-filtered) water spray sound that
starts when SPACE is held and fades out on release or extinguish.
initAudio() is now called on the Space keydown so the AudioContext
is ready before the first spray.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat:
1 file changed, 48 insertions(+), 3 deletions(-)
diff --git a/games/fire-truck/game.js b/games/fire-truck/game.js
@@ -57,6 +57,8 @@ class FireTruckScene extends Phaser.Scene {
this.spaceHeld = false;
this.fireMode = false;
this._stallSince = 0;
+ this.waterSoundSource = null;
+ this.waterSoundGain = null;
}
create() {
@@ -297,6 +299,7 @@ class FireTruckScene extends Phaser.Scene {
onKeyDown(event) {
if (event.code === 'Space') {
+ this.initAudio();
if (this.state === 'firefighting') this.spaceHeld = true;
return;
}
@@ -662,10 +665,12 @@ class FireTruckScene extends Phaser.Scene {
}
_updateWaterSpray() {
- if (!this.spaceHeld || !this.fireCell) {
+ if (!this.spaceHeld || !this.fireCell || !this.fireBuildingCell) {
if (this.waterGraphics) this.waterGraphics.clear();
+ this._stopWaterSound();
return;
}
+ this._startWaterSound();
if (!this.waterGraphics) {
this.waterGraphics = this.add.graphics();
this.waterGraphics.setDepth(11);
@@ -673,8 +678,8 @@ class FireTruckScene extends Phaser.Scene {
}
const gfx = this.waterGraphics;
gfx.clear();
- const tx = this.fireCell.x * CELL_SIZE + CELL_SIZE / 2;
- const ty = this.fireCell.y * CELL_SIZE + CELL_SIZE / 2;
+ const tx = this.fireBuildingCell.x * CELL_SIZE;
+ const ty = this.fireBuildingCell.y * CELL_SIZE;
const sx = this.truck.x;
const sy = this.truck.y;
gfx.lineStyle(8 * SCALE, 0x44aaff, 0.75);
@@ -689,6 +694,44 @@ class FireTruckScene extends Phaser.Scene {
}
}
+ _startWaterSound() {
+ if (this.waterSoundSource || !this.audioCtx) return;
+ try {
+ const ctx = this.audioCtx;
+ const bufferSize = ctx.sampleRate;
+ const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
+ const data = buffer.getChannelData(0);
+ for (let i = 0; i < bufferSize; i++) data[i] = Math.random() * 2 - 1;
+ const source = ctx.createBufferSource();
+ source.buffer = buffer;
+ source.loop = true;
+ const filter = ctx.createBiquadFilter();
+ filter.type = 'bandpass';
+ filter.frequency.value = 1200;
+ filter.Q.value = 0.6;
+ const gain = ctx.createGain();
+ gain.gain.value = 0.14;
+ source.connect(filter);
+ filter.connect(gain);
+ gain.connect(ctx.destination);
+ source.start();
+ this.waterSoundSource = source;
+ this.waterSoundGain = gain;
+ } catch (_) {}
+ }
+
+ _stopWaterSound() {
+ if (!this.waterSoundSource) return;
+ try {
+ const ctx = this.audioCtx;
+ this.waterSoundGain.gain.setValueAtTime(this.waterSoundGain.gain.value, ctx.currentTime);
+ this.waterSoundGain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.08);
+ this.waterSoundSource.stop(ctx.currentTime + 0.08);
+ } catch (_) {}
+ this.waterSoundSource = null;
+ this.waterSoundGain = null;
+ }
+
enterFirefighting() {
this.state = 'firefighting';
this.targetSpeed = 0;
@@ -707,6 +750,7 @@ class FireTruckScene extends Phaser.Scene {
this.successOverlay.setAlpha(0.12);
this.successUntil = this.time.now + 200;
this.playSuccessSound();
+ this._stopWaterSound();
if (this.fireGraphics) { this.fireGraphics.destroy(); this.fireGraphics = null; }
if (this.waterGraphics) { this.waterGraphics.destroy(); this.waterGraphics = null; }
this.fireCell = null;
@@ -749,6 +793,7 @@ class FireTruckScene extends Phaser.Scene {
clearInterval(this.fallbackTimer);
this.fallbackTimer = null;
}
+ this._stopWaterSound();
if (this.fireGraphics) { this.fireGraphics.destroy(); this.fireGraphics = null; }
if (this.waterGraphics) { this.waterGraphics.destroy(); this.waterGraphics = null; }
}