refactor: fix card visibility and animation sequence in GoFish
- Pass live sprites from `animateAsk` to `animatePairedCards` to prevent premature destruction. - Update `animateAsk` to fan collected cards into the asker's show zone instead of hiding them. - Update `animatePairedCards` to accept pre-existing sprites for seamless pair animation. - Remove `canAsk` check in `applyAsk` to ensure turn advances correctly after any ask.
This commit is contained in:
parent
b63bf1b651
commit
975ee6ea25
|
|
@ -651,7 +651,7 @@ export default class GoFishGame extends Phaser.Scene {
|
|||
if (after === before) { this.animating = false; return; }
|
||||
const last = after.lastAsk;
|
||||
|
||||
this.animateAsk(askerSeat, targetSeat, rank, last, before, () => {
|
||||
this.animateAsk(askerSeat, targetSeat, rank, last, before, (liveSprites) => {
|
||||
// Fish with pool cards remaining — player must pick interactively.
|
||||
if (after.phase === 'pick') {
|
||||
this.gs = after;
|
||||
|
|
@ -678,16 +678,17 @@ export default class GoFishGame extends Phaser.Scene {
|
|||
}
|
||||
|
||||
if (last.newPairs > 0 && last.pairedCards?.length >= 2) {
|
||||
this.time.delayedCall(600, () => {
|
||||
this.hideBanner();
|
||||
// Cards from animateAsk are already visible in position.
|
||||
// Do NOT call renderAll() here — it would destroy the liveSprites.
|
||||
// renderAll() is deferred to after the pair animation completes.
|
||||
this.hideBanner();
|
||||
this.animatePairedCards(askerSeat, last.pairedCards, () => {
|
||||
this.renderAll();
|
||||
this.updateStatus();
|
||||
this.animatePairedCards(askerSeat, last.pairedCards, () => {
|
||||
this.animating = false;
|
||||
if (isGameOver(this.gs)) { this.endGame(); return; }
|
||||
this.maybeStartAITurn();
|
||||
});
|
||||
});
|
||||
this.animating = false;
|
||||
if (isGameOver(this.gs)) { this.endGame(); return; }
|
||||
this.maybeStartAITurn();
|
||||
}, liveSprites ?? null);
|
||||
} else {
|
||||
this.time.delayedCall(900, () => {
|
||||
this.renderAll();
|
||||
|
|
@ -754,24 +755,31 @@ export default class GoFishGame extends Phaser.Scene {
|
|||
return { sprite, tx, ty };
|
||||
});
|
||||
|
||||
// After target cards are shown, fan all sprites into a holding position
|
||||
// at the asker's show zone — they stay visible for the pair animation.
|
||||
const waitMs = 250 + (n - 1) * 80 + 380;
|
||||
this.time.delayedCall(waitMs, () => {
|
||||
if (askerSprite.active) {
|
||||
this.tweens.add({
|
||||
targets: askerSprite, alpha: 0, duration: 200,
|
||||
onComplete: () => { if (askerSprite.active) askerSprite.destroy(); },
|
||||
});
|
||||
}
|
||||
for (const { sprite } of targetSprites) {
|
||||
const FGAP = 8;
|
||||
const nTotal = 1 + targetSprites.length;
|
||||
const isAskerVert = askerSlot === 'left' || askerSlot === 'right';
|
||||
const allSprites = [
|
||||
askerSprite,
|
||||
...targetSprites.map(({ sprite }) => sprite),
|
||||
].map((sprite, i) => {
|
||||
const x = isAskerVert
|
||||
? askerShow.x
|
||||
: askerShow.x + (i - (nTotal - 1) / 2) * (CARD_W + FGAP);
|
||||
const y = isAskerVert
|
||||
? askerShow.y + (i - (nTotal - 1) / 2) * (CARD_H + FGAP)
|
||||
: askerShow.y;
|
||||
return { sprite, x, y };
|
||||
});
|
||||
|
||||
for (const { sprite, x, y } of allSprites) {
|
||||
if (!sprite.active) continue;
|
||||
this.tweens.add({
|
||||
targets: sprite,
|
||||
x: askerLayout.handCenter.x, y: askerLayout.handCenter.y,
|
||||
alpha: 0, duration: 380, ease: 'Cubic.easeIn',
|
||||
onComplete: () => { if (sprite.active) sprite.destroy(); },
|
||||
});
|
||||
this.tweens.add({ targets: sprite, x, y, duration: 280, ease: 'Cubic.easeOut' });
|
||||
}
|
||||
this.time.delayedCall(420, onComplete);
|
||||
this.time.delayedCall(320, () => onComplete(allSprites));
|
||||
});
|
||||
} else {
|
||||
// Fish — flip card back face-down and return it to hand.
|
||||
|
|
@ -822,7 +830,30 @@ export default class GoFishGame extends Phaser.Scene {
|
|||
});
|
||||
}
|
||||
|
||||
animatePairedCards(askerSeat, pairedCards, onComplete) {
|
||||
animatePairedCards(askerSeat, pairedCards, onComplete, preSprites = null) {
|
||||
if (preSprites) {
|
||||
// Cards are already face-up and in position from animateAsk — skip sprite creation.
|
||||
const sprites = preSprites.map(({ sprite, x, y }) => ({ sprite, targetX: x, targetY: y }));
|
||||
playSound(this, SFX.CARD_PLACE);
|
||||
this.time.delayedCall(200, () => {
|
||||
const cx = sprites.reduce((s, { targetX }) => s + targetX, 0) / sprites.length;
|
||||
const cy = sprites.reduce((s, { targetY }) => s + targetY, 0) / sprites.length;
|
||||
this.spawnFireworks(cx, cy);
|
||||
this.time.delayedCall(350, () => this.spawnFireworks(cx, cy));
|
||||
});
|
||||
this.time.delayedCall(1200, () => {
|
||||
for (const { sprite } of sprites) {
|
||||
if (!sprite.active) continue;
|
||||
this.tweens.add({
|
||||
targets: sprite, x: CX, y: CY, alpha: 0, duration: 500, ease: 'Cubic.easeIn',
|
||||
onComplete: () => { if (sprite.active) sprite.destroy(); },
|
||||
});
|
||||
}
|
||||
});
|
||||
this.time.delayedCall(1800, onComplete);
|
||||
return;
|
||||
}
|
||||
|
||||
const slot = this.slotForSeat[askerSeat];
|
||||
const layout = slotLayout(slot);
|
||||
const n = pairedCards.length;
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ export function applyAsk(state, targetSeat, rank) {
|
|||
pairedCards: [],
|
||||
};
|
||||
ensureHasCards(next, askerSeat);
|
||||
if (!canAsk(next, askerSeat)) advanceTurn(next);
|
||||
advanceTurn(next);
|
||||
checkGameOver(next);
|
||||
return next;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue