feat: add per-game soundtracks for multiple games
Introduce game-specific music overrides using the soundtrack service. Adds arcadedark and hacker music packs, and wires them to Balatro, Colorado Defense, Tempest, Mastermind, Hexsweeper, Dot Link, and 2048. Games now lazy-load their soundtrack assets on entry instead of sharing a single generic music cache.
This commit is contained in:
parent
e1a5c21180
commit
7713089466
Binary file not shown.
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"tracks": [
|
||||||
|
{
|
||||||
|
"file": "arcadedark-track01.mp3",
|
||||||
|
"artist": "Cthulu",
|
||||||
|
"title": "Press the Button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "arcadedark-track02.mp3",
|
||||||
|
"artist": "The Backrooms",
|
||||||
|
"title": "Subconscious Memories"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"volume": 0.8
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"tracks": [
|
||||||
|
{
|
||||||
|
"file": "hacker-track01.mp3",
|
||||||
|
"artist": "Cy-Bro",
|
||||||
|
"title": "Rock the Anomaly"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "hacker-track02.mp3",
|
||||||
|
"artist": "Nadia",
|
||||||
|
"title": "A time after now, but also before"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "hacker-track03.mp3",
|
||||||
|
"artist": "DV-8-2303",
|
||||||
|
"title": "Cybercrimes"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"volume": 0.3
|
||||||
|
}
|
||||||
|
|
@ -135,8 +135,13 @@ export const MANIFEST = {
|
||||||
dungeonboss: (scene) => sheetsFrom(scene, 'dungeonboss-artwork',
|
dungeonboss: (scene) => sheetsFrom(scene, 'dungeonboss-artwork',
|
||||||
['roomSheet', 'heroSheet', 'spellSheet', 'bossSheet', 'deckBackSheet', 'iconSheet']),
|
['roomSheet', 'heroSheet', 'spellSheet', 'bossSheet', 'deckBackSheet', 'iconSheet']),
|
||||||
swdbg: (scene) => sheetsFrom(scene, 'swdbg-artwork', ['cardSheet', 'baseSheet']),
|
swdbg: (scene) => sheetsFrom(scene, 'swdbg-artwork', ['cardSheet', 'baseSheet']),
|
||||||
balatro: (scene) => sheetsFrom(scene, 'balatro-artwork',
|
balatro: [
|
||||||
['jokerSheet', 'tarotSheet', 'planetSheet', 'spectralSheet', 'voucherSheet', 'packSheet', 'deckBackSheet', 'bossIconSheet']),
|
(scene) => sheetsFrom(scene, 'balatro-artwork',
|
||||||
|
['jokerSheet', 'tarotSheet', 'planetSheet', 'spectralSheet', 'voucherSheet', 'packSheet', 'deckBackSheet', 'bossIconSheet']),
|
||||||
|
// hacker soundtrack (see services/soundtrack.js) — lazy-loaded here so
|
||||||
|
// its audio only downloads once Balatro is actually entered.
|
||||||
|
(scene) => musicFrom(scene, 'hacker-music'),
|
||||||
|
],
|
||||||
// Optional drop-in art — spec in assets/gamedata/peggle/sprites.md. Missing
|
// Optional drop-in art — spec in assets/gamedata/peggle/sprites.md. Missing
|
||||||
// files 404 harmlessly and the game renders procedurally: circles for
|
// files 404 harmlessly and the game renders procedurally: circles for
|
||||||
// pegs/ball, gradient starfield for the board background.
|
// pegs/ball, gradient starfield for the board background.
|
||||||
|
|
@ -194,6 +199,36 @@ export const MANIFEST = {
|
||||||
// its audio only downloads once Super Kart is actually entered.
|
// its audio only downloads once Super Kart is actually entered.
|
||||||
(scene) => musicFrom(scene, 'nintendo-music'),
|
(scene) => musicFrom(scene, 'nintendo-music'),
|
||||||
],
|
],
|
||||||
|
coloradodefense: [
|
||||||
|
// arcadedark soundtrack (see services/soundtrack.js) — lazy-loaded here
|
||||||
|
// so its audio only downloads once Colorado Defense is actually entered.
|
||||||
|
(scene) => musicFrom(scene, 'arcadedark-music'),
|
||||||
|
],
|
||||||
|
tempest: [
|
||||||
|
// arcadedark soundtrack (see services/soundtrack.js) — lazy-loaded here
|
||||||
|
// so its audio only downloads once Tempest is actually entered.
|
||||||
|
(scene) => musicFrom(scene, 'arcadedark-music'),
|
||||||
|
],
|
||||||
|
mastermind: [
|
||||||
|
// hacker soundtrack (see services/soundtrack.js) — lazy-loaded here so
|
||||||
|
// its audio only downloads once Mastermind is actually entered.
|
||||||
|
(scene) => musicFrom(scene, 'hacker-music'),
|
||||||
|
],
|
||||||
|
hexsweeper: [
|
||||||
|
// hacker soundtrack (see services/soundtrack.js) — lazy-loaded here so
|
||||||
|
// its audio only downloads once Hexsweeper is actually entered.
|
||||||
|
(scene) => musicFrom(scene, 'hacker-music'),
|
||||||
|
],
|
||||||
|
dotlink: [
|
||||||
|
// hacker soundtrack (see services/soundtrack.js) — lazy-loaded here so
|
||||||
|
// its audio only downloads once Dot Link is actually entered.
|
||||||
|
(scene) => musicFrom(scene, 'hacker-music'),
|
||||||
|
],
|
||||||
|
'2048': [
|
||||||
|
// hacker soundtrack (see services/soundtrack.js) — lazy-loaded here so
|
||||||
|
// its audio only downloads once 2048 is actually entered.
|
||||||
|
(scene) => musicFrom(scene, 'hacker-music'),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns the full (normalized) descriptor list for a slug — including assets
|
// Returns the full (normalized) descriptor list for a slug — including assets
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import * as Phaser from 'phaser';
|
||||||
import { GAME_WIDTH, GAME_HEIGHT, COLORS } from '../../config.js';
|
import { GAME_WIDTH, GAME_HEIGHT, COLORS } from '../../config.js';
|
||||||
import { Button } from '../../ui/Button.js';
|
import { Button } from '../../ui/Button.js';
|
||||||
import { MusicPlayer } from '../../ui/MusicPlayer.js';
|
import { MusicPlayer } from '../../ui/MusicPlayer.js';
|
||||||
|
import { getGameSoundtrack } from '../../services/soundtrack.js';
|
||||||
import { playSound, SFX } from '../../ui/Sounds.js';
|
import { playSound, SFX } from '../../ui/Sounds.js';
|
||||||
import { api } from '../../services/api.js';
|
import { api } from '../../services/api.js';
|
||||||
import {
|
import {
|
||||||
|
|
@ -55,8 +56,8 @@ export default class Game2048 extends Phaser.Scene {
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
try {
|
try {
|
||||||
const music = this.cache.json.get('music');
|
const { tracks, volume } = getGameSoundtrack(this);
|
||||||
if (music?.tracks) new MusicPlayer(this, music.tracks);
|
if (tracks.length) new MusicPlayer(this, tracks, volume);
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
this.schemes = this.cache.json.get('colored-playfields')?.schemes ?? [];
|
this.schemes = this.cache.json.get('colored-playfields')?.schemes ?? [];
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { GAME_WIDTH, GAME_HEIGHT, COLORS } from '../../config.js';
|
||||||
import { Button } from '../../ui/Button.js';
|
import { Button } from '../../ui/Button.js';
|
||||||
import { playSound, SFX } from '../../ui/Sounds.js';
|
import { playSound, SFX } from '../../ui/Sounds.js';
|
||||||
import { MusicPlayer } from '../../ui/MusicPlayer.js';
|
import { MusicPlayer } from '../../ui/MusicPlayer.js';
|
||||||
|
import { getGameSoundtrack } from '../../services/soundtrack.js';
|
||||||
import { api } from '../../services/api.js';
|
import { api } from '../../services/api.js';
|
||||||
import * as store from '../../services/localStore.js';
|
import * as store from '../../services/localStore.js';
|
||||||
import {
|
import {
|
||||||
|
|
@ -85,7 +86,10 @@ export default class BalatroGame extends Phaser.Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
try { const m = this.cache.json.get('music'); if (m?.tracks) new MusicPlayer(this, m.tracks); } catch (_) {}
|
try {
|
||||||
|
const { tracks, volume } = getGameSoundtrack(this);
|
||||||
|
if (tracks.length) new MusicPlayer(this, tracks, volume);
|
||||||
|
} catch (_) {}
|
||||||
this.art = this.cache.json.get('balatro-artwork') || {};
|
this.art = this.cache.json.get('balatro-artwork') || {};
|
||||||
|
|
||||||
if (!this.textures.exists('balatro-spark')) {
|
if (!this.textures.exists('balatro-spark')) {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import * as Phaser from 'phaser';
|
||||||
import { GAME_WIDTH, GAME_HEIGHT, COLORS } from '../../config.js';
|
import { GAME_WIDTH, GAME_HEIGHT, COLORS } from '../../config.js';
|
||||||
import { Button } from '../../ui/Button.js';
|
import { Button } from '../../ui/Button.js';
|
||||||
import { MusicPlayer } from '../../ui/MusicPlayer.js';
|
import { MusicPlayer } from '../../ui/MusicPlayer.js';
|
||||||
|
import { getGameSoundtrack } from '../../services/soundtrack.js';
|
||||||
import { playSound, SFX } from '../../ui/Sounds.js';
|
import { playSound, SFX } from '../../ui/Sounds.js';
|
||||||
import { api } from '../../services/api.js';
|
import { api } from '../../services/api.js';
|
||||||
import { applyArcadeCRTOverlay } from '../../ui/ArcadeCRTOverlay.js';
|
import { applyArcadeCRTOverlay } from '../../ui/ArcadeCRTOverlay.js';
|
||||||
|
|
@ -50,8 +51,8 @@ export default class ColoradoDefenseGame extends Phaser.Scene {
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
try {
|
try {
|
||||||
const music = this.cache.json.get('music');
|
const { tracks, volume } = getGameSoundtrack(this);
|
||||||
if (music?.tracks) this.music = new MusicPlayer(this, music.tracks);
|
if (tracks.length) this.music = new MusicPlayer(this, tracks, volume);
|
||||||
} catch (_) { /* optional */ }
|
} catch (_) { /* optional */ }
|
||||||
this.input.mouse?.disableContextMenu();
|
this.input.mouse?.disableContextMenu();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import * as Phaser from 'phaser';
|
||||||
import { GAME_WIDTH, GAME_HEIGHT, COLORS } from '../../config.js';
|
import { GAME_WIDTH, GAME_HEIGHT, COLORS } from '../../config.js';
|
||||||
import { Button } from '../../ui/Button.js';
|
import { Button } from '../../ui/Button.js';
|
||||||
import { MusicPlayer } from '../../ui/MusicPlayer.js';
|
import { MusicPlayer } from '../../ui/MusicPlayer.js';
|
||||||
|
import { getGameSoundtrack } from '../../services/soundtrack.js';
|
||||||
import { playSound, SFX, playDotLinkSound, playScifiLaunch } from '../../ui/Sounds.js';
|
import { playSound, SFX, playDotLinkSound, playScifiLaunch } from '../../ui/Sounds.js';
|
||||||
import { api } from '../../services/api.js';
|
import { api } from '../../services/api.js';
|
||||||
import {
|
import {
|
||||||
|
|
@ -94,8 +95,8 @@ export default class DotLinkGame extends Phaser.Scene {
|
||||||
|
|
||||||
async create() {
|
async create() {
|
||||||
try {
|
try {
|
||||||
const music = this.cache.json.get('music');
|
const { tracks, volume } = getGameSoundtrack(this);
|
||||||
if (music?.tracks) new MusicPlayer(this, music.tracks);
|
if (tracks.length) new MusicPlayer(this, tracks, volume);
|
||||||
} catch (_) { /* optional */ }
|
} catch (_) { /* optional */ }
|
||||||
|
|
||||||
this.buildTextures();
|
this.buildTextures();
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import * as Phaser from 'phaser';
|
||||||
import { GAME_WIDTH, GAME_HEIGHT, COLORS } from '../../config.js';
|
import { GAME_WIDTH, GAME_HEIGHT, COLORS } from '../../config.js';
|
||||||
import { Button } from '../../ui/Button.js';
|
import { Button } from '../../ui/Button.js';
|
||||||
import { MusicPlayer } from '../../ui/MusicPlayer.js';
|
import { MusicPlayer } from '../../ui/MusicPlayer.js';
|
||||||
|
import { getGameSoundtrack } from '../../services/soundtrack.js';
|
||||||
import { playSound, SFX } from '../../ui/Sounds.js';
|
import { playSound, SFX } from '../../ui/Sounds.js';
|
||||||
import { api } from '../../services/api.js';
|
import { api } from '../../services/api.js';
|
||||||
import {
|
import {
|
||||||
|
|
@ -44,8 +45,8 @@ export default class HexsweeperGame extends Phaser.Scene {
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
try {
|
try {
|
||||||
const music = this.cache.json.get('music');
|
const { tracks, volume } = getGameSoundtrack(this);
|
||||||
if (music?.tracks) new MusicPlayer(this, music.tracks);
|
if (tracks.length) new MusicPlayer(this, tracks, volume);
|
||||||
} catch (_) { /* optional */ }
|
} catch (_) { /* optional */ }
|
||||||
|
|
||||||
// Right-click should flag, not open the browser context menu.
|
// Right-click should flag, not open the browser context menu.
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { Button } from '../../ui/Button.js';
|
||||||
import { createOpponentPortrait } from '../../ui/Portrait.js';
|
import { createOpponentPortrait } from '../../ui/Portrait.js';
|
||||||
import { playSound, SFX } from '../../ui/Sounds.js';
|
import { playSound, SFX } from '../../ui/Sounds.js';
|
||||||
import { MusicPlayer } from '../../ui/MusicPlayer.js';
|
import { MusicPlayer } from '../../ui/MusicPlayer.js';
|
||||||
|
import { getGameSoundtrack } from '../../services/soundtrack.js';
|
||||||
import {
|
import {
|
||||||
makeConfig, createInitialState, setPlayerCode, applyGuess, isGameOver,
|
makeConfig, createInitialState, setPlayerCode, applyGuess, isGameOver,
|
||||||
} from './MastermindLogic.js';
|
} from './MastermindLogic.js';
|
||||||
|
|
@ -73,7 +74,10 @@ export default class MastermindGame extends Phaser.Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
try { new MusicPlayer(this, this.cache.json.get('music')?.tracks ?? []); } catch (_) {}
|
try {
|
||||||
|
const { tracks, volume } = getGameSoundtrack(this);
|
||||||
|
if (tracks.length) new MusicPlayer(this, tracks, volume);
|
||||||
|
} catch (_) {}
|
||||||
this.gs = createInitialState(this.config);
|
this.gs = createInitialState(this.config);
|
||||||
|
|
||||||
this.buildTextures();
|
this.buildTextures();
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import * as Phaser from 'phaser';
|
||||||
import { GAME_WIDTH, GAME_HEIGHT, COLORS } from '../../config.js';
|
import { GAME_WIDTH, GAME_HEIGHT, COLORS } from '../../config.js';
|
||||||
import { Button } from '../../ui/Button.js';
|
import { Button } from '../../ui/Button.js';
|
||||||
import { MusicPlayer } from '../../ui/MusicPlayer.js';
|
import { MusicPlayer } from '../../ui/MusicPlayer.js';
|
||||||
|
import { getGameSoundtrack } from '../../services/soundtrack.js';
|
||||||
import { playSound, SFX } from '../../ui/Sounds.js';
|
import { playSound, SFX } from '../../ui/Sounds.js';
|
||||||
import { api } from '../../services/api.js';
|
import { api } from '../../services/api.js';
|
||||||
import { applyArcadeCRTOverlay } from '../../ui/ArcadeCRTOverlay.js';
|
import { applyArcadeCRTOverlay } from '../../ui/ArcadeCRTOverlay.js';
|
||||||
|
|
@ -43,8 +44,8 @@ export default class TempestGame extends Phaser.Scene {
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
try {
|
try {
|
||||||
const music = this.cache.json.get('music');
|
const { tracks, volume } = getGameSoundtrack(this);
|
||||||
if (music?.tracks) this.music = new MusicPlayer(this, music.tracks);
|
if (tracks.length) this.music = new MusicPlayer(this, tracks, volume);
|
||||||
} catch (_) { /* optional */ }
|
} catch (_) { /* optional */ }
|
||||||
this.input.mouse?.disableContextMenu();
|
this.input.mouse?.disableContextMenu();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ export default class PreloadScene extends Phaser.Scene {
|
||||||
this.load.json('card-backs', 'data/card-backs.json');
|
this.load.json('card-backs', 'data/card-backs.json');
|
||||||
this.load.json('music', 'data/music.json');
|
this.load.json('music', 'data/music.json');
|
||||||
this.load.json('nintendo-music', 'data/nintendo-music.json');
|
this.load.json('nintendo-music', 'data/nintendo-music.json');
|
||||||
|
this.load.json('arcadedark-music', 'data/arcadedark-music.json');
|
||||||
|
this.load.json('hacker-music', 'data/hacker-music.json');
|
||||||
this.load.json('rushhour', 'data/rushhour.json');
|
this.load.json('rushhour', 'data/rushhour.json');
|
||||||
this.load.json('puddingmonsters', 'data/puddingmonsters.json');
|
this.load.json('puddingmonsters', 'data/puddingmonsters.json');
|
||||||
this.load.json('shift-artwork', 'data/shift-artwork.json');
|
this.load.json('shift-artwork', 'data/shift-artwork.json');
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,13 @@
|
||||||
// if (tracks.length) this.music = new MusicPlayer(this, tracks, volume);
|
// if (tracks.length) this.music = new MusicPlayer(this, tracks, volume);
|
||||||
export const GAME_SOUNDTRACK_OVERRIDES = {
|
export const GAME_SOUNDTRACK_OVERRIDES = {
|
||||||
superkart: 'nintendo',
|
superkart: 'nintendo',
|
||||||
|
coloradodefense: 'arcadedark',
|
||||||
|
tempest: 'arcadedark',
|
||||||
|
mastermind: 'hacker',
|
||||||
|
balatro: 'hacker',
|
||||||
|
hexsweeper: 'hacker',
|
||||||
|
dotlink: 'hacker',
|
||||||
|
'2048': 'hacker',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Resolve the track list (and optional volume override) a game scene's
|
// Resolve the track list (and optional volume override) a game scene's
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue