feat(blackjack): enhance betting UI with prompts and fix button interactions
- Add animated betting prompts with pulsing chip buttons and text overlay - Implement delayed start for betting animations after 5 seconds - Refactor chip buttons to use containers for proper hit detection - Update Button component to ensure interactive events work on all child objects - Improve betting UI visibility management with proper show/hide states - Add depth management for betting prompts and chip buttons
This commit is contained in:
parent
48f7ade241
commit
1f1897c8fe
|
|
@ -72,6 +72,9 @@ export default class BlackjackGame extends Phaser.Scene {
|
|||
this.statusBadges = {};
|
||||
this.nameTxts = {};
|
||||
this.chipTxts = {};
|
||||
this.bettingPromptGroup = [];
|
||||
this.bettingPromptTimer = null;
|
||||
this.chipPulseTweens = [];
|
||||
|
||||
this.add.rectangle(CX, GAME_HEIGHT / 2, GAME_WIDTH, GAME_HEIGHT, COLORS.bg).setDepth(D.bg);
|
||||
this.buildPlayfield();
|
||||
|
|
@ -199,15 +202,17 @@ export default class BlackjackGame extends Phaser.Scene {
|
|||
// Chip buttons
|
||||
CHIP_AMOUNTS.forEach((amt, i) => {
|
||||
const bx = cx - 120 + i * 80;
|
||||
const g = this.add.graphics().setDepth(D.ui + 1);
|
||||
g.fillStyle(CHIP_COLORS[amt], 1);
|
||||
g.fillCircle(bx, y, 28);
|
||||
const container = this.add.container(bx, y).setDepth(D.ui + 1);
|
||||
const g = this.add.graphics();
|
||||
g.lineStyle(3, 0xffffff, 0.4);
|
||||
g.strokeCircle(bx, y, 28);
|
||||
g.setInteractive(new Phaser.Geom.Circle(bx, y, 28), Phaser.Geom.Circle.Contains);
|
||||
g.on('pointerdown', () => this.onChipClick(amt));
|
||||
g.on('pointerover', () => g.setAlpha(0.8));
|
||||
g.on('pointerout', () => g.setAlpha(1));
|
||||
g.strokeCircle(0, 0, 28);
|
||||
g.fillStyle(CHIP_COLORS[amt], 1);
|
||||
g.fillCircle(0, 0, 28);
|
||||
container.add(g);
|
||||
container.setInteractive(new Phaser.Geom.Circle(0, 0, 28), Phaser.Geom.Circle.Contains);
|
||||
container.on('pointerdown', () => this.onChipClick(amt));
|
||||
container.on('pointerover', () => container.setAlpha(0.8));
|
||||
container.on('pointerout', () => container.setAlpha(1));
|
||||
|
||||
const t = this.add.text(bx, y, `$${amt}`, {
|
||||
fontFamily: 'system-ui, sans-serif', fontSize: '13px',
|
||||
|
|
@ -238,8 +243,136 @@ export default class BlackjackGame extends Phaser.Scene {
|
|||
this.hideBettingUI();
|
||||
}
|
||||
|
||||
showBettingUI() { for (const o of this.bettingUIGroup) o.setVisible?.(true) || (o.visible = true); }
|
||||
hideBettingUI() { for (const o of this.bettingUIGroup) o.setVisible?.(false) || (o.visible = false); }
|
||||
showBettingUI() {
|
||||
for (const o of this.bettingUIGroup) o.setVisible?.(true) || (o.visible = true);
|
||||
this.chipBtnGraphics.forEach(g => g.setDepth(D.ui + 1));
|
||||
this.startBettingPrompts();
|
||||
}
|
||||
hideBettingUI() {
|
||||
for (const o of this.bettingUIGroup) o.setVisible?.(false) || (o.visible = false);
|
||||
this.hideBettingPrompts();
|
||||
}
|
||||
|
||||
startBettingPrompts() {
|
||||
this.hideBettingPrompts();
|
||||
|
||||
// Wait 5 seconds before starting any animations
|
||||
this.bettingPromptTimer = this.time.delayedCall(5000, () => {
|
||||
if (!this.scene.isActive('BlackjackGame')) return;
|
||||
if (this.pendingBet > 0) return; // Don't start if already betting
|
||||
|
||||
// Start subtle radius pulsing animation
|
||||
this.chipBtnGraphics.forEach((chip, index) => {
|
||||
const delay = index * 200;
|
||||
const tween = this.tweens.add({
|
||||
targets: chip,
|
||||
scaleX: 1.25,
|
||||
scaleY: 1.25,
|
||||
duration: 1200,
|
||||
ease: 'Sine.easeInOut',
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
delay: delay,
|
||||
});
|
||||
this.chipPulseTweens.push(tween);
|
||||
});
|
||||
|
||||
// Show prompt text
|
||||
this.showBettingPrompt();
|
||||
});
|
||||
}
|
||||
|
||||
showBettingPrompt() {
|
||||
if (this.pendingBet > 0) return; // Don't show if already betting
|
||||
|
||||
const y = GAME_HEIGHT - 160;
|
||||
const cx = CX;
|
||||
|
||||
// Create semi-transparent background
|
||||
const bg = this.add.rectangle(cx + 100, y, 420, 60, 0x000000, 0.7)
|
||||
.setOrigin(0.5, 0.5)
|
||||
.setDepth(D.ui + 10)
|
||||
.setAlpha(0);
|
||||
|
||||
// Create prompt text
|
||||
const text = this.add.text(cx + 100, y, 'Choose an amount to bet and click Deal to begin', {
|
||||
fontFamily: 'system-ui, sans-serif',
|
||||
fontSize: '18px',
|
||||
color: '#ffffff',
|
||||
align: 'center',
|
||||
})
|
||||
.setOrigin(0.5, 0.5)
|
||||
.setDepth(D.ui + 11)
|
||||
.setAlpha(0);
|
||||
|
||||
this.bettingPromptGroup = [bg, text];
|
||||
|
||||
// Fade in
|
||||
this.tweens.add({
|
||||
targets: bg,
|
||||
alpha: 0.7,
|
||||
duration: 300,
|
||||
ease: 'Power2',
|
||||
});
|
||||
|
||||
this.tweens.add({
|
||||
targets: text,
|
||||
alpha: 1,
|
||||
duration: 300,
|
||||
ease: 'Power2',
|
||||
delay: 100,
|
||||
});
|
||||
}
|
||||
|
||||
hideBettingPrompts() {
|
||||
// Fade out prompt if visible
|
||||
if (this.bettingPromptGroup && this.bettingPromptGroup.length > 0) {
|
||||
this.tweens.add({
|
||||
targets: this.bettingPromptGroup,
|
||||
alpha: 0,
|
||||
duration: 200,
|
||||
ease: 'Power2',
|
||||
onComplete: () => {
|
||||
this.bettingPromptGroup.forEach(obj => obj.destroy());
|
||||
this.bettingPromptGroup = [];
|
||||
}
|
||||
});
|
||||
} else if (this.bettingPromptGroup.length > 0) {
|
||||
this.bettingPromptGroup.forEach(obj => obj.destroy());
|
||||
this.bettingPromptGroup = [];
|
||||
}
|
||||
|
||||
// Stop chip pulsing
|
||||
this.chipPulseTweens.forEach(tween => tween.destroy());
|
||||
this.chipPulseTweens = [];
|
||||
this.chipBtnGraphics.forEach(chip => {
|
||||
chip.setScale(1, 1);
|
||||
chip.setAlpha(1);
|
||||
});
|
||||
|
||||
// Clear timer
|
||||
if (this.bettingPromptTimer) {
|
||||
this.bettingPromptTimer.remove();
|
||||
this.bettingPromptTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
startBettingPromptsIfNoBet() {
|
||||
// Only start prompts if no bet has been placed yet
|
||||
if (this.pendingBet === 0) {
|
||||
this.startBettingPrompts();
|
||||
}
|
||||
}
|
||||
|
||||
hideBettingUI() {
|
||||
for (const o of this.bettingUIGroup) {
|
||||
o.setVisible?.(false) || (o.visible = false);
|
||||
}
|
||||
this.hideBettingPrompts();
|
||||
// Also hide chip containers from the betting UI
|
||||
this.chipBtnGraphics.forEach(g => g.setVisible?.(false) || (g.visible = false));
|
||||
this.chipBtnGraphics.forEach(g => g.setDepth(-100));
|
||||
}
|
||||
|
||||
// ── Chip balance ──────────────────────────────────────────────────────────
|
||||
async loadPlayerChips() {
|
||||
|
|
@ -474,11 +607,13 @@ export default class BlackjackGame extends Phaser.Scene {
|
|||
if (this.pendingBet + amount > human.chips) return;
|
||||
this.pendingBet += amount;
|
||||
this.updateBetDisplay();
|
||||
this.hideBettingPrompts();
|
||||
}
|
||||
|
||||
onClearBet() {
|
||||
this.pendingBet = 0;
|
||||
this.updateBetDisplay();
|
||||
this.startBettingPromptsIfNoBet();
|
||||
}
|
||||
|
||||
updateBetDisplay() {
|
||||
|
|
@ -492,6 +627,7 @@ export default class BlackjackGame extends Phaser.Scene {
|
|||
if (this.animating || this.pendingBet < 5) return;
|
||||
this.animating = true;
|
||||
this.hideBettingUI();
|
||||
this.hideBettingPrompts();
|
||||
|
||||
// Commit human bet
|
||||
this.gs = applyBet(this.gs, 0, this.pendingBet);
|
||||
|
|
@ -900,6 +1036,7 @@ export default class BlackjackGame extends Phaser.Scene {
|
|||
if (canSplit(p) && p.chips >= p.bet) {
|
||||
btns.push(new Button(this, CX + 220, by, 'Split', () => this.onSplit(), { width: 120, height: 52, fontSize: 20 }));
|
||||
}
|
||||
btns.forEach(btn => btn.setDepth(D.ui + 5));
|
||||
this.actionBtns = btns;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,9 @@ export class Button extends Phaser.GameObjects.Container {
|
|||
this.add([this.bgRect, this.text]);
|
||||
|
||||
this.setSize(width, height);
|
||||
this.setInteractive({ useHandCursor: true });
|
||||
this.setInteractive({ useHandCursor: true, hitArea: new Phaser.Geom.Rectangle(0, 0, width, height), hitAreaCallback: Phaser.Geom.Rectangle.Contains });
|
||||
this.bgRect.setInteractive({ hitArea: new Phaser.Geom.Rectangle(0, 0, width, height), hitAreaCallback: Phaser.Geom.Rectangle.Contains });
|
||||
this.text.setInteractive({ hitArea: new Phaser.Geom.Rectangle(0, 0, width, height), hitAreaCallback: Phaser.Geom.Rectangle.Contains });
|
||||
this.on('pointerover', () => this.bgRect.setFillStyle(bgHover, 1));
|
||||
this.on('pointerout', () => this.bgRect.setFillStyle(bg, variant === 'ghost' ? 0 : 1));
|
||||
this.on('pointerdown', () => this.bgRect.setScale(0.97));
|
||||
|
|
@ -36,6 +38,20 @@ export class Button extends Phaser.GameObjects.Container {
|
|||
this.on('pointerupoutside', () => this.bgRect.setScale(1));
|
||||
if (onClick) this.on('pointerup', onClick);
|
||||
|
||||
this.bgRect.on('pointerover', () => this.bgRect.setFillStyle(bgHover, 1));
|
||||
this.bgRect.on('pointerout', () => this.bgRect.setFillStyle(bg, variant === 'ghost' ? 0 : 1));
|
||||
this.bgRect.on('pointerdown', () => this.bgRect.setScale(0.97));
|
||||
this.bgRect.on('pointerup', () => this.bgRect.setScale(1));
|
||||
this.bgRect.on('pointerupoutside', () => this.bgRect.setScale(1));
|
||||
if (onClick) this.bgRect.on('pointerup', onClick);
|
||||
|
||||
this.text.on('pointerover', () => this.bgRect.setFillStyle(bgHover, 1));
|
||||
this.text.on('pointerout', () => this.bgRect.setFillStyle(bg, variant === 'ghost' ? 0 : 1));
|
||||
this.text.on('pointerdown', () => this.bgRect.setScale(0.97));
|
||||
this.text.on('pointerup', () => this.bgRect.setScale(1));
|
||||
this.text.on('pointerupoutside', () => this.bgRect.setScale(1));
|
||||
if (onClick) this.text.on('pointerup', onClick);
|
||||
|
||||
scene.add.existing(this);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue