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:
parent
85dff94613
commit
d6588405a4
|
|
@ -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.
|
||||
this.tweens.add({
|
||||
targets: sprite, scaleX: 1.08, scaleY: 1.08, yoyo: true, duration: 90,
|
||||
onComplete: () => onComplete && onComplete(),
|
||||
});
|
||||
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: () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue