feat: enhance Craps roll results with sequential badges and fireworks
Adds animated "Win!" or "Lose" badges that appear sequentially for each player after a roll, accompanied by sound effects. Introduces a colorful fireworks particle animation for wins. Adjusts dice landing positions for better visibility and removes redundant emotion/sound calls in favor of the new sequential result display.
This commit is contained in:
parent
327d3d7019
commit
f8d406ab4e
|
|
@ -259,7 +259,7 @@ export default class CrapsGame extends Phaser.Scene {
|
||||||
const ox = SEAT_X[this.gs.shooterIndex];
|
const ox = SEAT_X[this.gs.shooterIndex];
|
||||||
const oy = SEAT_Y - 60;
|
const oy = SEAT_Y - 60;
|
||||||
return Promise.all(this.diceGfx.map((g, i) =>
|
return Promise.all(this.diceGfx.map((g, i) =>
|
||||||
this.throwOneDie(g, finalDice[i], ox + (i ? 34 : -34), oy, i)));
|
this.throwOneDie(g, finalDice[i], ox + (i ? 44 : -44), oy, i)));
|
||||||
}
|
}
|
||||||
|
|
||||||
throwOneDie(g, face, ox, oy, idx) {
|
throwOneDie(g, face, ox, oy, idx) {
|
||||||
|
|
@ -270,7 +270,7 @@ export default class CrapsGame extends Phaser.Scene {
|
||||||
|
|
||||||
const wallX = TABLE.x + TABLE.w - 38 + (Math.random() * 24 - 12); // upper-right corner wall
|
const wallX = TABLE.x + TABLE.w - 38 + (Math.random() * 24 - 12); // upper-right corner wall
|
||||||
const wallY = TABLE.y + 44 + Math.random() * 22;
|
const wallY = TABLE.y + 44 + Math.random() * 22;
|
||||||
const landX = LAND.x + (idx ? 32 : -32) + (Math.random() * 20 - 10);
|
const landX = LAND.x + (idx ? 50 : -50) + (Math.random() * 14 - 7);
|
||||||
const landY = LAND.y + (Math.random() * 24 - 12);
|
const landY = LAND.y + (Math.random() * 24 - 12);
|
||||||
const outMs = 320 + idx * 28;
|
const outMs = 320 + idx * 28;
|
||||||
const backMs = 540 + idx * 44;
|
const backMs = 540 + idx * 44;
|
||||||
|
|
@ -696,16 +696,12 @@ export default class CrapsGame extends Phaser.Scene {
|
||||||
if (res.pointEstablished) this.movePuck(res.pointEstablished);
|
if (res.pointEstablished) this.movePuck(res.pointEstablished);
|
||||||
else if (res.newComeOut) this.movePuck(null);
|
else if (res.newComeOut) this.movePuck(null);
|
||||||
|
|
||||||
// Animate per-player net outcome.
|
// Chip animations fire immediately for all players.
|
||||||
for (let i = 0; i < this.gs.players.length; i++) {
|
for (let i = 0; i < this.gs.players.length; i++) {
|
||||||
const delta = this.gs.players[i].lastDelta;
|
const delta = this.gs.players[i].lastDelta;
|
||||||
if (delta > 0) { this.animateChips(i, true, delta); this.portraits[i]?.playEmotion?.('happy'); }
|
if (delta > 0) this.animateChips(i, true, delta);
|
||||||
else if (delta < 0) { this.animateChips(i, false, -delta); this.portraits[i]?.playEmotion?.('upset'); }
|
else if (delta < 0) this.animateChips(i, false, -delta);
|
||||||
}
|
}
|
||||||
const humanDelta = this.gs.players[0].lastDelta;
|
|
||||||
if (humanDelta > 0) playSound(this, SFX.CASINO_WIN);
|
|
||||||
else if (humanDelta < 0) playSound(this, SFX.CASINO_LOSE);
|
|
||||||
|
|
||||||
this.setStatus(this.outcomeMessage(res, total));
|
this.setStatus(this.outcomeMessage(res, total));
|
||||||
this.renderHumanBets();
|
this.renderHumanBets();
|
||||||
this.renderAiWagers();
|
this.renderAiWagers();
|
||||||
|
|
@ -713,6 +709,9 @@ export default class CrapsGame extends Phaser.Scene {
|
||||||
this.updateBalances();
|
this.updateBalances();
|
||||||
this.persistChips();
|
this.persistChips();
|
||||||
|
|
||||||
|
// Win/lose badges + emotions play sequentially left to right.
|
||||||
|
this.showRollResults();
|
||||||
|
|
||||||
this.time.delayedCall(1400, () => {
|
this.time.delayedCall(1400, () => {
|
||||||
this.animating = false;
|
this.animating = false;
|
||||||
this.diceGfx.forEach((g) => g.setVisible(true)); // leave dice resting on the felt
|
this.diceGfx.forEach((g) => g.setVisible(true)); // leave dice resting on the felt
|
||||||
|
|
@ -752,6 +751,79 @@ export default class CrapsGame extends Phaser.Scene {
|
||||||
return `Rolled ${total}.`;
|
return `Rolled ${total}.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Win / lose result badges (sequential left → right) ────────────────────
|
||||||
|
showRollResults() {
|
||||||
|
const entries = this.gs.players
|
||||||
|
.map((p, i) => ({ i, delta: p.lastDelta }))
|
||||||
|
.filter(e => e.delta !== 0)
|
||||||
|
.sort((a, b) => SEAT_X[a.i] - SEAT_X[b.i]);
|
||||||
|
|
||||||
|
entries.forEach(({ i, delta }, idx) => {
|
||||||
|
this.time.delayedCall(idx * 420, () => {
|
||||||
|
this.portraits[i]?.playEmotion?.(delta > 0 ? 'happy' : 'upset');
|
||||||
|
this.animatePlayerResult(i, delta);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
animatePlayerResult(pi, delta) {
|
||||||
|
const isWin = delta > 0;
|
||||||
|
const x = SEAT_X[pi];
|
||||||
|
const textY = SEAT_Y - PORTRAIT_R - 58;
|
||||||
|
|
||||||
|
const badge = this.add.text(x, textY, isWin ? 'Win!' : 'Lose', {
|
||||||
|
fontFamily: '"Julius Sans One"',
|
||||||
|
fontSize: isWin ? '52px' : '44px',
|
||||||
|
color: isWin ? '#f5d020' : '#e05c5c',
|
||||||
|
fontStyle: 'bold',
|
||||||
|
stroke: '#000000', strokeThickness: 5,
|
||||||
|
shadow: isWin ? { offsetX: 0, offsetY: 0, color: '#f5d020', blur: 24, fill: true } : undefined,
|
||||||
|
}).setOrigin(0.5).setAlpha(0).setScale(1.4).setDepth(D.modal);
|
||||||
|
|
||||||
|
this.tweens.add({ targets: badge, alpha: 1, scaleX: 1, scaleY: 1, duration: 200, ease: 'Back.Out' });
|
||||||
|
|
||||||
|
playSound(this, isWin ? SFX.CASINO_WIN : SFX.CASINO_LOSE);
|
||||||
|
if (isWin) this.animateFireworks(x, textY);
|
||||||
|
|
||||||
|
this.time.delayedCall(isWin ? 1200 : 900, () => {
|
||||||
|
this.tweens.add({
|
||||||
|
targets: badge, alpha: 0, y: textY - 30,
|
||||||
|
duration: 350, ease: 'Power2',
|
||||||
|
onComplete: () => badge.destroy(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
animateFireworks(cx, cy) {
|
||||||
|
const palette = [0xf5d020, 0xff8c00, 0x5cb85c, 0x4a90d9, 0xff69b4, 0xffffff, 0x00e5ff];
|
||||||
|
for (let burst = 0; burst < 3; burst++) {
|
||||||
|
this.time.delayedCall(burst * 380, () => {
|
||||||
|
if (!this.scene.isActive('CrapsGame')) return;
|
||||||
|
const bx = cx + (Math.random() - 0.5) * 140;
|
||||||
|
const by = cy - 10 + (Math.random() - 0.5) * 80;
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
const angle = (i / 10) * Math.PI * 2 + Math.random() * 0.3;
|
||||||
|
const dist = 65 + Math.random() * 65;
|
||||||
|
const color = palette[Math.floor(Math.random() * palette.length)];
|
||||||
|
const dot = this.add.graphics().setDepth(D.modal + 5);
|
||||||
|
dot.fillStyle(color, 1);
|
||||||
|
dot.fillCircle(0, 0, 4 + Math.random() * 3);
|
||||||
|
dot.x = bx; dot.y = by;
|
||||||
|
this.tweens.add({
|
||||||
|
targets: dot,
|
||||||
|
x: bx + Math.cos(angle) * dist,
|
||||||
|
y: by + Math.sin(angle) * dist,
|
||||||
|
alpha: 0, scaleX: 0.1, scaleY: 0.1,
|
||||||
|
duration: 700 + Math.random() * 400,
|
||||||
|
delay: Math.random() * 100,
|
||||||
|
ease: 'Power2',
|
||||||
|
onComplete: () => dot.destroy(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Chip win/loss animation (aggregated per player) ─────────────────────────
|
// ── Chip win/loss animation (aggregated per player) ─────────────────────────
|
||||||
animateChips(playerIndex, toPlayer, amount) {
|
animateChips(playerIndex, toPlayer, amount) {
|
||||||
const seatX = SEAT_X[playerIndex];
|
const seatX = SEAT_X[playerIndex];
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue