Add sound effects and music to slot machine game with victory animations and stage transitions
This commit is contained in:
parent
1ca2579b64
commit
c3eba35be7
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -46,6 +46,10 @@ export class LossAnimation {
|
||||||
// Screen tint to dark red briefly
|
// Screen tint to dark red briefly
|
||||||
scene.cameras.main.flash(800, 150, 0, 0, true);
|
scene.cameras.main.flash(800, 150, 0, 0, true);
|
||||||
|
|
||||||
|
// Play a random no-match voice line
|
||||||
|
const pick = Phaser.Math.Between(1, 6);
|
||||||
|
scene.sound.play(`sfx-nomatch-${pick}`);
|
||||||
|
|
||||||
// Tween devil + money up to sin box
|
// Tween devil + money up to sin box
|
||||||
scene.tweens.add({
|
scene.tweens.add({
|
||||||
targets: [devilGfx, moneyText],
|
targets: [devilGfx, moneyText],
|
||||||
|
|
|
||||||
|
|
@ -64,14 +64,25 @@ export class SlotMachine {
|
||||||
|
|
||||||
const payout = win ? results[0].payout : 0;
|
const payout = win ? results[0].payout : 0;
|
||||||
|
|
||||||
|
// Loop the spin sound for the duration of the spin
|
||||||
|
this._spinSound = this.scene.sound.add('sfx-spin', { loop: true });
|
||||||
|
this._spinSound.play();
|
||||||
|
|
||||||
let doneCount = 0;
|
let doneCount = 0;
|
||||||
const totalReels = this.reels.length;
|
const totalReels = this.reels.length;
|
||||||
const stopDelays = [800, 1300, 1800]; // ms before each reel starts decelerating
|
const stopDelays = [800, 1300, 1800]; // ms before each reel starts decelerating
|
||||||
|
|
||||||
this.reels.forEach((reel, i) => {
|
this.reels.forEach((reel, i) => {
|
||||||
reel.spin(results[i], stopDelays[i], () => {
|
reel.spin(results[i], stopDelays[i], () => {
|
||||||
|
// Play a thud for each reel landing
|
||||||
|
this.scene.sound.play('sfx-stop');
|
||||||
|
|
||||||
doneCount++;
|
doneCount++;
|
||||||
if (doneCount === totalReels) {
|
if (doneCount === totalReels) {
|
||||||
|
// All reels stopped — kill the spin loop
|
||||||
|
this._spinSound.stop();
|
||||||
|
this._spinSound.destroy();
|
||||||
|
this._spinSound = null;
|
||||||
onComplete({ win, symbols: results, payout });
|
onComplete({ win, symbols: results, payout });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,17 @@ import { MatchBanner } from './MatchBanner.js';
|
||||||
export class WinAnimation {
|
export class WinAnimation {
|
||||||
// playerBoxCenter, lordBoxCenter: { x, y } screen positions of fund boxes
|
// playerBoxCenter, lordBoxCenter: { x, y } screen positions of fund boxes
|
||||||
play(scene, originX, originY, playerBoxCenter, lordBoxCenter, symbol, onComplete) {
|
play(scene, originX, originY, playerBoxCenter, lordBoxCenter, symbol, onComplete) {
|
||||||
|
// Play a random match voice line as the banner appears
|
||||||
|
scene.sound.play(`sfx-match-${Phaser.Math.Between(1, 6)}`);
|
||||||
|
|
||||||
new MatchBanner().play(scene, symbol.label, () => {
|
new MatchBanner().play(scene, symbol.label, () => {
|
||||||
// Flash the screen gold
|
// Flash the screen gold
|
||||||
scene.cameras.main.flash(1200, 255, 215, 0, true);
|
scene.cameras.main.flash(1200, 255, 215, 0, true);
|
||||||
|
|
||||||
|
// Loop coins sound for the duration of the coin animation
|
||||||
|
const coinsSound = scene.sound.add('sfx-coins', { loop: true });
|
||||||
|
coinsSound.play();
|
||||||
|
|
||||||
const totalCoins = 20;
|
const totalCoins = 20;
|
||||||
const playerCoins = Math.round(totalCoins * 0.6);
|
const playerCoins = Math.round(totalCoins * 0.6);
|
||||||
const lordCoins = totalCoins - playerCoins;
|
const lordCoins = totalCoins - playerCoins;
|
||||||
|
|
@ -165,6 +172,8 @@ export class WinAnimation {
|
||||||
container.destroy();
|
container.destroy();
|
||||||
finished++;
|
finished++;
|
||||||
if (finished === totalCoins) {
|
if (finished === totalCoins) {
|
||||||
|
coinsSound.stop();
|
||||||
|
coinsSound.destroy();
|
||||||
stopPlayer();
|
stopPlayer();
|
||||||
stopLord();
|
stopLord();
|
||||||
scene.tweens.add({
|
scene.tweens.add({
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,22 @@ export default class BootScene extends Phaser.Scene {
|
||||||
// Victory videos
|
// Victory videos
|
||||||
this.load.video('lord-victory', 'assets/video/lord-victory.mp4', true);
|
this.load.video('lord-victory', 'assets/video/lord-victory.mp4', true);
|
||||||
this.load.video('sin-victory', 'assets/video/sin-victory.mp4', true);
|
this.load.video('sin-victory', 'assets/video/sin-victory.mp4', true);
|
||||||
|
|
||||||
|
// Background music
|
||||||
|
this.load.audio('music-01-02', 'assets/music/stage-01-02.mp3');
|
||||||
|
this.load.audio('music-03-04', 'assets/music/stage-03-04.mp3');
|
||||||
|
this.load.audio('music-05-06', 'assets/music/stage-05-06.mp3');
|
||||||
|
this.load.audio('music-lord-victory', 'assets/music/lord-victory.mp3');
|
||||||
|
this.load.audio('music-sin-victory', 'assets/music/sin-victory.mp3');
|
||||||
|
|
||||||
|
// Sound effects
|
||||||
|
this.load.audio('sfx-spin', 'assets/fx/spin.mp3');
|
||||||
|
this.load.audio('sfx-stop', 'assets/fx/stop.mp3');
|
||||||
|
for (let i = 1; i <= 6; i++) {
|
||||||
|
this.load.audio(`sfx-nomatch-${i}`, `assets/fx/nomatch${i}.mp3`);
|
||||||
|
this.load.audio(`sfx-match-${i}`, `assets/fx/match${i}.mp3`);
|
||||||
|
}
|
||||||
|
this.load.audio('sfx-coins', 'assets/fx/coins.mp3');
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ export default class GameScene extends Phaser.Scene {
|
||||||
this._currentStage = 2;
|
this._currentStage = 2;
|
||||||
this._activeVideo = null;
|
this._activeVideo = null;
|
||||||
this._gameOver = false;
|
this._gameOver = false;
|
||||||
|
this._currentMusicKey = null;
|
||||||
|
this._musicSound = null;
|
||||||
const vidX = this._vidX;
|
const vidX = this._vidX;
|
||||||
const vidY = this._vidY;
|
const vidY = this._vidY;
|
||||||
const vidW = this._vidW;
|
const vidW = this._vidW;
|
||||||
|
|
@ -36,6 +38,7 @@ export default class GameScene extends Phaser.Scene {
|
||||||
|
|
||||||
// Start with the stage-02 loop (game begins at stage 2)
|
// Start with the stage-02 loop (game begins at stage 2)
|
||||||
this._setVideo('stage-02', true);
|
this._setVideo('stage-02', true);
|
||||||
|
this._switchMusic(this._getMusicKey(2));
|
||||||
|
|
||||||
// Gradient backdrop behind title — opaque on left, fades to transparent
|
// Gradient backdrop behind title — opaque on left, fades to transparent
|
||||||
const titleBg = this.add.graphics();
|
const titleBg = this.add.graphics();
|
||||||
|
|
@ -101,13 +104,25 @@ export default class GameScene extends Phaser.Scene {
|
||||||
if (!this._gameOver) this.slotMachine.setEnabled(true);
|
if (!this._gameOver) this.slotMachine.setEnabled(true);
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
// Victory: play the victory video and shatter the losing vial
|
// Victory: play the victory video, music, and shatter the losing vial
|
||||||
this.game.events.on('vial-winner', ({ winner }) => {
|
this.game.events.on('vial-winner', ({ winner }) => {
|
||||||
this._gameOver = true;
|
this._gameOver = true;
|
||||||
const lordWins = winner.toLowerCase().includes('lord');
|
const lordWins = winner.toLowerCase().includes('lord');
|
||||||
const key = lordWins ? 'lord-victory' : 'sin-victory';
|
const key = lordWins ? 'lord-victory' : 'sin-victory';
|
||||||
this._setVideo(key, false);
|
this._setVideo(key, false);
|
||||||
|
|
||||||
|
// Fade out background music and play the victory track once
|
||||||
|
if (this._musicSound) {
|
||||||
|
const outgoing = this._musicSound;
|
||||||
|
this._musicSound = null;
|
||||||
|
this._currentMusicKey = null;
|
||||||
|
this.tweens.add({
|
||||||
|
targets: outgoing, volume: 0, duration: 1000,
|
||||||
|
onComplete: () => { outgoing.stop(); outgoing.destroy(); },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.sound.play(lordWins ? 'music-lord-victory' : 'music-sin-victory');
|
||||||
|
|
||||||
// Shatter the losing vial after a brief dramatic pause
|
// Shatter the losing vial after a brief dramatic pause
|
||||||
this.time.delayedCall(550, () => {
|
this.time.delayedCall(550, () => {
|
||||||
if (lordWins) {
|
if (lordWins) {
|
||||||
|
|
@ -250,6 +265,8 @@ export default class GameScene extends Phaser.Scene {
|
||||||
const oldStage = this._currentStage;
|
const oldStage = this._currentStage;
|
||||||
this._currentStage = newStage;
|
this._currentStage = newStage;
|
||||||
|
|
||||||
|
this._switchMusic(this._getMusicKey(newStage));
|
||||||
|
|
||||||
const pad = n => String(n).padStart(2, '0');
|
const pad = n => String(n).padStart(2, '0');
|
||||||
const direction = newStage > oldStage ? 1 : -1;
|
const direction = newStage > oldStage ? 1 : -1;
|
||||||
const transitions = [];
|
const transitions = [];
|
||||||
|
|
@ -261,4 +278,33 @@ export default class GameScene extends Phaser.Scene {
|
||||||
if (!this._gameOver) this._setVideo(`stage-${pad(newStage)}`, true);
|
if (!this._gameOver) this._setVideo(`stage-${pad(newStage)}`, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Map a stage number to its music track key. */
|
||||||
|
_getMusicKey(stage) {
|
||||||
|
if (stage <= 2) return 'music-01-02';
|
||||||
|
if (stage <= 4) return 'music-03-04';
|
||||||
|
return 'music-05-06';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Crossfade from the current music track to newKey (no-op if already playing). */
|
||||||
|
_switchMusic(newKey) {
|
||||||
|
if (newKey === this._currentMusicKey) return;
|
||||||
|
this._currentMusicKey = newKey;
|
||||||
|
|
||||||
|
// Fade out and destroy the outgoing track
|
||||||
|
if (this._musicSound) {
|
||||||
|
const outgoing = this._musicSound;
|
||||||
|
this.tweens.add({
|
||||||
|
targets: outgoing,
|
||||||
|
volume: 0,
|
||||||
|
duration: 1000,
|
||||||
|
onComplete: () => { outgoing.stop(); outgoing.destroy(); },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fade in the incoming track from silence
|
||||||
|
this._musicSound = this.sound.add(newKey, { loop: true, volume: 0 });
|
||||||
|
this._musicSound.play();
|
||||||
|
this.tweens.add({ targets: this._musicSound, volume: 1, duration: 1000 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue