feat(catan): enhance robber mechanics, dice animations, and resource hand UI

- Add animated robber movement with custom sprite, sound effects, and glow
- Implement dynamic dice roll animation with arc trajectory and sound cues
- Replace static resource hand with draggable, selectable cards
- Add victory points badge above player portrait
- Expand trade panel width and button sizes for better usability
- Load new robber asset and speech clips in preload scene
This commit is contained in:
Brian Fertig 2026-05-23 15:19:39 -06:00
parent 29fb4a9b2f
commit aa5609f2f9
19 changed files with 246 additions and 58 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 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.

View File

@ -5,6 +5,7 @@ import { auth } from '../../services/auth.js';
import { api } from '../../services/api.js'; import { api } from '../../services/api.js';
import { createOpponentPortrait, createPlayerPortrait } from '../../ui/Portrait.js'; import { createOpponentPortrait, createPlayerPortrait } from '../../ui/Portrait.js';
import { playSound, SFX } from '../../ui/Sounds.js'; import { playSound, SFX } from '../../ui/Sounds.js';
import { enqueue as enqueueSpeech } from '../../ui/SpeechQueue.js';
import { MusicPlayer } from '../../ui/MusicPlayer.js'; import { MusicPlayer } from '../../ui/MusicPlayer.js';
import { import {
NODES, EDGES, HEXES, PORT_SLOTS, RESOURCE_INFO, RESOURCE_TYPES, DESERT_COLOR, NODES, EDGES, HEXES, PORT_SLOTS, RESOURCE_INFO, RESOURCE_TYPES, DESERT_COLOR,
@ -32,6 +33,9 @@ export default class CatanGame extends Phaser.Scene {
this.opponentPortraits = []; this.opponentPortraits = [];
this.buttons = {}; this.buttons = {};
this.placeMode = null; // 'road' | 'settlement' | 'city' | null this.placeMode = null; // 'road' | 'settlement' | 'city' | null
this.handDisplay = [];
this.handCardObjs = [];
this.handSelectedIdx = null;
} }
create() { create() {
@ -264,21 +268,76 @@ export default class CatanGame extends Phaser.Scene {
animateDice(values) { animateDice(values) {
return new Promise((resolve) => { return new Promise((resolve) => {
playSound(this, SFX.DICE_ROLL); playSound(this, SFX.DICE_ROLL);
this.diceContainers.forEach((c) => c.setAlpha(1));
let elapsed = 0; const total = 650; const landX = [1256, 1324];
const tick = () => { const landY = 950;
const startX = GAME_WIDTH / 2; // 960 — center of bottom bar
const startY = 1015;
const arcY = 755; // arc peak
// Move dice to throw origin, small, random angle
this.diceContainers.forEach((c, i) => {
c.setAlpha(1).setScale(0.35).setAngle(Phaser.Math.Between(0, 359))
.setPosition(startX + (i === 0 ? -12 : 12), startY);
this.drawDie(this.diceG[i], Phaser.Math.Between(1, 6));
});
// Cycle random faces while airborne
let cyclerStopped = false;
const cycler = this.time.addEvent({
delay: 55, loop: true,
callback: () => {
this.drawDie(this.diceG[0], Phaser.Math.Between(1, 6)); this.drawDie(this.diceG[0], Phaser.Math.Between(1, 6));
this.drawDie(this.diceG[1], Phaser.Math.Between(1, 6)); this.drawDie(this.diceG[1], Phaser.Math.Between(1, 6));
elapsed += 70; },
if (elapsed < total) this.time.delayedCall(70, tick); });
else { const stopCycler = () => { if (!cyclerStopped) { cyclerStopped = true; cycler.remove(); } };
this.drawDie(this.diceG[0], values[0]);
this.drawDie(this.diceG[1], values[1]); let settled = 0;
this.diceContainers.forEach((c) => this.tweens.add({ targets: c, scale: 1.18, duration: 90, yoyo: true })); this.diceContainers.forEach((c, i) => {
this.time.delayedCall(140, resolve); const lx = landX[i] + (Math.random() * 8 - 4);
const ly = landY + (Math.random() * 8 - 4);
const outMs = 295 + i * 32;
const backMs = 430 + i * 44;
const totalMs = outMs + backMs;
// X flies straight to landing; Y arcs up then bounces down
this.tweens.add({ targets: c, x: lx, duration: totalMs, ease: 'Quad.Out' });
this.tweens.chain({ targets: c, tweens: [
{ y: arcY, duration: outMs, ease: 'Quad.Out' },
{ y: ly, duration: backMs, ease: 'Bounce.Out' },
]});
// Scale up as die approaches
this.tweens.add({ targets: c, scale: 1, duration: outMs + backMs * 0.55, ease: 'Quad.Out' });
// Spin
this.tweens.add({
targets: c,
angle: c.angle + 540 + Math.random() * 180,
duration: totalMs,
ease: 'Quad.Out',
});
this.time.delayedCall(totalMs, () => {
stopCycler();
this.drawDie(this.diceG[i], values[i]);
// Snap to nearest upright angle with a small wiggle
const upright = Math.round(c.angle / 90) * 90 + (Math.random() * 10 - 5);
this.tweens.add({
targets: c, angle: upright, duration: 120, ease: 'Back.Out',
onComplete: () => {
settled++;
if (settled === 2) {
this.diceContainers.forEach((dc) =>
this.tweens.add({ targets: dc, scaleX: 1.14, scaleY: 0.88, duration: 80, yoyo: true })
);
const numberWords = ['','one','two','three','four','five','six','seven','eight','nine','ten','eleven','twelve'];
enqueueSpeech(`numbers-${numberWords[values[0] + values[1]]}`);
this.time.delayedCall(160, resolve);
} }
}; },
tick(); });
});
});
}); });
} }
@ -290,38 +349,15 @@ export default class CatanGame extends Phaser.Scene {
// human portrait // human portrait
createPlayerPortrait(this, 90, 980, 64, D.hud, 'CatanGame'); createPlayerPortrait(this, 90, 980, 64, D.hud, 'CatanGame');
// VP badge above portrait — graphics redrawn in updateHand() once gs is available
this.playerVpBadgeGfx = this.add.graphics().setDepth(D.hud + 5);
this.playerVpText = this.add.text(90, 906, '0', {
fontFamily: '"Julius Sans One"', fontSize: '15px', color: '#ffffff', fontStyle: 'bold',
}).setOrigin(0.5).setDepth(D.hud + 6);
this.add.text(90, 1056, auth.user?.username ?? 'You', { this.add.text(90, 1056, auth.user?.username ?? 'You', {
fontFamily: '"Julius Sans One"', fontSize: '16px', color: COLORS.textHex, fontFamily: '"Julius Sans One"', fontSize: '16px', color: COLORS.textHex,
}).setOrigin(0.5).setDepth(D.hud); }).setOrigin(0.5).setDepth(D.hud);
// resource hand
this.resText = {};
const startX = 230, gap = 86;
const cardW = 60, cardH = 84, cardR = 6, borderW = 3;
RESOURCE_TYPES.forEach((r, i) => {
const x = startX + i * gap, y = 950;
// 1. Dark background fill
const gFill = this.add.graphics().setDepth(D.hud);
gFill.fillStyle(0x111111, 0.85);
gFill.fillRoundedRect(x - cardW / 2, y - cardH / 2, cardW, cardH, cardR);
// 2. Card artwork (270×390 source → 54×78 display)
this.add.image(x, y, 'catan-cards', i).setDisplaySize(54, 78).setDepth(D.hud);
// 3. Colored border on top of artwork
const gBorder = this.add.graphics().setDepth(D.hud);
gBorder.lineStyle(borderW, RESOURCE_INFO[r].swatch, 1);
gBorder.strokeRoundedRect(x - cardW / 2, y - cardH / 2, cardW, cardH, cardR);
// 4. Resource label below card
this.add.text(x, y + cardH / 2 + 9, RESOURCE_INFO[r].label, {
fontFamily: '"Julius Sans One"', fontSize: '11px', color: COLORS.mutedHex,
}).setOrigin(0.5, 0).setDepth(D.hud);
// 5. Quantity number over artwork
this.resText[r] = this.add.text(x, y + 6, '0', {
fontFamily: 'Righteous', fontSize: '26px', color: '#ffffff',
stroke: '#000000', strokeThickness: 4,
shadow: { color: '#000000', fill: true, offsetX: 2, offsetY: 2, blur: 3 },
}).setOrigin(0.5).setDepth(D.hud);
});
// dev card hand area label // dev card hand area label
this.devHandContainer = this.add.container(0, 0).setDepth(D.hud); this.devHandContainer = this.add.container(0, 0).setDepth(D.hud);
this.add.text(740, 916, 'Development Cards', { this.add.text(740, 916, 'Development Cards', {
@ -745,19 +781,160 @@ export default class CatanGame extends Phaser.Scene {
renderRobber() { renderRobber() {
if (this.robberObj) this.robberObj.destroy(); if (this.robberObj) this.robberObj.destroy();
const { x, y } = this.hexPos(this.gs.robberHex); const { x, y } = this.hexPos(this.gs.robberHex);
const g = this.add.graphics(); this.robberObj = this.add.image(x, y, 'catan-robber')
g.fillStyle(0x000000, 0.3); g.fillEllipse(2, 30, 30, 10); .setDisplaySize(64, 64)
g.fillStyle(0x2b2b2b, 1); .setDepth(D.robber);
g.fillEllipse(0, 26, 30, 14); // base this.robberObj.postFX.addGlow(0x000000, 8, 0, false, 0.1, 24);
g.fillRoundedRect(-11, -2, 22, 30, 8); // body }
g.fillCircle(0, -10, 12); // head
g.lineStyle(2, 0x000000, 0.5); g.strokeCircle(0, -10, 12); async animateRobber(fromHexId, toHexId) {
this.robberObj = this.add.container(x, y - 14, [g]).setDepth(D.robber); const clip = `catan-robber-0${Phaser.Math.Between(1, 4)}`;
enqueueSpeech(clip);
if (this.robberObj) this.robberObj.destroy();
const from = this.hexPos(fromHexId);
const to = this.hexPos(toHexId);
this.robberObj = this.add.image(from.x, from.y, 'catan-robber')
.setDisplaySize(64, 64)
.setDepth(D.robber);
this.robberObj.postFX.addGlow(0x000000, 8, 0, false, 0.1, 24);
const base = this.robberObj.scaleX;
await new Promise(resolve => {
this.tweens.add({
targets: this.robberObj,
x: to.x, y: to.y,
duration: 3000,
ease: 'Sine.InOut',
});
this.tweens.chain({
targets: this.robberObj,
tweens: [
{ scaleX: base * 2, scaleY: base * 2, duration: 1500, ease: 'Sine.Out' },
{ scaleX: base, scaleY: base, duration: 1500, ease: 'Sine.In', onComplete: resolve },
],
});
});
} }
updateHand() { updateHand() {
const p = this.gs.players[0]; const p = this.gs.players[0];
for (const r of RESOURCE_TYPES) this.resText[r].setText(String(p.resources[r]));
// Sync handDisplay with game state, preserving drag order
const updated = [...this.handDisplay];
for (const r of RESOURCE_TYPES) {
const have = updated.filter(x => x === r).length;
const need = p.resources[r];
if (have > need) {
let removed = 0;
for (let i = updated.length - 1; i >= 0 && removed < have - need; i--) {
if (updated[i] === r) { updated.splice(i, 1); removed++; }
}
} else {
for (let k = 0; k < need - have; k++) updated.push(r);
}
}
if (JSON.stringify(updated) !== JSON.stringify(this.handDisplay)) this.handSelectedIdx = null;
this.handDisplay = updated;
this.renderHand();
// VP badge
const col = PLAYER_COLORS[p.colorIndex];
this.playerVpBadgeGfx.clear();
this.playerVpBadgeGfx.fillStyle(col.hexDark, 1); this.playerVpBadgeGfx.fillCircle(90, 906, 18);
this.playerVpBadgeGfx.lineStyle(2.5, col.hex, 0.9); this.playerVpBadgeGfx.strokeCircle(90, 906, 18);
this.playerVpText.setText(String(L.victoryPoints(this.gs, 0)));
}
renderHand() {
this.handCardObjs.forEach(c => c.destroy());
this.handCardObjs = [];
const N = this.handDisplay.length;
if (N === 0) return;
const cardW = 60, cardH = 84, cardR = 6, borderW = 3;
const step = Math.min(66, Math.max(34, 490 / Math.max(N - 1, 1)));
this.handDisplay.forEach((resource, idx) => {
const x = 220 + idx * step;
const frameIdx = RESOURCE_TYPES.indexOf(resource);
const c = this.add.container(x, 950).setDepth(D.hud + idx);
const bg = this.add.graphics();
bg.fillStyle(0x111111, 0.85);
bg.fillRoundedRect(-cardW / 2, -cardH / 2, cardW, cardH, cardR);
const img = this.add.image(0, 0, 'catan-cards', frameIdx).setDisplaySize(54, 78);
const border = this.add.graphics();
border.lineStyle(borderW, RESOURCE_INFO[resource].swatch, 1);
border.strokeRoundedRect(-cardW / 2, -cardH / 2, cardW, cardH, cardR);
c.add([bg, img, border]);
if (idx === this.handSelectedIdx) {
const sel = this.add.graphics();
sel.lineStyle(3, 0xffd700, 1);
sel.strokeRoundedRect(-cardW / 2 - 3, -cardH / 2 - 3, cardW + 6, cardH + 6, cardR + 2);
c.add(sel);
c.setScale(1.1).setDepth(D.hud + 20);
}
c.setSize(cardW, cardH).setInteractive();
this._setupHandDrag(c, idx);
this.handCardObjs.push(c);
});
}
_setupHandDrag(container, idx) {
const THRESHOLD = 8;
let dragging = false, origX = 0, pointerStartX = 0;
let slotIndicator = null;
const cancelDrag = () => {
if (slotIndicator) { slotIndicator.destroy(); slotIndicator = null; }
this.input.off('pointermove', onSceneMove);
this.input.off('pointerup', onSceneUp);
};
const onSceneMove = (ptr) => {
container.x = origX + (ptr.x - pointerStartX);
const N = this.handDisplay.length;
const step = Math.min(66, Math.max(34, 490 / Math.max(N - 1, 1)));
const newIdx = Math.max(0, Math.min(N - 1, Math.round((ptr.x - 220) / step)));
if (!slotIndicator) slotIndicator = this.add.rectangle(0, 950, 6, 84, COLORS.accent, 0.8).setDepth(D.hud + 49);
slotIndicator.x = 220 + newIdx * step - step / 2;
};
const onSceneUp = (ptr) => {
cancelDrag();
dragging = false;
const N = this.handDisplay.length;
const step = Math.min(66, Math.max(34, 490 / Math.max(N - 1, 1)));
const newIdx = Math.max(0, Math.min(N - 1, Math.round((ptr.x - 220) / step)));
if (newIdx !== idx) {
const [card] = this.handDisplay.splice(idx, 1);
this.handDisplay.splice(newIdx, 0, card);
}
this.handSelectedIdx = null;
this.renderHand();
};
container.on('pointerdown', (ptr) => {
origX = container.x; pointerStartX = ptr.x; dragging = false;
});
container.on('pointermove', (ptr) => {
if (this.handSelectedIdx !== idx) return;
if (!dragging && Math.abs(ptr.x - pointerStartX) >= THRESHOLD) {
dragging = true;
container.setDepth(D.hud + 50);
this.input.on('pointermove', onSceneMove);
this.input.on('pointerup', onSceneUp);
}
});
container.on('pointerup', () => {
if (!dragging) {
// Click: toggle selection
this.handSelectedIdx = this.handSelectedIdx === idx ? null : idx;
this.renderHand();
}
});
} }
updateDevHand() { updateDevHand() {
@ -926,7 +1103,9 @@ export default class CatanGame extends Phaser.Scene {
this.gs = L.playKnight(this.gs, seat); this.gs = L.playKnight(this.gs, seat);
this.renderAll(); await this.delay(400); this.renderAll(); await this.delay(400);
const m = AI.chooseRobberMove(this.gs, seat); const m = AI.chooseRobberMove(this.gs, seat);
const preRobberHex = this.gs.robberHex;
this.gs = L.moveRobber(this.gs, m.hexId, m.targetSeat); this.gs = L.moveRobber(this.gs, m.hexId, m.targetSeat);
await this.animateRobber(preRobberHex, m.hexId);
this.renderAll(); await this.delay(400); this.renderAll(); await this.delay(400);
} }
if (this.gs.phase === 'rollPhase') { if (this.gs.phase === 'rollPhase') {
@ -947,8 +1126,10 @@ export default class CatanGame extends Phaser.Scene {
const seat = this.gs.currentPlayer; const seat = this.gs.currentPlayer;
await this.delay(450); await this.delay(450);
const m = AI.chooseRobberMove(this.gs, seat); const m = AI.chooseRobberMove(this.gs, seat);
const preRobberHex = this.gs.robberHex;
this.gs = L.moveRobber(this.gs, m.hexId, m.targetSeat); this.gs = L.moveRobber(this.gs, m.hexId, m.targetSeat);
if (m.targetSeat != null) this.opponentPortraits[seat]?.playEmotion?.('happy'); if (m.targetSeat != null) this.opponentPortraits[seat]?.playEmotion?.('happy');
await this.animateRobber(preRobberHex, m.hexId);
this.renderAll(); this.renderAll();
await this.delay(450); await this.delay(450);
this.busy = false; this.busy = false;
@ -966,7 +1147,9 @@ export default class CatanGame extends Phaser.Scene {
this.gs = this.applyAction(seat, a); this.gs = this.applyAction(seat, a);
if (this.gs.phase === 'moveRobber') { if (this.gs.phase === 'moveRobber') {
const m = AI.chooseRobberMove(this.gs, seat); const m = AI.chooseRobberMove(this.gs, seat);
const preRobberHex = this.gs.robberHex;
this.gs = L.moveRobber(this.gs, m.hexId, m.targetSeat); this.gs = L.moveRobber(this.gs, m.hexId, m.targetSeat);
await this.animateRobber(preRobberHex, m.hexId);
} }
this.renderAll(); this.renderAll();
await this.delay(480); await this.delay(480);
@ -1035,11 +1218,13 @@ export default class CatanGame extends Phaser.Scene {
for (const hex of this.gs.hexes) { for (const hex of this.gs.hexes) {
if (hex.hasRobber) continue; if (hex.hasRobber) continue;
const { x, y } = this.hexPos(hex.id); const { x, y } = this.hexPos(hex.id);
this.addHighlight(x, y, () => { this.addHighlight(x, y, async () => {
this.clearHighlights(); this.clearHighlights();
const targets = L.stealTargets(this.gs, hex.id, 0); const targets = L.stealTargets(this.gs, hex.id, 0);
if (targets.length <= 1) { if (targets.length <= 1) {
const preRobberHex = this.gs.robberHex;
this.gs = L.moveRobber(this.gs, hex.id, targets[0] ?? null); this.gs = L.moveRobber(this.gs, hex.id, targets[0] ?? null);
await this.animateRobber(preRobberHex, hex.id);
this.advance(); this.advance();
} else { } else {
this.pickStealTarget(hex.id, targets); this.pickStealTarget(hex.id, targets);
@ -1051,9 +1236,11 @@ export default class CatanGame extends Phaser.Scene {
pickStealTarget(hexId, targets) { pickStealTarget(hexId, targets) {
const panel = this.modalPanel(540, 'Steal from which player?'); const panel = this.modalPanel(540, 'Steal from which player?');
targets.forEach((seat, i) => { targets.forEach((seat, i) => {
this.modalButton(panel, 1000, 480 + i * 64, `${this.pname(seat)} (${L.handSize(this.gs.players[seat])} cards)`, () => { this.modalButton(panel, 1000, 480 + i * 64, `${this.pname(seat)} (${L.handSize(this.gs.players[seat])} cards)`, async () => {
panel.destroy(); panel.destroy();
const preRobberHex = this.gs.robberHex;
this.gs = L.moveRobber(this.gs, hexId, seat); this.gs = L.moveRobber(this.gs, hexId, seat);
await this.animateRobber(preRobberHex, hexId);
this.advance(); this.advance();
}); });
}); });
@ -1168,7 +1355,7 @@ export default class CatanGame extends Phaser.Scene {
const give = { brick: 0, lumber: 0, wool: 0, grain: 0, ore: 0 }; const give = { brick: 0, lumber: 0, wool: 0, grain: 0, ore: 0 };
const get = { brick: 0, lumber: 0, wool: 0, grain: 0, ore: 0 }; const get = { brick: 0, lumber: 0, wool: 0, grain: 0, ore: 0 };
const overlay = this.add.rectangle(GAME_WIDTH / 2, GAME_HEIGHT / 2, GAME_WIDTH, GAME_HEIGHT, 0x000000, 0.6).setInteractive().setDepth(D.panel); const overlay = this.add.rectangle(GAME_WIDTH / 2, GAME_HEIGHT / 2, GAME_WIDTH, GAME_HEIGHT, 0x000000, 0.6).setInteractive().setDepth(D.panel);
const box = this.add.rectangle(1000, 470, 760, 540, COLORS.panel, 1).setStrokeStyle(3, COLORS.accent).setDepth(D.panel); const box = this.add.rectangle(1000, 470, 920, 540, COLORS.panel, 1).setStrokeStyle(3, COLORS.accent).setDepth(D.panel);
const title = this.add.text(1000, 240, 'Trade', { fontFamily: 'Righteous', fontSize: '34px', color: COLORS.goldHex }).setOrigin(0.5).setDepth(D.panel + 1); const title = this.add.text(1000, 240, 'Trade', { fontFamily: 'Righteous', fontSize: '34px', color: COLORS.goldHex }).setOrigin(0.5).setDepth(D.panel + 1);
const hintGive = this.add.text(760, 300, 'You give', { fontFamily: '"Julius Sans One"', fontSize: '18px', color: COLORS.textHex }).setOrigin(0.5).setDepth(D.panel + 1); const hintGive = this.add.text(760, 300, 'You give', { fontFamily: '"Julius Sans One"', fontSize: '18px', color: COLORS.textHex }).setOrigin(0.5).setDepth(D.panel + 1);
const hintGet = this.add.text(1240, 300, 'You get', { fontFamily: '"Julius Sans One"', fontSize: '18px', color: COLORS.textHex }).setOrigin(0.5).setDepth(D.panel + 1); const hintGet = this.add.text(1240, 300, 'You get', { fontFamily: '"Julius Sans One"', fontSize: '18px', color: COLORS.textHex }).setOrigin(0.5).setDepth(D.panel + 1);
@ -1208,9 +1395,9 @@ export default class CatanGame extends Phaser.Scene {
} }
} }
this.flashStatus('Bank trade needs N of one resource for 1 of another (N = your ratio).'); this.flashStatus('Bank trade needs N of one resource for 1 of another (N = your ratio).');
}, { width: 220, height: 48 }).setDepth(D.panel + 1); }, { width: 260, height: 48 }).setDepth(D.panel + 1);
const offerBtn = new Button(this, 1150, 640, 'Offer to Players', () => { const offerBtn = new Button(this, 1160, 640, 'Offer to Players', () => {
const gCount = RESOURCE_TYPES.reduce((s, r) => s + give[r], 0); const gCount = RESOURCE_TYPES.reduce((s, r) => s + give[r], 0);
const tCount = RESOURCE_TYPES.reduce((s, r) => s + get[r], 0); const tCount = RESOURCE_TYPES.reduce((s, r) => s + get[r], 0);
if (!gCount || !tCount) { this.flashStatus('Set what you give and get.'); return; } if (!gCount || !tCount) { this.flashStatus('Set what you give and get.'); return; }
@ -1225,7 +1412,7 @@ export default class CatanGame extends Phaser.Scene {
playSound(this, SFX.CARD_PLACE); playSound(this, SFX.CARD_PLACE);
this.flashStatus(`${this.pname(accepted)} accepted the trade.`); this.flashStatus(`${this.pname(accepted)} accepted the trade.`);
this.advance(); this.advance();
}, { width: 220, height: 48 }).setDepth(D.panel + 1); }, { width: 300, height: 48 }).setDepth(D.panel + 1);
const cancelBtn = new Button(this, 1000, 700, 'Cancel', () => close(), { variant: 'ghost', width: 160, height: 44 }).setDepth(D.panel + 1); const cancelBtn = new Button(this, 1000, 700, 'Cancel', () => close(), { variant: 'ghost', width: 160, height: 44 }).setDepth(D.panel + 1);
objs.push(bankBtn, offerBtn, cancelBtn); objs.push(bankBtn, offerBtn, cancelBtn);

View File

@ -38,6 +38,7 @@ export default class PreloadScene extends Phaser.Scene {
frameWidth: 312, frameWidth: 312,
frameHeight: 312, frameHeight: 312,
}); });
this.load.image('catan-robber', '/assets/images/catan-robber.png');
this.load.image('bg-menu', '/assets/images/background-menu.png'); this.load.image('bg-menu', '/assets/images/background-menu.png');
this.load.image('bg-room', '/assets/images/background-room.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-casino', '/assets/images/background-casino.png');