Add Tents sound effects and intro jingle #2
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -515,6 +515,16 @@ export const MANIFEST = {
|
||||||
{ type: 'audio', key: 'sfx-zuma-win', path: 'assets/fx/zuma-win.mp3' },
|
{ type: 'audio', key: 'sfx-zuma-win', path: 'assets/fx/zuma-win.mp3' },
|
||||||
{ type: 'audio', key: 'sfx-zuma-lose', path: 'assets/fx/zuma-lose.mp3' },
|
{ type: 'audio', key: 'sfx-zuma-lose', path: 'assets/fx/zuma-lose.mp3' },
|
||||||
],
|
],
|
||||||
|
tents: [
|
||||||
|
// Tent placement/removal cues and the win jingle (played by TentsGame
|
||||||
|
// via the SFX.TENTS_* keys in src/ui/Sounds.js).
|
||||||
|
{ type: 'audio', key: 'sfx-tents-place', path: 'assets/fx/tents-place.mp3' },
|
||||||
|
{ type: 'audio', key: 'sfx-tents-remove', path: 'assets/fx/tents-remove.mp3' },
|
||||||
|
{ type: 'audio', key: 'sfx-tents-win', path: 'assets/fx/tents-win.mp3' },
|
||||||
|
// Puzzle-start intro jingle — TentsGame.startGame ducks the soundtrack
|
||||||
|
// for its duration (same pattern as Zuma's level-start fanfare).
|
||||||
|
{ type: 'audio', key: 'sfx-tents-intro', path: 'assets/music/tents-intro.mp3' },
|
||||||
|
],
|
||||||
'2048': [
|
'2048': [
|
||||||
// hacker soundtrack (see services/soundtrack.js) — lazy-loaded here so
|
// hacker soundtrack (see services/soundtrack.js) — lazy-loaded here so
|
||||||
// its audio only downloads once 2048 is actually entered.
|
// its audio only downloads once 2048 is actually entered.
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ export default class TentsGame extends Phaser.Scene {
|
||||||
create() {
|
create() {
|
||||||
try {
|
try {
|
||||||
const { tracks, volume } = getGameSoundtrack(this);
|
const { tracks, volume } = getGameSoundtrack(this);
|
||||||
if (tracks.length) new MusicPlayer(this, tracks, volume);
|
if (tracks.length) this.music = new MusicPlayer(this, tracks, volume);
|
||||||
} catch (_) { /* no soundtrack available */ }
|
} catch (_) { /* no soundtrack available */ }
|
||||||
|
|
||||||
this.input.mouse?.disableContextMenu?.();
|
this.input.mouse?.disableContextMenu?.();
|
||||||
|
|
@ -318,6 +318,9 @@ export default class TentsGame extends Phaser.Scene {
|
||||||
this.tweens.killTweensOf(this.sky);
|
this.tweens.killTweensOf(this.sky);
|
||||||
this.sky.t = 0;
|
this.sky.t = 0;
|
||||||
|
|
||||||
|
// Puzzle-start intro — ducks the soundtrack for its own length.
|
||||||
|
this.playJingle(SFX.TENTS_INTRO);
|
||||||
|
|
||||||
const def = DIFFICULTIES[difficultyKey] ?? DIFFICULTIES.porch;
|
const def = DIFFICULTIES[difficultyKey] ?? DIFFICULTIES.porch;
|
||||||
this.view = 'play';
|
this.view = 'play';
|
||||||
this.selectLayer.setVisible(false);
|
this.selectLayer.setVisible(false);
|
||||||
|
|
@ -393,11 +396,11 @@ export default class TentsGame extends Phaser.Scene {
|
||||||
|
|
||||||
if (res.placed) {
|
if (res.placed) {
|
||||||
this.pop.set(keyOf(c, r), this.time.now);
|
this.pop.set(keyOf(c, r), this.time.now);
|
||||||
playSoundEx(this, SFX.UI_ACTIVATE, { volume: 0.8 });
|
playSoundEx(this, SFX.TENTS_PLACE, { volume: 0.8 });
|
||||||
this.checkViolationFeedback();
|
this.checkViolationFeedback();
|
||||||
} else {
|
} else {
|
||||||
this.pop.delete(keyOf(c, r));
|
this.pop.delete(keyOf(c, r));
|
||||||
playSoundEx(this, SFX.UI_PICK, { volume: 0.7 });
|
playSoundEx(this, SFX.TENTS_REMOVE, { volume: 0.7 });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateHud();
|
this.updateHud();
|
||||||
|
|
@ -437,7 +440,7 @@ export default class TentsGame extends Phaser.Scene {
|
||||||
this.flash.until = 0;
|
this.flash.until = 0;
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
this.updateHud();
|
this.updateHud();
|
||||||
playSoundEx(this, SFX.UI_PICK, { volume: 0.6 });
|
playSoundEx(this, SFX.TENTS_REMOVE, { volume: 0.6 });
|
||||||
}
|
}
|
||||||
|
|
||||||
revealAnswer() {
|
revealAnswer() {
|
||||||
|
|
@ -451,7 +454,7 @@ export default class TentsGame extends Phaser.Scene {
|
||||||
this.g.tents.add(k);
|
this.g.tents.add(k);
|
||||||
this.pop.set(k, this.time.now);
|
this.pop.set(k, this.time.now);
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
playSoundEx(this, SFX.UI_ACTIVATE, { volume: 0.55 });
|
playSoundEx(this, SFX.TENTS_PLACE, { volume: 0.55 });
|
||||||
}));
|
}));
|
||||||
this.time.delayedCall(75 * missing.length + 180, () => {
|
this.time.delayedCall(75 * missing.length + 180, () => {
|
||||||
this.updateHud();
|
this.updateHud();
|
||||||
|
|
@ -460,10 +463,25 @@ export default class TentsGame extends Phaser.Scene {
|
||||||
this.btnAnswer.setEnabled(false);
|
this.btnAnswer.setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Duck the soundtrack for a one-shot jingle (puzzle-start intro, win),
|
||||||
|
// then hand playback back to MusicPlayer when the jingle ends — not a
|
||||||
|
// fixed delay, since the jingle's own length is what decides when the
|
||||||
|
// music comes back (same pattern as Zuma's start/lose fanfares).
|
||||||
|
playJingle(key, volume = 0.9) {
|
||||||
|
this.music?.pause();
|
||||||
|
try {
|
||||||
|
const sfx = this.sound.add(key, { volume });
|
||||||
|
sfx.once(Phaser.Sound.Events.COMPLETE, () => this.music?.resume());
|
||||||
|
sfx.play();
|
||||||
|
} catch (_) {
|
||||||
|
this.music?.resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
startWin() {
|
startWin() {
|
||||||
if (this.won) return;
|
if (this.won) return;
|
||||||
this.won = true;
|
this.won = true;
|
||||||
playSoundEx(this, SFX.VICTORY_SHORT, { volume: 0.9 });
|
this.playJingle(SFX.TENTS_WIN);
|
||||||
this.toast('Every tent is pitched… the sun is going down.', 950);
|
this.toast('Every tent is pitched… the sun is going down.', 950);
|
||||||
|
|
||||||
// Nightfall: ~6.5s, then the cozy panel.
|
// Nightfall: ~6.5s, then the cozy panel.
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,12 @@ export const SFX = {
|
||||||
KART_SHELL: 'sfx-kart-shell',
|
KART_SHELL: 'sfx-kart-shell',
|
||||||
KART_STAR: 'sfx-kart-star',
|
KART_STAR: 'sfx-kart-star',
|
||||||
KART_SPINNER: 'sfx-kart-spinner',
|
KART_SPINNER: 'sfx-kart-spinner',
|
||||||
|
// Tents & Trees cues (lazy-loaded via the `tents` entry in
|
||||||
|
// data/assetManifest.js when the game room is entered).
|
||||||
|
TENTS_PLACE: 'sfx-tents-place',
|
||||||
|
TENTS_REMOVE: 'sfx-tents-remove',
|
||||||
|
TENTS_WIN: 'sfx-tents-win',
|
||||||
|
TENTS_INTRO: 'sfx-tents-intro',
|
||||||
VEGA_SELECT: 'sfx-vega-select',
|
VEGA_SELECT: 'sfx-vega-select',
|
||||||
VEGA_BUILD: 'sfx-vega-build',
|
VEGA_BUILD: 'sfx-vega-build',
|
||||||
VEGA_CLOSE: 'sfx-vega-close',
|
VEGA_CLOSE: 'sfx-vega-close',
|
||||||
|
|
|
||||||
|
|
@ -28,13 +28,18 @@ function stub() {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sfxPlayed = []; // keys passed to scene.sound.play
|
||||||
|
const sfxAdded = []; // keys passed to scene.sound.add (one-shot jingles)
|
||||||
const s = new TentsGame();
|
const s = new TentsGame();
|
||||||
|
|
||||||
// Wire up scene services.
|
// Wire up scene services.
|
||||||
s.add = stub();
|
s.add = stub();
|
||||||
s.scale = { setZoom() {} };
|
s.scale = { setZoom() {} };
|
||||||
s.input = { mouse: { disableContextMenu() {} } };
|
s.input = { mouse: { disableContextMenu() {} } };
|
||||||
s.sound = { play() {} };
|
s.sound = {
|
||||||
|
play(key) { sfxPlayed.push(key); },
|
||||||
|
add(key) { sfxAdded.push(key); return { once() {}, play() {} }; },
|
||||||
|
};
|
||||||
s.cache = { json: { get: () => null } };
|
s.cache = { json: { get: () => null } };
|
||||||
s.scene = { start() {} };
|
s.scene = { start() {} };
|
||||||
s.events = { on() {}, once() {} };
|
s.events = { on() {}, once() {} };
|
||||||
|
|
@ -63,6 +68,7 @@ check('select screen built', Array.isArray(s.selectLayer) === false && s.selectL
|
||||||
// ── Start a game ──
|
// ── Start a game ──
|
||||||
s.startGame('porch');
|
s.startGame('porch');
|
||||||
check('game state exists', s.g && s.g.puzzle && s.g.tents instanceof Set);
|
check('game state exists', s.g && s.g.puzzle && s.g.tents instanceof Set);
|
||||||
|
check('intro jingle queued on start', sfxAdded.includes('sfx-tents-intro'), `added=${sfxAdded}`);
|
||||||
check('board layout computed', typeof s.cell === 'number' && s.cell > 20 && s.cell < 140);
|
check('board layout computed', typeof s.cell === 'number' && s.cell > 20 && s.cell < 140);
|
||||||
check('zones built', s.cellZones.length === s.g.puzzle.size ** 2);
|
check('zones built', s.cellZones.length === s.g.puzzle.size ** 2);
|
||||||
check('labels built', s.colLabels.length === s.g.puzzle.size && s.rowLabels.length === s.g.puzzle.size);
|
check('labels built', s.colLabels.length === s.g.puzzle.size && s.rowLabels.length === s.g.puzzle.size);
|
||||||
|
|
@ -88,7 +94,9 @@ for (let r = 0; r < p.size; r++) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
check('all solution tents placed', s.g.tents.size === p.trees.length && placed === p.trees.length, `tents=${s.g.tents.size} placed=${placed} trees=${p.trees.length}`);
|
check('all solution tents placed', s.g.tents.size === p.trees.length && placed === p.trees.length, `tents=${s.g.tents.size} placed=${placed} trees=${p.trees.length}`);
|
||||||
|
check('place cue played', sfxPlayed.includes('sfx-tents-place'), `played=${sfxPlayed}`);
|
||||||
check('win triggered', s.won === true);
|
check('win triggered', s.won === true);
|
||||||
|
check('win jingle queued', sfxAdded.includes('sfx-tents-win'), `added=${sfxAdded}`);
|
||||||
check('sky tween scheduled', s.tweens.tweens.some((t) => t.targets === s.sky));
|
check('sky tween scheduled', s.tweens.tweens.some((t) => t.targets === s.sky));
|
||||||
|
|
||||||
// ── Nightfall: animate sky to night and render ──
|
// ── Nightfall: animate sky to night and render ──
|
||||||
|
|
@ -126,6 +134,7 @@ for (const [c, r] of half) s.onCellClick(c, r);
|
||||||
check('partial placement ok', s.g.tents.size === 4, `tents=${s.g.tents.size}`);
|
check('partial placement ok', s.g.tents.size === 4, `tents=${s.g.tents.size}`);
|
||||||
s.resetTents();
|
s.resetTents();
|
||||||
check('reset clears tents', s.g.tents.size === 0);
|
check('reset clears tents', s.g.tents.size === 0);
|
||||||
|
check('remove cue played', sfxPlayed.includes('sfx-tents-remove'), `played=${sfxPlayed}`);
|
||||||
|
|
||||||
// ── All difficulties generate + solve ──
|
// ── All difficulties generate + solve ──
|
||||||
for (const key of ['porch', 'clearing', 'meadow', 'deepwoods']) {
|
for (const key of ['porch', 'clearing', 'meadow', 'deepwoods']) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue