feat(jewelquest): enhance battle visuals and add sword SFX

- Add dedicated battle background image, separate from non-battle screens
- Move felt background to render after textures are loaded
- Fix callout text z-ordering so it always renders above jewels/FX
- Play sword-hit SFX on skull matches, sword-slice on damage
- Register new audio assets and sound constants
This commit is contained in:
Brian Fertig 2026-06-11 21:06:36 -06:00
parent d01a2917b1
commit c74fc88e04
7 changed files with 18 additions and 5 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

View File

@ -57,7 +57,7 @@ export default class JewelQuestGame extends Phaser.Scene {
if (music?.tracks) new MusicPlayer(this, music.tracks);
} catch (_) { /* optional */ }
this.add.rectangle(GAME_WIDTH / 2, GAME_HEIGHT / 2, GAME_WIDTH, GAME_HEIGHT, FELT).setDepth(D.felt);
const raw = this.cache.json.get('jewelquest');
this.config = raw ?? this.config;
@ -83,6 +83,9 @@ export default class JewelQuestGame extends Phaser.Scene {
} catch (_) { /* private mode */ }
this.makeTextures();
// Solid felt background for non-battle screens (class select, level select, intro)
this.add.rectangle(GAME_WIDTH / 2, GAME_HEIGHT / 2, GAME_WIDTH, GAME_HEIGHT, FELT).setDepth(D.felt);
this.layer = this.add.container(0, 0);
if (this.playerClass) this.showLevelSelect();
else this.showClassSelect();
@ -197,6 +200,7 @@ export default class JewelQuestGame extends Phaser.Scene {
this.selected = null;
this.dragFrom = null;
this.layer.removeAll(true);
if (this.calloutText) { this.calloutText.destroy(); this.calloutText = null; }
this.cellSprites = null;
this.spellButtons = null;
}
@ -540,6 +544,9 @@ export default class JewelQuestGame extends Phaser.Scene {
}
drawBattleChrome() {
// Battle-only background
this.add.image(GAME_WIDTH / 2, GAME_HEIGHT / 2, 'bg-jewelquest-battle').setDisplaySize(GAME_WIDTH, GAME_HEIGHT).setDepth(D.felt);
const cx = GAME_WIDTH / 2;
const hud = this.add.text(cx, 52, `Level ${this.level} — vs ${this.opponent.name}`, {
fontFamily: 'Righteous', fontSize: '36px', color: COLORS.goldHex,
@ -593,11 +600,10 @@ export default class JewelQuestGame extends Phaser.Scene {
this.drawSpellPanel();
// callout
// callout — not in container so it always renders above jewels/fx
this.calloutText = this.add.text(cx, 540, '', {
fontFamily: 'Righteous', fontSize: '54px', color: COLORS.goldHex, stroke: '#000000', strokeThickness: 6,
}).setOrigin(0.5).setDepth(D.fx).setAlpha(0);
this.layer.add(this.calloutText);
}).setOrigin(0.5).setDepth(D.ui + 5).setAlpha(0);
const quit = new Button(this, 140, GAME_HEIGHT - 50, 'Levels', () => this.showLevelSelect(),
{ variant: 'ghost', width: 180, height: 52, fontSize: 22 });
@ -894,7 +900,8 @@ export default class JewelQuestGame extends Phaser.Scene {
this.flashCells([e.a, e.b], 0xffffff);
break;
case 'clear': {
playSound(this, SFX.MASTERMIND_MATCH ?? SFX.CARD_SHOW);
const hasSkullMatch = (e.groups ?? []).some((grp) => grp.cls === 'skull' && grp.cells.length >= 3);
playSound(this, hasSkullMatch ? SFX.SWORD_HIT : (SFX.MASTERMIND_MATCH ?? SFX.CARD_SHOW));
const actorPanel = PANEL_X[e.actor];
const foePanel = PANEL_X[1 - e.actor];
for (const grp of e.groups ?? []) {
@ -929,6 +936,7 @@ export default class JewelQuestGame extends Phaser.Scene {
break;
}
case 'damage':
playSound(this, SFX.SWORD_SLICE);
this.floatText(PANEL_X[e.target], 320, `-${e.amount}`, GEM_HEX.red, PANEL_X[e.target], 376);
this.updateMetersFrom(e);
break;

View File

@ -55,6 +55,7 @@ export default class PreloadScene extends Phaser.Scene {
this.load.image('bg-menu', '/assets/images/background-menu.png');
this.load.image('bg-room', '/assets/images/background-room.png');
this.load.image('bg-casino', '/assets/images/background-casino.png');
this.load.image('bg-jewelquest-battle', '/assets/images/background-jewelquest.png');
this.load.image('main-title', '/assets/images/main-title.png');
this.load.json('playfields', '/data/playfields.json');
this.load.json('card-backs', '/data/card-backs.json');
@ -100,6 +101,8 @@ export default class PreloadScene extends Phaser.Scene {
this.load.audio('sfx-battleship-miss', '/assets/fx/battleship-miss.mp3');
this.load.audio('sfx-battleship-launch', '/assets/fx/battleship-launch.mp3');
this.load.audio('sfx-victory-short', '/assets/fx/victory-short.mp3');
this.load.audio('sfx-sword-hit', '/assets/fx/sword-hit.mp3');
this.load.audio('sfx-sword-slice', '/assets/fx/sword-slice.mp3');
this.load.audio('sfx-scifi-launch', '/assets/fx/scifi-launch.mp3');
this.load.audio('sfx-scifi-explode', '/assets/fx/scifi-explode.mp3');
this.load.audio('sfx-scifi-riser', '/assets/fx/scifi-riser.mp3');

View File

@ -32,6 +32,8 @@ export const SFX = {
SCIFI_RISER: 'sfx-scifi-riser',
SCIFI_REVEAL: 'sfx-scifi-reveal',
SCIFI_WOOSH: 'sfx-scifi-woosh',
SWORD_HIT: 'sfx-sword-hit',
SWORD_SLICE: 'sfx-sword-slice',
MONOPOLY_PURCHASE: 'sfx-monopoly-purchase',
MONOPOLY_EXPENSE: 'sfx-monopoly-expense',
MONOPAY: 'sfx-monopoly-pay',