feat: Add sound effects for battle actions and menu interactions
Integrate audio feedback across the game by adding new SFX assets and loading them in `BootScene`. Key changes include: - Added 12 new sound effect files (attacks, abilities, UI) and a main menu music track. - Implemented SFX playback in `BattleScene` for actions like attacks, counters, mortar/strike/siege/pierce fires, buffs (rally), shields (protect), damage, destructions, and legendary card plays. - Stopped main menu music during battles to avoid audio overlap and resumed it upon returning to `MainMenuScene`. - Added hover and select sounds for UI buttons in `MainMenuScene`. - Fixed deck composition in `DeckManager` by removing one 'imp_trooper_1' from the starter deck.
This commit is contained in:
parent
3c92d58055
commit
850a32c33f
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -27,7 +27,7 @@ export class DeckManager {
|
||||||
name: 'Starter Deck',
|
name: 'Starter Deck',
|
||||||
commander: 'imp_cmd_1',
|
commander: 'imp_cmd_1',
|
||||||
cards: [
|
cards: [
|
||||||
'imp_trooper_1', 'imp_trooper_1', 'imp_trooper_1', 'imp_trooper_1',
|
'imp_trooper_1', 'imp_trooper_1', 'imp_trooper_1',
|
||||||
'imp_gunner_1', 'imp_gunner_1', 'imp_gunner_1',
|
'imp_gunner_1', 'imp_gunner_1', 'imp_gunner_1',
|
||||||
'imp_guardian_1', 'imp_guardian_1', 'imp_guardian_1',
|
'imp_guardian_1', 'imp_guardian_1', 'imp_guardian_1',
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ export class BattleScene extends Phaser.Scene {
|
||||||
const cardManager = this.registry.get('cardManager');
|
const cardManager = this.registry.get('cardManager');
|
||||||
const save = this.registry.get('save');
|
const save = this.registry.get('save');
|
||||||
|
|
||||||
|
// Stop main menu music for the duration of the battle
|
||||||
|
this.registry.get('music_main_menu')?.stop();
|
||||||
|
|
||||||
// Layout constants
|
// Layout constants
|
||||||
// Lanes: 4 lanes centred at x = 430, 740, 1050, 1360 (spacing 310)
|
// Lanes: 4 lanes centred at x = 430, 740, 1050, 1360 (spacing 310)
|
||||||
// Commander column: x = 150
|
// Commander column: x = 150
|
||||||
|
|
@ -312,8 +315,8 @@ export class BattleScene extends Phaser.Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
const events = this.engine.processNextAttack();
|
const events = this.engine.processNextAttack();
|
||||||
const preAttackEvent = events.find(e => e.type === 'preAttack');
|
const preAttackEvent = events.find(e => e.type === 'preAttack');
|
||||||
const attackEvent = events.find(e => e.type === 'attack');
|
const attackEvent = events.find(e => e.type === 'attack');
|
||||||
const postAttackEvent = events.find(e => e.type === 'postAttack');
|
const postAttackEvent = events.find(e => e.type === 'postAttack');
|
||||||
|
|
||||||
if (!attackEvent) {
|
if (!attackEvent) {
|
||||||
|
|
@ -329,8 +332,8 @@ export class BattleScene extends Phaser.Scene {
|
||||||
this.commanderObjects.get(state.player.commander.instanceId)?.refresh();
|
this.commanderObjects.get(state.player.commander.instanceId)?.refresh();
|
||||||
this.commanderObjects.get(state.opponent.commander.instanceId)?.refresh();
|
this.commanderObjects.get(state.opponent.commander.instanceId)?.refresh();
|
||||||
|
|
||||||
const berserkEvent = events.find(e => e.type === 'berserk');
|
const berserkEvent = events.find(e => e.type === 'berserk');
|
||||||
const counterEvent = events.find(e => e.type === 'counter');
|
const counterEvent = events.find(e => e.type === 'counter');
|
||||||
|
|
||||||
// Fire pre-attack hook, then proceed to attack animation.
|
// Fire pre-attack hook, then proceed to attack animation.
|
||||||
// Counter fires at impact (cards still enlarged at center) via onImpact callback.
|
// Counter fires at impact (cards still enlarged at center) via onImpact callback.
|
||||||
|
|
@ -374,6 +377,7 @@ export class BattleScene extends Phaser.Scene {
|
||||||
.setDisplaySize(630, 630)
|
.setDisplaySize(630, 630)
|
||||||
.setDepth(23);
|
.setDepth(23);
|
||||||
explosionSprite.play('explosion_anim');
|
explosionSprite.play('explosion_anim');
|
||||||
|
this.sound.play('sfx_destroy', { volume: 0.85 });
|
||||||
explosionSprite.once('animationcomplete', () => {
|
explosionSprite.once('animationcomplete', () => {
|
||||||
if (explosionSprite.scene) explosionSprite.destroy();
|
if (explosionSprite.scene) explosionSprite.destroy();
|
||||||
if (!obj.scene) { if (onComplete) onComplete(); return; }
|
if (!obj.scene) { if (onComplete) onComplete(); return; }
|
||||||
|
|
@ -394,6 +398,9 @@ export class BattleScene extends Phaser.Scene {
|
||||||
// Animate a card appearing on the field (scale from 0 with overshoot)
|
// Animate a card appearing on the field (scale from 0 with overshoot)
|
||||||
_animateDeploy(cardObj, onComplete) {
|
_animateDeploy(cardObj, onComplete) {
|
||||||
if (!cardObj) { onComplete(); return; }
|
if (!cardObj) { onComplete(); return; }
|
||||||
|
if (cardObj.cardData?.rarity === 'legendary') {
|
||||||
|
this.sound.play('sfx_legendary_play', { volume: 0.9 });
|
||||||
|
}
|
||||||
cardObj.setScale(0).setAlpha(0);
|
cardObj.setScale(0).setAlpha(0);
|
||||||
this.tweens.add({
|
this.tweens.add({
|
||||||
targets: cardObj,
|
targets: cardObj,
|
||||||
|
|
@ -430,6 +437,7 @@ export class BattleScene extends Phaser.Scene {
|
||||||
const targetObj = _lookup(counterEvent.target.instanceId); // the original attacker
|
const targetObj = _lookup(counterEvent.target.instanceId); // the original attacker
|
||||||
|
|
||||||
if (!sourceObj?.scene || !targetObj?.scene) return;
|
if (!sourceObj?.scene || !targetObj?.scene) return;
|
||||||
|
this.sound.play('sfx_counter', { volume: 0.8 });
|
||||||
|
|
||||||
this.statusText.setText(`${counterEvent.source.name} counters ${counterEvent.target.name}!`);
|
this.statusText.setText(`${counterEvent.source.name} counters ${counterEvent.target.name}!`);
|
||||||
|
|
||||||
|
|
@ -460,8 +468,9 @@ export class BattleScene extends Phaser.Scene {
|
||||||
if (counterEvent.damage > 0) {
|
if (counterEvent.damage > 0) {
|
||||||
const target = counterEvent.target;
|
const target = counterEvent.target;
|
||||||
const hpBefore = target.currentHP + counterEvent.damage;
|
const hpBefore = target.currentHP + counterEvent.damage;
|
||||||
const hpAfter = Math.max(0, target.currentHP);
|
const hpAfter = Math.max(0, target.currentHP);
|
||||||
if (targetObj.hpText) targetObj.hpText.setText(`${hpBefore}`);
|
if (targetObj.hpText) targetObj.hpText.setText(`${hpBefore}`);
|
||||||
|
if (target.currentHP > 0) this.sound.play('sfx_damage', { volume: 0.8 });
|
||||||
targetObj.animateHPLoss(counterEvent.damage, null, hpBefore, hpAfter);
|
targetObj.animateHPLoss(counterEvent.damage, null, hpBefore, hpAfter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -521,6 +530,7 @@ export class BattleScene extends Phaser.Scene {
|
||||||
const targetObj = _lookup(mortarFire.target.instanceId);
|
const targetObj = _lookup(mortarFire.target.instanceId);
|
||||||
|
|
||||||
if (!sourceObj?.scene || !targetObj?.scene) { onComplete(); return; }
|
if (!sourceObj?.scene || !targetObj?.scene) { onComplete(); return; }
|
||||||
|
this.sound.play('sfx_mortar', { volume: 0.8 });
|
||||||
|
|
||||||
// Immediately correct the target's HP display to the pre-mortar value.
|
// Immediately correct the target's HP display to the pre-mortar value.
|
||||||
// The early commander refresh (and any prior render) may have already set it to
|
// The early commander refresh (and any prior render) may have already set it to
|
||||||
|
|
@ -545,7 +555,7 @@ export class BattleScene extends Phaser.Scene {
|
||||||
// Brief pause so the rotated mortar is visible on the card before launching
|
// Brief pause so the rotated mortar is visible on the card before launching
|
||||||
this.time.delayedCall(450, () => {
|
this.time.delayedCall(450, () => {
|
||||||
const startX = sourceObj.x, startY = sourceObj.y;
|
const startX = sourceObj.x, startY = sourceObj.y;
|
||||||
const endX = targetObj.x, endY = targetObj.y;
|
const endX = targetObj.x, endY = targetObj.y;
|
||||||
const ARC_HEIGHT = -180;
|
const ARC_HEIGHT = -180;
|
||||||
const progress = { t: 0 };
|
const progress = { t: 0 };
|
||||||
|
|
||||||
|
|
@ -597,6 +607,7 @@ export class BattleScene extends Phaser.Scene {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (mortarFire.damage > 0) {
|
if (mortarFire.damage > 0) {
|
||||||
|
if (hpAfter > 0) this.sound.play('sfx_damage', { volume: 0.8 });
|
||||||
targetObj.animateHPLoss(mortarFire.damage, afterImpact, mortarFire.hpBefore, hpAfter);
|
targetObj.animateHPLoss(mortarFire.damage, afterImpact, mortarFire.hpBefore, hpAfter);
|
||||||
} else {
|
} else {
|
||||||
afterImpact();
|
afterImpact();
|
||||||
|
|
@ -617,6 +628,7 @@ export class BattleScene extends Phaser.Scene {
|
||||||
const targetObj = _lookup(strikeFire.target.instanceId);
|
const targetObj = _lookup(strikeFire.target.instanceId);
|
||||||
|
|
||||||
if (!sourceObj?.scene || !targetObj?.scene) { onComplete(); return; }
|
if (!sourceObj?.scene || !targetObj?.scene) { onComplete(); return; }
|
||||||
|
this.sound.play('sfx_strike', { volume: 0.8 });
|
||||||
|
|
||||||
// Immediately correct the target's HP display to the pre-strike value.
|
// Immediately correct the target's HP display to the pre-strike value.
|
||||||
// Commanders are refreshed to the final HP before _onPreAttackStep runs;
|
// Commanders are refreshed to the final HP before _onPreAttackStep runs;
|
||||||
|
|
@ -641,7 +653,7 @@ export class BattleScene extends Phaser.Scene {
|
||||||
// Brief pause so the aimed missile is visible before launch
|
// Brief pause so the aimed missile is visible before launch
|
||||||
this.time.delayedCall(300, () => {
|
this.time.delayedCall(300, () => {
|
||||||
const startX = sourceObj.x, startY = sourceObj.y;
|
const startX = sourceObj.x, startY = sourceObj.y;
|
||||||
const endX = targetObj.x, endY = targetObj.y;
|
const endX = targetObj.x, endY = targetObj.y;
|
||||||
const ARC_HEIGHT = -70; // missiles fly flatter than mortar shells
|
const ARC_HEIGHT = -70; // missiles fly flatter than mortar shells
|
||||||
const progress = { t: 0 };
|
const progress = { t: 0 };
|
||||||
|
|
||||||
|
|
@ -690,6 +702,7 @@ export class BattleScene extends Phaser.Scene {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (strikeFire.damage > 0) {
|
if (strikeFire.damage > 0) {
|
||||||
|
if (hpAfter > 0) this.sound.play('sfx_damage', { volume: 0.8 });
|
||||||
targetObj.animateHPLoss(strikeFire.damage, afterImpact, strikeFire.hpBefore, hpAfter);
|
targetObj.animateHPLoss(strikeFire.damage, afterImpact, strikeFire.hpBefore, hpAfter);
|
||||||
} else {
|
} else {
|
||||||
afterImpact();
|
afterImpact();
|
||||||
|
|
@ -708,6 +721,7 @@ export class BattleScene extends Phaser.Scene {
|
||||||
const targetObj = _lookup(pierceFire.target.instanceId);
|
const targetObj = _lookup(pierceFire.target.instanceId);
|
||||||
|
|
||||||
if (!sourceObj?.scene || !targetObj?.scene || pierceFire.amount <= 0) { onComplete(); return; }
|
if (!sourceObj?.scene || !targetObj?.scene || pierceFire.amount <= 0) { onComplete(); return; }
|
||||||
|
this.sound.play('sfx_pierce', { volume: 0.8 });
|
||||||
|
|
||||||
this.statusText.setText(`${attacker.name} pierces ${pierceFire.target.name}'s armor!`);
|
this.statusText.setText(`${attacker.name} pierces ${pierceFire.target.name}'s armor!`);
|
||||||
|
|
||||||
|
|
@ -892,6 +906,7 @@ export class BattleScene extends Phaser.Scene {
|
||||||
'attacks'
|
'attacks'
|
||||||
).setDisplaySize(480, 480).setDepth(22);
|
).setDisplaySize(480, 480).setDepth(22);
|
||||||
attackSprite.play('attack_anim');
|
attackSprite.play('attack_anim');
|
||||||
|
this.sound.play('sfx_attack', { volume: 0.7 });
|
||||||
attackSprite.once('animationcomplete', () => {
|
attackSprite.once('animationcomplete', () => {
|
||||||
if (attackSprite.scene) attackSprite.destroy();
|
if (attackSprite.scene) attackSprite.destroy();
|
||||||
this._onAttackReconcile(event, berserkEvent);
|
this._onAttackReconcile(event, berserkEvent);
|
||||||
|
|
@ -985,8 +1000,8 @@ export class BattleScene extends Phaser.Scene {
|
||||||
|
|
||||||
// Called once per preBattle step. Handles buff animations, siege fires, then protect fires.
|
// Called once per preBattle step. Handles buff animations, siege fires, then protect fires.
|
||||||
_onPreBattleStep(event, onComplete) {
|
_onPreBattleStep(event, onComplete) {
|
||||||
const hasBuffs = event.buffs?.length > 0;
|
const hasBuffs = event.buffs?.length > 0;
|
||||||
const hasSiege = event.siegeFires?.length > 0;
|
const hasSiege = event.siegeFires?.length > 0;
|
||||||
const hasProtect = event.protectFires?.length > 0;
|
const hasProtect = event.protectFires?.length > 0;
|
||||||
|
|
||||||
if (hasBuffs || hasSiege || hasProtect) {
|
if (hasBuffs || hasSiege || hasProtect) {
|
||||||
|
|
@ -1035,6 +1050,7 @@ export class BattleScene extends Phaser.Scene {
|
||||||
const targetObj = _lookup(buff.target.instanceId);
|
const targetObj = _lookup(buff.target.instanceId);
|
||||||
if (!sourceObj?.scene || !targetObj?.scene) { onComplete(); return; }
|
if (!sourceObj?.scene || !targetObj?.scene) { onComplete(); return; }
|
||||||
|
|
||||||
|
this.sound.play('sfx_rally', { volume: 0.8 });
|
||||||
this.statusText.setText(`${buff.source.name} rallies ${buff.target.name}! +${buff.amount} ATK`);
|
this.statusText.setText(`${buff.source.name} rallies ${buff.target.name}! +${buff.amount} ATK`);
|
||||||
|
|
||||||
// Base scale for 160px display from 460px source frame
|
// Base scale for 160px display from 460px source frame
|
||||||
|
|
@ -1070,8 +1086,8 @@ export class BattleScene extends Phaser.Scene {
|
||||||
// Arc flight — tween a progress value and compute position + scale each frame
|
// Arc flight — tween a progress value and compute position + scale each frame
|
||||||
const startX = sourceObj.x;
|
const startX = sourceObj.x;
|
||||||
const startY = sourceObj.y;
|
const startY = sourceObj.y;
|
||||||
const endX = targetObj.x;
|
const endX = targetObj.x;
|
||||||
const endY = targetObj.y;
|
const endY = targetObj.y;
|
||||||
const ARC_HEIGHT = -200; // px upward at the midpoint
|
const ARC_HEIGHT = -200; // px upward at the midpoint
|
||||||
|
|
||||||
const progress = { t: 0 };
|
const progress = { t: 0 };
|
||||||
|
|
@ -1145,10 +1161,12 @@ export class BattleScene extends Phaser.Scene {
|
||||||
if (!launcherSprite.scene) { onComplete(); return; }
|
if (!launcherSprite.scene) { onComplete(); return; }
|
||||||
|
|
||||||
const startX = sourceObj.x, startY = sourceObj.y;
|
const startX = sourceObj.x, startY = sourceObj.y;
|
||||||
const endX = targetObj.x, endY = targetObj.y;
|
const endX = targetObj.x, endY = targetObj.y;
|
||||||
const ARC_HEIGHT = -160;
|
const ARC_HEIGHT = -160;
|
||||||
const progress = { t: 0 };
|
const progress = { t: 0 };
|
||||||
|
|
||||||
|
this.sound.play('sfx_siege', { volume: 0.8 });
|
||||||
|
|
||||||
// Siege missile sprite (14) — aimed at commander, points up by default
|
// Siege missile sprite (14) — aimed at commander, points up by default
|
||||||
const missileSprite = this.add.sprite(startX, startY, 'attacks', 14)
|
const missileSprite = this.add.sprite(startX, startY, 'attacks', 14)
|
||||||
.setScale(BASE_SCALE)
|
.setScale(BASE_SCALE)
|
||||||
|
|
@ -1198,6 +1216,7 @@ export class BattleScene extends Phaser.Scene {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (siegeFire.damage > 0) {
|
if (siegeFire.damage > 0) {
|
||||||
|
if (hpAfter > 0) this.sound.play('sfx_damage', { volume: 0.8 });
|
||||||
targetObj.animateHPLoss(siegeFire.damage, afterImpact, siegeFire.hpBefore, hpAfter);
|
targetObj.animateHPLoss(siegeFire.damage, afterImpact, siegeFire.hpBefore, hpAfter);
|
||||||
} else {
|
} else {
|
||||||
afterImpact();
|
afterImpact();
|
||||||
|
|
@ -1215,21 +1234,22 @@ export class BattleScene extends Phaser.Scene {
|
||||||
const sourceObj = _lookup(protectFire.source.instanceId);
|
const sourceObj = _lookup(protectFire.source.instanceId);
|
||||||
if (!sourceObj?.scene) { onComplete(); return; }
|
if (!sourceObj?.scene) { onComplete(); return; }
|
||||||
|
|
||||||
|
this.sound.play('sfx_protect', { volume: 0.8 });
|
||||||
this.statusText.setText(`${protectFire.source.name} raises shields!`);
|
this.statusText.setText(`${protectFire.source.name} raises shields!`);
|
||||||
|
|
||||||
const BASE_SCALE = 160 / 460;
|
const BASE_SCALE = 160 / 460;
|
||||||
const NEIGHBOR_SCALE = BASE_SCALE * (2 / 3);
|
const NEIGHBOR_SCALE = BASE_SCALE * (2 / 3);
|
||||||
const RISE_DURATION = 600;
|
const RISE_DURATION = 600;
|
||||||
const FLY_DURATION = 500;
|
const FLY_DURATION = 500;
|
||||||
const SETTLE_DELAY = 650; // ms before ARM gain animations start
|
const SETTLE_DELAY = 650; // ms before ARM gain animations start
|
||||||
|
|
||||||
// Resolve the target CardObjects and separate self from neighbors
|
// Resolve the target CardObjects and separate self from neighbors
|
||||||
const self = protectFire.targets.find(t => t.laneOffset === 0);
|
const self = protectFire.targets.find(t => t.laneOffset === 0);
|
||||||
const left = protectFire.targets.find(t => t.laneOffset === -1);
|
const left = protectFire.targets.find(t => t.laneOffset === -1);
|
||||||
const right = protectFire.targets.find(t => t.laneOffset === 1);
|
const right = protectFire.targets.find(t => t.laneOffset === 1);
|
||||||
|
|
||||||
const selfObj = self ? _lookup(self.card.instanceId) : null;
|
const selfObj = self ? _lookup(self.card.instanceId) : null;
|
||||||
const leftObj = left ? _lookup(left.card.instanceId) : null;
|
const leftObj = left ? _lookup(left.card.instanceId) : null;
|
||||||
const rightObj = right ? _lookup(right.card.instanceId) : null;
|
const rightObj = right ? _lookup(right.card.instanceId) : null;
|
||||||
|
|
||||||
const sprites = [];
|
const sprites = [];
|
||||||
|
|
@ -1251,7 +1271,7 @@ export class BattleScene extends Phaser.Scene {
|
||||||
|
|
||||||
// 2/3-size shields for each neighbor — fly from source to neighbor card
|
// 2/3-size shields for each neighbor — fly from source to neighbor card
|
||||||
for (const { neighbor, neighborObj } of [
|
for (const { neighbor, neighborObj } of [
|
||||||
{ neighbor: left, neighborObj: leftObj },
|
{ neighbor: left, neighborObj: leftObj },
|
||||||
{ neighbor: right, neighborObj: rightObj }
|
{ neighbor: right, neighborObj: rightObj }
|
||||||
]) {
|
]) {
|
||||||
if (!neighbor || !neighborObj?.scene) continue;
|
if (!neighbor || !neighborObj?.scene) continue;
|
||||||
|
|
@ -1315,7 +1335,7 @@ export class BattleScene extends Phaser.Scene {
|
||||||
if (event.debuffs?.length > 0) {
|
if (event.debuffs?.length > 0) {
|
||||||
for (const debuff of event.debuffs) {
|
for (const debuff of event.debuffs) {
|
||||||
const obj = this.cardObjects.get(debuff.target.instanceId)
|
const obj = this.cardObjects.get(debuff.target.instanceId)
|
||||||
?? this.commanderObjects?.get(debuff.target.instanceId);
|
?? this.commanderObjects?.get(debuff.target.instanceId);
|
||||||
obj?.refresh();
|
obj?.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1330,6 +1350,7 @@ export class BattleScene extends Phaser.Scene {
|
||||||
|
|
||||||
// HP loss animation on defender; on completion, trigger death if HP reached zero
|
// HP loss animation on defender; on completion, trigger death if HP reached zero
|
||||||
if (defenderObj?.scene && event.damage > 0) {
|
if (defenderObj?.scene && event.damage > 0) {
|
||||||
|
if (event.defender.currentHP > 0) this.sound.play('sfx_damage', { volume: 0.8 });
|
||||||
defenderObj.animateHPLoss(event.damage, () => {
|
defenderObj.animateHPLoss(event.damage, () => {
|
||||||
if (event.defender.currentHP <= 0 && !defenderObj.isCommander) {
|
if (event.defender.currentHP <= 0 && !defenderObj.isCommander) {
|
||||||
this._playCardDeath(defenderObj, event.defender);
|
this._playCardDeath(defenderObj, event.defender);
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,22 @@ export class BootScene extends Phaser.Scene {
|
||||||
this.load.json('missions', 'data/missions.json');
|
this.load.json('missions', 'data/missions.json');
|
||||||
this.load.json('skills', 'data/skills.json');
|
this.load.json('skills', 'data/skills.json');
|
||||||
|
|
||||||
|
this.load.audio('sfx_attack', 'assets/audio/fx/attack.mp3');
|
||||||
|
this.load.audio('sfx_damage', 'assets/audio/fx/damage.mp3');
|
||||||
|
this.load.audio('sfx_mortar', 'assets/audio/fx/mortar.mp3');
|
||||||
|
this.load.audio('sfx_strike', 'assets/audio/fx/strike.mp3');
|
||||||
|
this.load.audio('sfx_pierce', 'assets/audio/fx/pierce.mp3');
|
||||||
|
this.load.audio('sfx_protect', 'assets/audio/fx/protect.mp3');
|
||||||
|
this.load.audio('sfx_legendary_play', 'assets/audio/fx/legendary-play.mp3');
|
||||||
|
this.load.audio('sfx_destroy', 'assets/audio/fx/destroy.mp3');
|
||||||
|
this.load.audio('sfx_rally', 'assets/audio/fx/rally.mp3');
|
||||||
|
this.load.audio('sfx_siege', 'assets/audio/fx/siege.mp3');
|
||||||
|
this.load.audio('sfx_counter', 'assets/audio/fx/counter.mp3');
|
||||||
|
this.load.audio('sfx_menu_select', 'assets/audio/fx/menu_select.mp3');
|
||||||
|
this.load.audio('sfx_menu_hover', 'assets/audio/fx/menu_hover.mp3');
|
||||||
|
|
||||||
|
this.load.audio('music_main_menu', 'assets/audio/music/main_menu.mp3');
|
||||||
|
|
||||||
// Once cards.json finishes, queue card art images and resume loading
|
// Once cards.json finishes, queue card art images and resume loading
|
||||||
this.load.once('filecomplete-json-cards', (_key, _type, data) => {
|
this.load.once('filecomplete-json-cards', (_key, _type, data) => {
|
||||||
data.forEach(card => {
|
data.forEach(card => {
|
||||||
|
|
@ -67,6 +83,11 @@ export class BootScene extends Phaser.Scene {
|
||||||
this.registry.set('missions', missionsData);
|
this.registry.set('missions', missionsData);
|
||||||
this.registry.set('save', save);
|
this.registry.set('save', save);
|
||||||
|
|
||||||
|
// Start main menu music looping; store reference so other scenes can stop/resume it
|
||||||
|
const menuMusic = this.sound.add('music_main_menu', { loop: true, volume: 0.5 });
|
||||||
|
menuMusic.play();
|
||||||
|
this.registry.set('music_main_menu', menuMusic);
|
||||||
|
|
||||||
this.scene.start('MainMenuScene');
|
this.scene.start('MainMenuScene');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,10 @@ export class MainMenuScene extends Phaser.Scene {
|
||||||
const save = this.registry.get('save');
|
const save = this.registry.get('save');
|
||||||
const { width, height } = this.scale;
|
const { width, height } = this.scale;
|
||||||
|
|
||||||
|
// Resume main menu music if it was stopped by a battle
|
||||||
|
const menuMusic = this.registry.get('music_main_menu');
|
||||||
|
if (menuMusic && !menuMusic.isPlaying) menuMusic.play();
|
||||||
|
|
||||||
// Background
|
// Background
|
||||||
this.add.rectangle(width / 2, height / 2, width, height, 0x0d1b2a);
|
this.add.rectangle(width / 2, height / 2, width, height, 0x0d1b2a);
|
||||||
|
|
||||||
|
|
@ -65,9 +69,9 @@ export class MainMenuScene extends Phaser.Scene {
|
||||||
fontSize: '28px', color: '#ffffff'
|
fontSize: '28px', color: '#ffffff'
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
||||||
bg.on('pointerover', () => bg.setFillStyle(0x2a5a8c));
|
bg.on('pointerover', () => { this.sound.play('sfx_menu_hover', { volume: 0.5 }); bg.setFillStyle(0x2a5a8c); });
|
||||||
bg.on('pointerout', () => bg.setFillStyle(0x1a3a5c));
|
bg.on('pointerout', () => bg.setFillStyle(0x1a3a5c));
|
||||||
bg.on('pointerdown', callback);
|
bg.on('pointerdown', () => { this.sound.play('sfx_menu_select', { volume: 0.7 }); callback(); });
|
||||||
|
|
||||||
return { bg, text };
|
return { bg, text };
|
||||||
}
|
}
|
||||||
|
|
@ -83,9 +87,10 @@ export class MainMenuScene extends Phaser.Scene {
|
||||||
fontSize: '20px', color: '#88cc88'
|
fontSize: '20px', color: '#88cc88'
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
||||||
bg.on('pointerover', () => bg.setFillStyle(0x2a3f2a));
|
bg.on('pointerover', () => { this.sound.play('sfx_menu_hover', { volume: 0.5 }); bg.setFillStyle(0x2a3f2a); });
|
||||||
bg.on('pointerout', () => bg.setFillStyle(0x1a2a1a));
|
bg.on('pointerout', () => bg.setFillStyle(0x1a2a1a));
|
||||||
bg.on('pointerdown', () => {
|
bg.on('pointerdown', () => {
|
||||||
|
this.sound.play('sfx_menu_select', { volume: 0.7 });
|
||||||
if (this.scale.isFullscreen) {
|
if (this.scale.isFullscreen) {
|
||||||
this.scale.stopFullscreen();
|
this.scale.stopFullscreen();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue