feat(monopoly): add card deck visuals and rent/card animations
- Draw Chance and Community Chest card decks on the board center - Add animated card draw with flip effect from deck position - Add animated rent payment with money flying between players - Integrate new Monopoly sound effects (purchase, expense, paid) - Extract applyRent() to MonopolyLogic for cleaner state handling - Update monopoly-cards spritesheet with new card art
This commit is contained in:
parent
0317d1c14f
commit
359740f4f7
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 69 KiB |
Binary file not shown.
|
|
@ -15,7 +15,7 @@ import {
|
|||
createInitialState, rollDice, resolveSpace, buyProperty, declineProperty,
|
||||
placeBid, passAuction, buildHouse, buildHotel, sellHouse, sellHotel,
|
||||
mortgageProperty, unmortgageProperty, payJailFine, useJailCard,
|
||||
applyCardEffect, endTurn, checkGameOver, calculateRent,
|
||||
applyCardEffect, applyRent, endTurn, checkGameOver, calculateRent,
|
||||
canBuildHouse, canBuildHotel, ownsGroup, netWorth,
|
||||
} from './MonopolyLogic.js';
|
||||
import { chooseBuy, chooseBid, chooseJailAction, chooseBuild, nextThinkDelay } from './MonopolyAI.js';
|
||||
|
|
@ -32,6 +32,9 @@ const RP_W = GAME_WIDTH - RP_X - 20; // ~980
|
|||
// Depth
|
||||
const DEPTH = { bg:0, board:5, band:6, text:7, houses:10, pawns:15, ui:25, popup:50, banner:90 };
|
||||
|
||||
// Center deck offset (must match drawCenterDecks constant)
|
||||
const DECK_D = 130;
|
||||
|
||||
// Property purchase modal
|
||||
const MODAL_W = 340;
|
||||
const MODAL_H = 500;
|
||||
|
|
@ -76,6 +79,8 @@ export default class MonopolyGame extends Phaser.Scene {
|
|||
this.modalOverlay = null;
|
||||
this.modalSpaceIdx = null;
|
||||
this.modalOrigin = null;
|
||||
// Card draw animation flag — suppresses static popup until animation finishes
|
||||
this.cardAnimPlayed = false;
|
||||
}
|
||||
|
||||
create() {
|
||||
|
|
@ -154,6 +159,61 @@ export default class MonopolyGame extends Phaser.Scene {
|
|||
|
||||
// Draw all 40 spaces
|
||||
for (let i = 0; i < 40; i++) this.drawBoardSpace(g, i);
|
||||
|
||||
this.drawCenterDecks();
|
||||
}
|
||||
|
||||
drawCenterDecks() {
|
||||
const cx = BL + BS / 2; // 450
|
||||
const cy = BT + BS / 2; // 540
|
||||
// Chance: lower-left, orange; Community Chest: upper-right, blue
|
||||
this._drawCardDeck(cx - DECK_D, cy + DECK_D, 88, 126, -0.14, 0xE77A2C, 'Chance');
|
||||
this._drawCardDeck(cx + DECK_D, cy - DECK_D, 88, 126, 0.12, 0x1565C0, 'Community\nChest');
|
||||
}
|
||||
|
||||
_drawCardDeck(x, y, w, h, rot, color, label) {
|
||||
// Darken the base color for shadow card layers
|
||||
const dr = (((color >> 16) & 0xFF) * 0.60) | 0;
|
||||
const dg = (((color >> 8) & 0xFF) * 0.60) | 0;
|
||||
const db = ((color & 0xFF) * 0.60) | 0;
|
||||
const dark = (dr << 16) | (dg << 8) | db;
|
||||
|
||||
const container = this.add.container(x, y).setDepth(DEPTH.text).setRotation(rot);
|
||||
|
||||
// Shadow card layers — offset downward-right to simulate deck thickness
|
||||
for (let i = 3; i >= 1; i--) {
|
||||
const sg = this.add.graphics();
|
||||
sg.fillStyle(dark, 1);
|
||||
sg.lineStyle(1, 0x1a1208, 0.6);
|
||||
sg.fillRoundedRect(-w / 2 + i * 2, -h / 2 + i * 2, w, h, 5);
|
||||
sg.strokeRoundedRect(-w / 2 + i * 2, -h / 2 + i * 2, w, h, 5);
|
||||
container.add(sg);
|
||||
}
|
||||
|
||||
// Top card body
|
||||
const bg = this.add.graphics();
|
||||
bg.fillStyle(color, 1);
|
||||
bg.lineStyle(2, 0x1a1208, 1);
|
||||
bg.fillRoundedRect(-w / 2, -h / 2, w, h, 5);
|
||||
bg.strokeRoundedRect(-w / 2, -h / 2, w, h, 5);
|
||||
container.add(bg);
|
||||
|
||||
// Inner cream border
|
||||
const bdr = this.add.graphics();
|
||||
bdr.lineStyle(1.5, 0xFFF8E7, 0.85);
|
||||
bdr.strokeRoundedRect(-w / 2 + 6, -h / 2 + 6, w - 12, h - 12, 3);
|
||||
container.add(bdr);
|
||||
|
||||
// Label
|
||||
const txt = this.add.text(0, 0, label, {
|
||||
fontFamily: 'Righteous',
|
||||
fontSize: '11px',
|
||||
color: '#FFFFFF',
|
||||
align: 'center',
|
||||
stroke: '#00000055',
|
||||
strokeThickness: 1,
|
||||
}).setOrigin(0.5);
|
||||
container.add(txt);
|
||||
}
|
||||
|
||||
drawBoardSpace(g, idx) {
|
||||
|
|
@ -415,7 +475,7 @@ export default class MonopolyGame extends Phaser.Scene {
|
|||
this.positionPawns();
|
||||
this.drawPlayerPanels();
|
||||
this.drawActionBar();
|
||||
if (this.gs.pendingCard) this.drawCardPopup();
|
||||
if (this.gs.pendingCard && this.cardAnimPlayed) this.drawCardPopup();
|
||||
if (this.gs.phase === 'auction' && this.gs.pendingAuction) this.drawAuctionPanel();
|
||||
if (this.modalActive && this.gs.phase === 'buy') this.drawModalBuyButtons();
|
||||
// DOM video portraits always render above canvas — hide them during any overlay
|
||||
|
|
@ -699,15 +759,222 @@ export default class MonopolyGame extends Phaser.Scene {
|
|||
}
|
||||
}
|
||||
|
||||
if (phase === 'card' && gs.pendingCard && gs.current === this.humanSeat) {
|
||||
mkBtn('OK', () => this.onDismissCard());
|
||||
}
|
||||
// Card OK button is drawn inside drawCardPopup(), overlaid on the card
|
||||
|
||||
if (phase === 'jailChoice') {
|
||||
// Jail handling is in preroll above
|
||||
}
|
||||
}
|
||||
|
||||
// ── Rent Payment Animation ─────────────────────────────────────────────────
|
||||
async animateRent() {
|
||||
const { payer, receiver, amount } = this.gs.pendingRent;
|
||||
const depth = DEPTH.banner - 1; // 89 — above all game elements
|
||||
const cx = GAME_WIDTH / 2, cy = GAME_HEIGHT / 2;
|
||||
|
||||
playSound(this, SFX.MONOPOLY_EXPENSE);
|
||||
|
||||
// Phase 1: Banner + amount appear centered
|
||||
const bannerTxt = this.add.text(cx, cy - 60, 'PAY RENT', {
|
||||
fontFamily: 'Righteous', fontSize: '80px', color: '#FFFFFF',
|
||||
stroke: '#1a1208', strokeThickness: 5,
|
||||
}).setOrigin(0.5).setDepth(depth);
|
||||
|
||||
const amtTxt = this.add.text(cx, cy + 40, `$${amount.toLocaleString()}`, {
|
||||
fontFamily: 'Righteous', fontSize: '56px', color: '#FFD700',
|
||||
stroke: '#1a1208', strokeThickness: 4,
|
||||
}).setOrigin(0.5).setDepth(depth);
|
||||
|
||||
await this.delay(1000);
|
||||
|
||||
// Phase 2: Amount flies to payer's panel (750 ms), turns red, adds minus
|
||||
const { px: ppx, py: ppy, panelW: ppw } = this.panelPos(payer);
|
||||
const { px: rpx, py: rpy, panelW: rpw } = this.panelPos(receiver);
|
||||
const payerX = ppx + ppw / 2, payerY = ppy + 44;
|
||||
const recvX = rpx + rpw / 2, recvY = rpy + 44;
|
||||
|
||||
amtTxt.setText(`-$${amount.toLocaleString()}`);
|
||||
amtTxt.setColor('#FF4444');
|
||||
// Fade banner out simultaneously
|
||||
this.tweens.add({ targets: bannerTxt, alpha: 0, duration: 750, ease: 'Linear' });
|
||||
|
||||
await new Promise(resolve => {
|
||||
this.tweens.add({
|
||||
targets: amtTxt,
|
||||
x: payerX, y: payerY,
|
||||
scaleX: 0.5, scaleY: 0.5,
|
||||
duration: 750, ease: 'Cubic.easeIn',
|
||||
onComplete: resolve,
|
||||
});
|
||||
});
|
||||
|
||||
await this.delay(250);
|
||||
|
||||
// Phase 3: Amount arches to receiver's panel (1200 ms), turns green, adds plus
|
||||
amtTxt.setText(`+$${amount.toLocaleString()}`);
|
||||
amtTxt.setColor('#44FF88');
|
||||
|
||||
const sx = amtTxt.x, sy = amtTxt.y;
|
||||
const midX = (sx + recvX) / 2;
|
||||
const midY = Math.min(sy, recvY) - 220; // arch above both panels
|
||||
const proxy = { t: 0 };
|
||||
|
||||
await new Promise(resolve => {
|
||||
this.tweens.add({
|
||||
targets: proxy, t: 1,
|
||||
duration: 1200, ease: 'Sine.easeInOut',
|
||||
onUpdate: () => {
|
||||
const t = proxy.t, u = 1 - t;
|
||||
amtTxt.x = u*u*sx + 2*u*t*midX + t*t*recvX;
|
||||
amtTxt.y = u*u*sy + 2*u*t*midY + t*t*recvY;
|
||||
},
|
||||
onComplete: resolve,
|
||||
});
|
||||
});
|
||||
|
||||
playSound(this, SFX.MONOPOLY_PAID);
|
||||
await this.delay(300);
|
||||
bannerTxt.destroy();
|
||||
amtTxt.destroy();
|
||||
}
|
||||
|
||||
// ── Card Draw Animation ────────────────────────────────────────────────────
|
||||
async animateCardDraw() {
|
||||
const { cardType, text } = this.gs.pendingCard;
|
||||
const isChance = cardType === 'chance';
|
||||
const BOARD_CX = BL + BS / 2;
|
||||
const BOARD_CY = BT + BS / 2;
|
||||
const cardColor = isChance ? 0xE77A2C : 0x1565C0;
|
||||
|
||||
// Deck position — must match drawCenterDecks()
|
||||
const deckX = isChance ? BOARD_CX - DECK_D : BOARD_CX + DECK_D;
|
||||
const deckY = isChance ? BOARD_CY + DECK_D : BOARD_CY - DECK_D;
|
||||
const deckRot = isChance ? -0.14 : 0.12;
|
||||
|
||||
// Full popup card size; container scales up from deck visual width
|
||||
const CW = 360, CH = 480;
|
||||
const startScale = 88 / CW; // ≈ 0.244
|
||||
|
||||
// Dim overlay (not in dyn — destroyed at end of animation)
|
||||
const overlay = this.add.rectangle(
|
||||
GAME_WIDTH / 2, GAME_HEIGHT / 2, GAME_WIDTH, GAME_HEIGHT, 0x000000, 0
|
||||
).setDepth(DEPTH.popup - 2);
|
||||
this.tweens.add({ targets: overlay, alpha: 0.6, duration: 500, ease: 'Linear' });
|
||||
|
||||
// Container starts at deck position, scaled and rotated to match deck
|
||||
const container = this.add.container(deckX, deckY)
|
||||
.setDepth(DEPTH.popup - 1)
|
||||
.setScale(startScale)
|
||||
.setRotation(deckRot);
|
||||
|
||||
// ── Back face (matches the face-down deck appearance) ─────────────────
|
||||
const backGfx = this.add.graphics();
|
||||
backGfx.fillStyle(cardColor, 1);
|
||||
backGfx.lineStyle(3, 0xFFF8E7, 1);
|
||||
backGfx.fillRoundedRect(-CW / 2, -CH / 2, CW, CH, 16);
|
||||
backGfx.strokeRoundedRect(-CW / 2, -CH / 2, CW, CH, 16);
|
||||
backGfx.lineStyle(2, 0xFFF8E7, 0.75);
|
||||
backGfx.strokeRoundedRect(-CW / 2 + 14, -CH / 2 + 14, CW - 28, CH - 28, 10);
|
||||
container.add(backGfx);
|
||||
|
||||
const backLabel = this.add.text(0, 0, isChance ? 'Chance' : 'Community\nChest', {
|
||||
fontFamily: 'Righteous', fontSize: '36px', color: '#FFF8E7', align: 'center',
|
||||
}).setOrigin(0.5);
|
||||
container.add(backLabel);
|
||||
|
||||
// ── Front face (matches drawCardPopup content, hidden until flip) ─────
|
||||
const frontObjs = [];
|
||||
|
||||
const frontBg = this.add.graphics();
|
||||
frontBg.fillStyle(cardColor, 1);
|
||||
frontBg.lineStyle(4, 0xFFF8E7, 1);
|
||||
frontBg.fillRoundedRect(-CW / 2, -CH / 2, CW, CH, 16);
|
||||
frontBg.strokeRoundedRect(-CW / 2, -CH / 2, CW, CH, 16);
|
||||
frontBg.setVisible(false);
|
||||
container.add(frontBg);
|
||||
frontObjs.push(frontBg);
|
||||
|
||||
if (this.hasCards) {
|
||||
const frame = isChance ? CARD_FRAME.chance : CARD_FRAME.community_chest;
|
||||
const art = this.add.image(0, -CH / 2 + 120, 'monopoly-cards', frame)
|
||||
.setDisplaySize(CW - 20, 220).setVisible(false);
|
||||
container.add(art);
|
||||
frontObjs.push(art);
|
||||
} else {
|
||||
const fallBg = this.add.graphics();
|
||||
fallBg.fillStyle(0xffffff, 0.15);
|
||||
fallBg.fillRoundedRect(-CW / 2 + 10, -CH / 2 + 10, CW - 20, 210, 12);
|
||||
fallBg.setVisible(false);
|
||||
container.add(fallBg);
|
||||
frontObjs.push(fallBg);
|
||||
|
||||
const icon = this.add.text(0, -CH / 2 + 110, isChance ? '?' : '📦', {
|
||||
fontFamily: 'Righteous', fontSize: '80px', color: '#ffffff',
|
||||
}).setOrigin(0.5).setVisible(false);
|
||||
container.add(icon);
|
||||
frontObjs.push(icon);
|
||||
}
|
||||
|
||||
const frontTitle = this.add.text(0, -CH / 2 + 30, isChance ? 'CHANCE' : 'COMMUNITY CHEST', {
|
||||
fontFamily: 'Righteous', fontSize: '18px', color: '#FFF8E7',
|
||||
}).setOrigin(0.5).setVisible(false);
|
||||
container.add(frontTitle);
|
||||
frontObjs.push(frontTitle);
|
||||
|
||||
const frontBody = this.add.text(0, -CH / 2 + 250, text, {
|
||||
fontFamily: '"Julius Sans One"', fontSize: '18px', color: '#FFF8E7',
|
||||
align: 'center', wordWrap: { width: CW - 30 },
|
||||
}).setOrigin(0.5, 0).setVisible(false);
|
||||
container.add(frontBody);
|
||||
frontObjs.push(frontBody);
|
||||
|
||||
// Phase 1: fly from deck to center, grow, straighten (700 ms)
|
||||
await new Promise(resolve => {
|
||||
this.tweens.add({
|
||||
targets: container,
|
||||
x: GAME_WIDTH / 2,
|
||||
y: GAME_HEIGHT / 2,
|
||||
scaleX: 1, scaleY: 1,
|
||||
rotation: 0,
|
||||
duration: 700,
|
||||
ease: 'Cubic.easeOut',
|
||||
onComplete: resolve,
|
||||
});
|
||||
});
|
||||
|
||||
// Phase 2a: flip first half — collapse to zero width
|
||||
await new Promise(resolve => {
|
||||
this.tweens.add({
|
||||
targets: container,
|
||||
scaleX: 0,
|
||||
duration: 180,
|
||||
ease: 'Sine.easeIn',
|
||||
onComplete: resolve,
|
||||
});
|
||||
});
|
||||
|
||||
// Swap faces at zero-width moment
|
||||
backGfx.setVisible(false);
|
||||
backLabel.setVisible(false);
|
||||
frontObjs.forEach(o => o.setVisible(true));
|
||||
|
||||
// Phase 2b: flip second half — expand back to full width
|
||||
await new Promise(resolve => {
|
||||
this.tweens.add({
|
||||
targets: container,
|
||||
scaleX: 1,
|
||||
duration: 180,
|
||||
ease: 'Sine.easeOut',
|
||||
onComplete: resolve,
|
||||
});
|
||||
});
|
||||
|
||||
// Cleanup — render() will immediately draw the static popup at the same coords
|
||||
container.each(child => { try { child.destroy(); } catch {} });
|
||||
container.destroy();
|
||||
overlay.destroy();
|
||||
}
|
||||
|
||||
// ── Card Popup ─────────────────────────────────────────────────────────────
|
||||
drawCardPopup() {
|
||||
if (!this.gs.pendingCard) return;
|
||||
|
|
@ -751,6 +1018,15 @@ export default class MonopolyGame extends Phaser.Scene {
|
|||
fontFamily:'"Julius Sans One"', fontSize:'18px', color:'#FFF8E7',
|
||||
align:'center', wordWrap:{ width: pw - 30 },
|
||||
}).setOrigin(0.5, 0).setDepth(DEPTH.popup+1));
|
||||
|
||||
// OK button — only shown on the human player's turn
|
||||
if (this.gs.current === this.humanSeat) {
|
||||
const btn = new Button(this, px + pw/2, py + ph - 36, 'OK', () => this.onDismissCard(), {
|
||||
width: 220, height: 48, fontSize: 20,
|
||||
});
|
||||
btn.setDepth(DEPTH.popup + 2);
|
||||
this.reg(btn);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Auction Panel ──────────────────────────────────────────────────────────
|
||||
|
|
@ -1066,6 +1342,33 @@ export default class MonopolyGame extends Phaser.Scene {
|
|||
return;
|
||||
}
|
||||
|
||||
// Guard 3: animate card draw once per card event (human and AI)
|
||||
if (gs.phase === 'card' && gs.pendingCard && !this.cardAnimPlayed) {
|
||||
this.busy = true;
|
||||
this.hidePortraits();
|
||||
this.animateCardDraw().then(() => {
|
||||
this.cardAnimPlayed = true;
|
||||
this.busy = false;
|
||||
this.render();
|
||||
this.advance();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Guard 4: animate rent payment (human and AI)
|
||||
if (gs.phase === 'rent' && gs.pendingRent) {
|
||||
this.busy = true;
|
||||
this.hidePortraits();
|
||||
this.animateRent().then(() => {
|
||||
this.gs = applyRent(this.gs);
|
||||
this.showPortraits();
|
||||
this.busy = false;
|
||||
this.render();
|
||||
this.advance();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine who acts next
|
||||
let actingSeat = gs.current;
|
||||
if (gs.phase === 'auction' && gs.pendingAuction) {
|
||||
|
|
@ -1147,7 +1450,6 @@ export default class MonopolyGame extends Phaser.Scene {
|
|||
await this.delay(700); // AI "thinking" pause
|
||||
if (buy) {
|
||||
this.gs = buyProperty(this.gs, seat);
|
||||
playSound(this, SFX.purchase);
|
||||
await this.dismissPropertyModal(); // fill with owner color + zoom back
|
||||
} else {
|
||||
this.gs = declineProperty(this.gs, seat);
|
||||
|
|
@ -1158,6 +1460,7 @@ export default class MonopolyGame extends Phaser.Scene {
|
|||
}
|
||||
case 'card': {
|
||||
await this.delay(2800);
|
||||
this.cardAnimPlayed = false;
|
||||
this.gs = applyCardEffect(this.gs, seat);
|
||||
this.render();
|
||||
// If card moved player to buy or another phase, handle next advance
|
||||
|
|
@ -1450,6 +1753,7 @@ export default class MonopolyGame extends Phaser.Scene {
|
|||
|
||||
async animateModalFill(seat) {
|
||||
if (!this.modalContainer) return;
|
||||
playSound(this, SFX.MONOPOLY_PURCHASE);
|
||||
const fillGfx = this.add.graphics();
|
||||
this.modalContainer.add(fillGfx);
|
||||
const proxy = { h: 0 };
|
||||
|
|
@ -1564,7 +1868,6 @@ export default class MonopolyGame extends Phaser.Scene {
|
|||
if (this.busy) return;
|
||||
this.busy = true;
|
||||
this.gs = buyProperty(this.gs, this.humanSeat); // owner set → dismissPropertyModal fills
|
||||
playSound(this, SFX.purchase);
|
||||
await this.dismissPropertyModal(); // fill + zoom back
|
||||
this.busy = false;
|
||||
this.advance();
|
||||
|
|
@ -1581,6 +1884,7 @@ export default class MonopolyGame extends Phaser.Scene {
|
|||
|
||||
onDismissCard() {
|
||||
if (this.busy) return;
|
||||
this.cardAnimPlayed = false;
|
||||
this.gs = applyCardEffect(this.gs, this.humanSeat);
|
||||
this.render();
|
||||
this.advance();
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ export function createInitialState({ playerCount, names, seed = Date.now() }) {
|
|||
pendingCard: null,
|
||||
pendingBuy: null,
|
||||
pendingAuction: null,
|
||||
pendingRent: null,
|
||||
winner: null,
|
||||
log: [],
|
||||
};
|
||||
|
|
@ -309,10 +310,9 @@ export function resolveSpace(state, seat) {
|
|||
} else {
|
||||
const dice = s.diceRoll[0] + s.diceRoll[1];
|
||||
const rent = calculateRent(s, spIdx, dice);
|
||||
const result = payTo(s, seat, own.owner, rent);
|
||||
Object.assign(s, result);
|
||||
s.pendingRent = { payer: seat, receiver: own.owner, amount: rent };
|
||||
log(s, `${p.name} pays $${rent} rent to ${s.players[own.owner].name}.`);
|
||||
s.phase = 'endturn';
|
||||
s.phase = 'rent';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -699,6 +699,16 @@ export function useJailCard(state, seat) {
|
|||
return s;
|
||||
}
|
||||
|
||||
export function applyRent(state) {
|
||||
const s = clone(state);
|
||||
const { payer, receiver, amount } = s.pendingRent;
|
||||
const result = payTo(s, payer, receiver, amount);
|
||||
Object.assign(s, result);
|
||||
s.pendingRent = null;
|
||||
s.phase = 'endturn';
|
||||
return s;
|
||||
}
|
||||
|
||||
// ── Turn ──────────────────────────────────────────────────────────────────────
|
||||
export function endTurn(state) {
|
||||
const s = clone(state);
|
||||
|
|
|
|||
|
|
@ -136,6 +136,9 @@ export default class PreloadScene extends Phaser.Scene {
|
|||
this.load.spritesheet('monopoly-pawns', '/assets/images/monopoly-pawns.png', { frameWidth: 80, frameHeight: 80 });
|
||||
// Monopoly card art: frame 0 = Chance, frame 1 = Community Chest, at 200×300.
|
||||
this.load.spritesheet('monopoly-cards', '/assets/images/monopoly-cards.png', { frameWidth: 200, frameHeight: 300 });
|
||||
this.load.audio('sfx-monopoly-purchase', '/assets/fx/monopoly-purchase.mp3');
|
||||
this.load.audio('sfx-monopoly-expense', '/assets/fx/monopoly-expense.mp3');
|
||||
this.load.audio('sfx-monopoly-paid', '/assets/fx/monopoly-paid.mp3');
|
||||
}
|
||||
|
||||
async create() {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ export const SFX = {
|
|||
SCIFI_RISER: 'sfx-scifi-riser',
|
||||
SCIFI_REVEAL: 'sfx-scifi-reveal',
|
||||
SCIFI_WOOSH: 'sfx-scifi-woosh',
|
||||
MONOPOLY_PURCHASE: 'sfx-monopoly-purchase',
|
||||
MONOPOLY_EXPENSE: 'sfx-monopoly-expense',
|
||||
MONOPOLY_PAID: 'sfx-monopoly-paid',
|
||||
};
|
||||
|
||||
export function playSound(scene, key) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue