feat(blokus): animate AI piece placement from portrait to board
Introduce a visual transition where AI pieces fly from their opponent portrait to the board upon placement. Human player placements remain instant with a flash effect. Implemented animateAIPiece to handle the tweening and temporary graphics rendering.
This commit is contained in:
parent
5c88ab7986
commit
2f7b2e183e
|
|
@ -392,7 +392,7 @@ export default class BlokusGame extends Phaser.Scene {
|
|||
if (this.gameOver) return;
|
||||
const mv = chooseMove(this.gs, seat, this.skillForSeat(seat));
|
||||
if (!mv) { this.gs = passTurn(this.gs, seat); this.animating = false; this.driveTurn(); return; }
|
||||
this.placePiece(seat, mv, () => { this.animating = false; this.driveTurn(); });
|
||||
this.placePiece(seat, mv, () => { this.animating = false; this.driveTurn(); }, true);
|
||||
}
|
||||
|
||||
humanPass() {
|
||||
|
|
@ -402,22 +402,79 @@ export default class BlokusGame extends Phaser.Scene {
|
|||
this.driveTurn();
|
||||
}
|
||||
|
||||
placePiece(seat, move, done) {
|
||||
placePiece(seat, move, done, animateFromPortrait = false) {
|
||||
const next = applyPlacement(this.gs, seat, move.pieceId, move.oriIdx, move.anchorR, move.anchorC);
|
||||
if (next === this.gs) { done(); return; } // illegal — should not happen
|
||||
this.gs = next;
|
||||
playSound(this, SFX.CARD_PLACE);
|
||||
this.renderPieces();
|
||||
this.renderTray();
|
||||
this.renderScores();
|
||||
|
||||
// Brief pop on the freshly placed cells.
|
||||
if (animateFromPortrait && seat > 0) {
|
||||
this.animateAIPiece(seat, move, done);
|
||||
} else {
|
||||
this.renderPieces();
|
||||
this.renderTray();
|
||||
this.renderScores();
|
||||
// Brief pop on the freshly placed cells.
|
||||
const cells = placementCells(move.pieceId, move.oriIdx, move.anchorR, move.anchorC);
|
||||
const flash = this.add.graphics().setDepth(DEPTH.ghost);
|
||||
for (const [r, c] of cells) this.fillCell(flash, r, c, 0xffffff, 0.5);
|
||||
this.tweens.add({ targets: flash, alpha: 0, duration: 280, ease: 'Quad.easeOut', onComplete: () => flash.destroy() });
|
||||
this.time.delayedCall(180, done);
|
||||
}
|
||||
}
|
||||
|
||||
animateAIPiece(seat, move, done) {
|
||||
const cells = placementCells(move.pieceId, move.oriIdx, move.anchorR, move.anchorC);
|
||||
const flash = this.add.graphics().setDepth(DEPTH.ghost);
|
||||
for (const [r, c] of cells) this.fillCell(flash, r, c, 0xffffff, 0.5);
|
||||
this.tweens.add({ targets: flash, alpha: 0, duration: 280, ease: 'Quad.easeOut', onComplete: () => flash.destroy() });
|
||||
const col = COLOR_PALETTE[seat];
|
||||
|
||||
this.time.delayedCall(180, done);
|
||||
// Calculate board center of the piece
|
||||
let avgR = 0, avgC = 0;
|
||||
for (const [r, c] of cells) { avgR += r; avgC += c; }
|
||||
avgR /= cells.length;
|
||||
avgC /= cells.length;
|
||||
const destX = GRID_X + avgC * CELL;
|
||||
const destY = GRID_Y + avgR * CELL;
|
||||
|
||||
// Portrait position for this opponent
|
||||
const panelH = 192;
|
||||
const portraitYOffset = -panelH / 2 + 60 + 14;
|
||||
const panelY = 221 + (seat - 1) * (panelH + 10);
|
||||
const startX = SIDE_X;
|
||||
const startY = panelY + portraitYOffset + 200;
|
||||
|
||||
// Create flying piece graphics — draw centered at (0,0) so we can tween position
|
||||
const flyGfx = this.add.graphics().setDepth(DEPTH.piece + 1);
|
||||
flyGfx.fillStyle(col.hex, 1);
|
||||
flyGfx.lineStyle(2, col.dark, 1);
|
||||
for (const [r, c] of cells) {
|
||||
const cx = (c - avgC) * CELL;
|
||||
const cy = (r - avgR) * CELL;
|
||||
flyGfx.fillRoundedRect(cx - CELL / 2 + 2, cy - CELL / 2 + 2, CELL - 4, CELL - 4, 6);
|
||||
flyGfx.strokeRoundedRect(cx - CELL / 2 + 2, cy - CELL / 2 + 2, CELL - 4, CELL - 4, 6);
|
||||
}
|
||||
flyGfx.x = startX;
|
||||
flyGfx.y = startY;
|
||||
|
||||
// Animate from portrait to board
|
||||
const duration = 400;
|
||||
this.tweens.add({
|
||||
targets: flyGfx,
|
||||
x: destX,
|
||||
y: destY,
|
||||
duration,
|
||||
ease: 'Quad.easeInOut',
|
||||
onComplete: () => {
|
||||
flyGfx.destroy();
|
||||
this.renderPieces();
|
||||
this.renderTray();
|
||||
this.renderScores();
|
||||
// Brief pop on the freshly placed cells.
|
||||
const flash = this.add.graphics().setDepth(DEPTH.ghost);
|
||||
for (const [r, c] of cells) this.fillCell(flash, r, c, 0xffffff, 0.5);
|
||||
this.tweens.add({ targets: flash, alpha: 0, duration: 280, ease: 'Quad.easeOut', onComplete: () => flash.destroy() });
|
||||
this.time.delayedCall(180, done);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
updateButtons() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue