enhance uno game animations and emotional feedback

- Add opponent emotion reactions (upset/happy) for specific game events like skips, draws, and wild card plays
- Differentiate animation durations and effects between local player and opponents
- Implement showcase animation for opponent's played cards with a grow/shrink effect
- Adjust card fly and stagger timings for opponent animations to improve visual clarity
This commit is contained in:
Brian Fertig 2026-05-19 20:01:22 -06:00
parent 85dff94613
commit d6588405a4
1 changed files with 33 additions and 9 deletions

View File

@ -1057,6 +1057,7 @@ export default class UnoGame extends Phaser.Scene {
const banner = this.formatChallengeBanner(last);
this.gs = after;
this.renderAll();
this.handleLogEffects(before, after);
if (banner) {
this.showBanner(banner);
this.time.delayedCall(1300, () => this.hideBanner());
@ -1086,14 +1087,23 @@ export default class UnoGame extends Phaser.Scene {
for (let i = oldLen; i < after.log.length; i++) {
const e = after.log[i];
if (e.kind === 'skip') {
this.opponentPortraits[e.seat]?.playEmotion('upset');
this.showBanner(`${this.opponentName(e.seat)} is skipped!`);
this.time.delayedCall(900, () => this.hideBanner());
} else if (e.kind === 'reverse') {
this.showBanner('Direction reversed.');
this.time.delayedCall(900, () => this.hideBanner());
} else if (e.kind === 'draw2') {
this.opponentPortraits[e.seat]?.playEmotion('upset');
this.showBanner(`${this.opponentName(e.seat)} draws 2 and is skipped.`);
this.time.delayedCall(1100, () => this.hideBanner());
} else if (e.kind === 'wild4Accept' || e.kind === 'wild4ChallengeLose' || e.kind === 'wild4ChallengeWin') {
this.opponentPortraits[e.seat]?.playEmotion('upset');
} else if (e.kind === 'play') {
const playedCard = before.players[e.seat]?.hand?.find((c) => c.id === e.cardId);
if (playedCard?.kind === 'wild4') {
this.opponentPortraits[e.seat]?.playEmotion('happy');
}
} else if (e.kind === 'win') {
// handled by endGame
}
@ -1105,6 +1115,7 @@ export default class UnoGame extends Phaser.Scene {
animatePlayCard(seat, card, onComplete) {
const layout = slotLayout(this.slotForSeat[seat], this.gs.players.length);
const handFaceUp = layout.handFaceUp;
const isOpponent = seat !== 0;
const fromSprite = this.cardObjs.get(`hand-${seat}-${card.id}`);
let sprite;
if (fromSprite) {
@ -1124,16 +1135,25 @@ export default class UnoGame extends Phaser.Scene {
targets: sprite,
x: DISCARD_POS.x, y: DISCARD_POS.y,
rotation: 0,
duration: 360,
duration: isOpponent ? 600 : 360,
ease: 'Cubic.easeOut',
onComplete: () => {
if (!handFaceUp) this.renderUnoCardFace(sprite, card, true);
sprite.setDepth(D.card);
// Small bounce.
if (isOpponent) {
// Showcase: grow then shrink over 1.5 s to spotlight the played card.
this.tweens.add({
targets: sprite, scaleX: 1.3, scaleY: 1.3,
duration: 750, ease: 'Sine.easeInOut', yoyo: true,
onComplete: () => onComplete && onComplete(),
});
} else {
// Small bounce for local player.
this.tweens.add({
targets: sprite, scaleX: 1.08, scaleY: 1.08, yoyo: true, duration: 90,
onComplete: () => onComplete && onComplete(),
});
}
},
});
}
@ -1157,7 +1177,7 @@ export default class UnoGame extends Phaser.Scene {
targets: sprite,
x: dest.x, y: dest.y,
rotation: 0,
duration: 400,
duration: seat !== 0 ? 650 : 400,
ease: 'Cubic.easeIn',
onComplete: () => onComplete && onComplete(),
});
@ -1179,6 +1199,9 @@ export default class UnoGame extends Phaser.Scene {
animateBatchDraw(seat, count, onComplete) {
if (count <= 0) { onComplete && onComplete(); return; }
const layout = slotLayout(this.slotForSeat[seat], this.gs.players.length);
const isOpponent = seat !== 0;
const flyDuration = isOpponent ? 480 : 320;
const stagger = isOpponent ? 170 : 110;
let remaining = count;
const fire = (i) => {
const sprite = this.makeUnoCardSprite(null, DRAW_POS.x, DRAW_POS.y, {
@ -1190,7 +1213,7 @@ export default class UnoGame extends Phaser.Scene {
this.tweens.add({
targets: sprite,
x: layout.handCenter.x, y: layout.handCenter.y,
duration: 320,
duration: flyDuration,
ease: 'Cubic.easeIn',
onComplete: () => {
remaining -= 1;
@ -1199,7 +1222,7 @@ export default class UnoGame extends Phaser.Scene {
});
};
for (let i = 0; i < count; i++) {
this.time.delayedCall(i * 110, () => fire(i));
this.time.delayedCall(i * stagger, () => fire(i));
}
}
@ -1236,6 +1259,7 @@ export default class UnoGame extends Phaser.Scene {
}).setOrigin(0.5).setDepth(D.banner + 2).setScale(0.2);
this.transientObjs.push(t);
playSound(this, SFX.CASINO_WIN);
this.opponentPortraits[seat]?.playEmotion('happy');
this.tweens.add({
targets: t, scaleX: 1, scaleY: 1, duration: 280, ease: 'Back.easeOut',
onComplete: () => {