refactor(onitama): extract tweens into factory functions
Centralize Onitama animation logic by moving inline Phaser tweens into a dedicated `tweens.ts` configuration file. This improves reusability and cleans up spawners and renderers. Also updated documentation to warn against adding interruptions to looped tweens to prevent game logic hangs.
This commit is contained in:
parent
3568d99e6e
commit
c25759d147
|
|
@ -59,6 +59,8 @@ Extend `Phaser.GameObjects.Container` to encapsulate visuals and state.
|
|||
### 5. Tween Interruption
|
||||
Always register state-related tweens: `this.scene.addTweenInterruption(tween)`.
|
||||
Prevents visual glitches when game state changes mid-animation.
|
||||
DO NOT add interruptions for looped tweens.
|
||||
Otherwise the game logic will hang.
|
||||
|
||||
### 6. Scene Navigation
|
||||
Use `await this.sceneController.launch('SceneKey')`.
|
||||
|
|
|
|||
|
|
@ -219,3 +219,33 @@ export function getCardPosition(
|
|||
export function colorToStr(hex: number) {
|
||||
return Display.Color.ValueToColor(hex).toString();
|
||||
}
|
||||
|
||||
// Tween factory functions
|
||||
export {
|
||||
// Card tweens
|
||||
createCardMoveTween,
|
||||
createCardRotateTween,
|
||||
createCardSpawnTween,
|
||||
createCardDespawnTween,
|
||||
// Pawn tweens
|
||||
createPawnMoveTween,
|
||||
createPawnSpawnTween,
|
||||
createPawnDespawnTween,
|
||||
// Highlight tweens
|
||||
createHighlightSpawnTween,
|
||||
createHighlightDespawnTween,
|
||||
createHighlightClickFeedbackTween,
|
||||
createHighlightInnerPulseTween,
|
||||
createHighlightOuterPulseTween,
|
||||
// Selection tweens
|
||||
createSelectionFadeInTween,
|
||||
createSelectionPulseTween,
|
||||
createSelectionFadeOutTween,
|
||||
createSelectionShowTween,
|
||||
createSelectionRingPulseTween,
|
||||
createSelectionHideTween,
|
||||
// Overlay tweens
|
||||
createWinnerPulseTween,
|
||||
// Types
|
||||
type TweenScene,
|
||||
} from "@/config/tweens";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,322 @@
|
|||
import type Phaser from "phaser";
|
||||
|
||||
import { ANIMATIONS } from "@/config";
|
||||
|
||||
export type TweenScene = Phaser.Scene & {
|
||||
addTweenInterruption(tween: Phaser.Tweens.Tween): void;
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Card Tweens
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
export function createCardMoveTween(
|
||||
scene: TweenScene,
|
||||
target: Phaser.GameObjects.GameObject,
|
||||
x: number,
|
||||
y: number,
|
||||
): Phaser.Tweens.Tween {
|
||||
const tween = scene.tweens.add({
|
||||
targets: target,
|
||||
x,
|
||||
y,
|
||||
duration: ANIMATIONS.cardMove,
|
||||
ease: "Back.easeOut",
|
||||
});
|
||||
scene.addTweenInterruption(tween);
|
||||
return tween;
|
||||
}
|
||||
|
||||
export function createCardRotateTween(
|
||||
scene: TweenScene,
|
||||
target: Phaser.GameObjects.GameObject,
|
||||
fromAngle: number,
|
||||
toAngle: number,
|
||||
): Phaser.Tweens.Tween {
|
||||
const tween = scene.tweens.add({
|
||||
targets: target,
|
||||
angle: { from: fromAngle, to: toAngle },
|
||||
duration: ANIMATIONS.cardRotate,
|
||||
ease: "Back.easeOut",
|
||||
});
|
||||
scene.addTweenInterruption(tween);
|
||||
return tween;
|
||||
}
|
||||
|
||||
export function createCardSpawnTween(
|
||||
scene: TweenScene,
|
||||
target: Phaser.GameObjects.GameObject,
|
||||
): Phaser.Tweens.Tween {
|
||||
const tween = scene.tweens.add({
|
||||
targets: target,
|
||||
alpha: 1,
|
||||
duration: ANIMATIONS.cardSpawn,
|
||||
ease: "Power2",
|
||||
});
|
||||
scene.addTweenInterruption(tween);
|
||||
return tween;
|
||||
}
|
||||
|
||||
export function createCardDespawnTween(
|
||||
scene: TweenScene,
|
||||
target: Phaser.GameObjects.GameObject,
|
||||
onComplete: () => void,
|
||||
): Phaser.Tweens.Tween {
|
||||
const tween = scene.tweens.add({
|
||||
targets: target,
|
||||
alpha: 0,
|
||||
scale: 0.8,
|
||||
duration: ANIMATIONS.cardDespawn,
|
||||
ease: "Power2",
|
||||
onComplete,
|
||||
});
|
||||
scene.addTweenInterruption(tween);
|
||||
return tween;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Pawn Tweens
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
export function createPawnMoveTween(
|
||||
scene: TweenScene,
|
||||
target: Phaser.GameObjects.GameObject,
|
||||
x: number,
|
||||
y: number,
|
||||
): Phaser.Tweens.Tween {
|
||||
const tween = scene.tweens.add({
|
||||
targets: target,
|
||||
x,
|
||||
y,
|
||||
duration: ANIMATIONS.pawnMove,
|
||||
ease: "Back.easeOut",
|
||||
});
|
||||
scene.addTweenInterruption(tween);
|
||||
return tween;
|
||||
}
|
||||
|
||||
export function createPawnSpawnTween(
|
||||
scene: TweenScene,
|
||||
target: Phaser.GameObjects.GameObject,
|
||||
): Phaser.Tweens.Tween {
|
||||
const tween = scene.tweens.add({
|
||||
targets: target,
|
||||
scale: 1,
|
||||
duration: ANIMATIONS.pawnSpawn,
|
||||
ease: "Back.easeOut",
|
||||
});
|
||||
scene.addTweenInterruption(tween);
|
||||
return tween;
|
||||
}
|
||||
|
||||
export function createPawnDespawnTween(
|
||||
scene: TweenScene,
|
||||
target: Phaser.GameObjects.GameObject,
|
||||
onComplete: () => void,
|
||||
): Phaser.Tweens.Tween {
|
||||
const tween = scene.tweens.add({
|
||||
targets: target,
|
||||
scale: 0,
|
||||
alpha: 0,
|
||||
duration: ANIMATIONS.pawnDespawn,
|
||||
ease: "Back.easeIn",
|
||||
onComplete,
|
||||
});
|
||||
scene.addTweenInterruption(tween);
|
||||
return tween;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Highlight Tweens
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
export function createHighlightSpawnTween(
|
||||
scene: TweenScene,
|
||||
target: Phaser.GameObjects.GameObject,
|
||||
): Phaser.Tweens.Tween {
|
||||
const tween = scene.tweens.add({
|
||||
targets: target,
|
||||
alpha: 1,
|
||||
scale: 1,
|
||||
duration: ANIMATIONS.highlightSpawn,
|
||||
ease: "Back.easeOut",
|
||||
});
|
||||
scene.addTweenInterruption(tween);
|
||||
return tween;
|
||||
}
|
||||
|
||||
export function createHighlightDespawnTween(
|
||||
scene: TweenScene,
|
||||
target: Phaser.GameObjects.GameObject,
|
||||
onComplete: () => void,
|
||||
): Phaser.Tweens.Tween {
|
||||
const tween = scene.tweens.add({
|
||||
targets: target,
|
||||
scale: 0,
|
||||
alpha: 0,
|
||||
duration: ANIMATIONS.highlightDespawn,
|
||||
ease: "Back.easeIn",
|
||||
onComplete,
|
||||
});
|
||||
scene.addTweenInterruption(tween);
|
||||
return tween;
|
||||
}
|
||||
|
||||
export function createHighlightClickFeedbackTween(
|
||||
scene: TweenScene,
|
||||
target: Phaser.GameObjects.GameObject,
|
||||
): Phaser.Tweens.Tween {
|
||||
return scene.tweens.add({
|
||||
targets: target,
|
||||
scale: 1.5,
|
||||
alpha: 0.8,
|
||||
duration: ANIMATIONS.clickFeedback,
|
||||
ease: "Power2",
|
||||
yoyo: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function createHighlightInnerPulseTween(
|
||||
scene: TweenScene,
|
||||
targets: Phaser.GameObjects.GameObject[],
|
||||
): Phaser.Tweens.Tween {
|
||||
return scene.tweens.add({
|
||||
targets,
|
||||
scale: 1.2,
|
||||
alpha: 0.6,
|
||||
duration: 600,
|
||||
ease: "Sine.easeInOut",
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
});
|
||||
}
|
||||
|
||||
export function createHighlightOuterPulseTween(
|
||||
scene: TweenScene,
|
||||
target: Phaser.GameObjects.GameObject,
|
||||
): Phaser.Tweens.Tween {
|
||||
return scene.tweens.add({
|
||||
targets: target,
|
||||
scale: 1.3,
|
||||
alpha: 0.3,
|
||||
duration: 800,
|
||||
ease: "Sine.easeInOut",
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
delay: 200,
|
||||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Selection Tweens
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
export function createSelectionFadeInTween(
|
||||
scene: TweenScene,
|
||||
target: Phaser.GameObjects.GameObject,
|
||||
onComplete: () => void,
|
||||
): Phaser.Tweens.Tween {
|
||||
return scene.tweens.add({
|
||||
targets: target,
|
||||
alpha: 1,
|
||||
scale: 1.05,
|
||||
duration: ANIMATIONS.selectionFadeIn,
|
||||
ease: "Power2",
|
||||
onComplete,
|
||||
});
|
||||
}
|
||||
|
||||
export function createSelectionPulseTween(
|
||||
scene: TweenScene,
|
||||
target: Phaser.GameObjects.GameObject,
|
||||
lineWidth: number,
|
||||
): Phaser.Tweens.Tween {
|
||||
return scene.tweens.add({
|
||||
targets: target,
|
||||
alpha: 0.7,
|
||||
lineWidth: lineWidth + 1,
|
||||
duration: ANIMATIONS.selectionPulse,
|
||||
ease: "Sine.easeInOut",
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
});
|
||||
}
|
||||
|
||||
export function createSelectionFadeOutTween(
|
||||
scene: TweenScene,
|
||||
target: Phaser.GameObjects.GameObject,
|
||||
onComplete: () => void,
|
||||
): Phaser.Tweens.Tween {
|
||||
return scene.tweens.add({
|
||||
targets: target,
|
||||
alpha: 0,
|
||||
scale: 0.95,
|
||||
duration: ANIMATIONS.selectionFadeOut,
|
||||
ease: "Power2",
|
||||
onComplete,
|
||||
});
|
||||
}
|
||||
|
||||
export function createSelectionShowTween(
|
||||
scene: TweenScene,
|
||||
target: Phaser.GameObjects.GameObject,
|
||||
onCompleteFadeIn: () => void,
|
||||
): Phaser.Tweens.Tween {
|
||||
return scene.tweens.add({
|
||||
targets: target,
|
||||
alpha: 0.8,
|
||||
duration: ANIMATIONS.selectionFadeIn,
|
||||
ease: "Power2",
|
||||
onComplete: onCompleteFadeIn,
|
||||
});
|
||||
}
|
||||
|
||||
export function createSelectionRingPulseTween(
|
||||
scene: TweenScene,
|
||||
target: Phaser.GameObjects.GameObject,
|
||||
): Phaser.Tweens.Tween {
|
||||
return scene.tweens.add({
|
||||
targets: target,
|
||||
scale: 1.15,
|
||||
alpha: 0.6,
|
||||
duration: ANIMATIONS.selectionPulse,
|
||||
ease: "Sine.easeInOut",
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
});
|
||||
}
|
||||
|
||||
export function createSelectionHideTween(
|
||||
scene: TweenScene,
|
||||
target: Phaser.GameObjects.GameObject,
|
||||
onComplete: () => void,
|
||||
): Phaser.Tweens.Tween {
|
||||
return scene.tweens.add({
|
||||
targets: target,
|
||||
alpha: 0,
|
||||
scale: 0.9,
|
||||
duration: ANIMATIONS.selectionFadeOut,
|
||||
ease: "Power2",
|
||||
onComplete: () => {
|
||||
target.destroy();
|
||||
onComplete();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Overlay Tweens
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
export function createWinnerPulseTween(
|
||||
scene: TweenScene,
|
||||
target: Phaser.GameObjects.GameObject,
|
||||
): Phaser.Tweens.Tween {
|
||||
return scene.tweens.add({
|
||||
targets: target,
|
||||
scale: 1.2,
|
||||
duration: ANIMATIONS.winnerPulse,
|
||||
yoyo: true,
|
||||
repeat: 1,
|
||||
});
|
||||
}
|
||||
|
|
@ -1,6 +1,11 @@
|
|||
import type { OnitamaScene } from "@/scenes/OnitamaScene";
|
||||
|
||||
import { CELL_SIZE, COLORS } from "@/config";
|
||||
import {
|
||||
CELL_SIZE,
|
||||
COLORS,
|
||||
createHighlightInnerPulseTween,
|
||||
createHighlightOuterPulseTween,
|
||||
} from "@/config";
|
||||
|
||||
export interface HighlightRenderOptions {
|
||||
x: number;
|
||||
|
|
@ -78,26 +83,9 @@ export class HighlightRenderer {
|
|||
if (!outerCircle || !innerCircle) return;
|
||||
|
||||
// Inner circle pulse
|
||||
this.scene.tweens.add({
|
||||
targets: [outerCircle, innerCircle],
|
||||
scale: 1.2,
|
||||
alpha: 0.6,
|
||||
duration: 600,
|
||||
ease: "Sine.easeInOut",
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
});
|
||||
createHighlightInnerPulseTween(this.scene, [outerCircle, innerCircle]);
|
||||
|
||||
// Outer circle staggered pulse
|
||||
this.scene.tweens.add({
|
||||
targets: outerCircle,
|
||||
scale: 1.3,
|
||||
alpha: 0.3,
|
||||
duration: 800,
|
||||
ease: "Sine.easeInOut",
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
delay: 200,
|
||||
});
|
||||
createHighlightOuterPulseTween(this.scene, outerCircle);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,13 @@ import { GameObjects } from "phaser";
|
|||
|
||||
import type { OnitamaScene } from "@/scenes/OnitamaScene";
|
||||
|
||||
import { CELL_SIZE, COLORS, ANIMATIONS } from "@/config";
|
||||
import {
|
||||
CELL_SIZE,
|
||||
COLORS,
|
||||
createSelectionShowTween,
|
||||
createSelectionRingPulseTween,
|
||||
createSelectionHideTween,
|
||||
} from "@/config";
|
||||
|
||||
export interface SelectionRenderOptions {
|
||||
x: number;
|
||||
|
|
@ -51,23 +57,9 @@ export class SelectionRenderer {
|
|||
// Fade in animation
|
||||
// DO NOT add interruption here
|
||||
// otherwise the game will stop indefinitely
|
||||
tweens.add({
|
||||
targets: ring,
|
||||
alpha: 0.8,
|
||||
duration: ANIMATIONS.selectionFadeIn,
|
||||
ease: "Power2",
|
||||
onComplete: () => {
|
||||
// Start pulse animation after fade-in completes
|
||||
pulseTween = tweens.add({
|
||||
targets: ring,
|
||||
scale: 1.15,
|
||||
alpha: 0.6,
|
||||
duration: ANIMATIONS.selectionPulse,
|
||||
ease: "Sine.easeInOut",
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
});
|
||||
},
|
||||
createSelectionShowTween(this.scene, ring, () => {
|
||||
// Start pulse animation after fade-in completes
|
||||
pulseTween = createSelectionRingPulseTween(this.scene, ring);
|
||||
});
|
||||
|
||||
// Return cleanup function
|
||||
|
|
@ -97,17 +89,7 @@ export class SelectionRenderer {
|
|||
tweens.killTweensOf(ring);
|
||||
|
||||
// Fade out animation
|
||||
tweens.add({
|
||||
targets: ring,
|
||||
alpha: 0,
|
||||
scale: 0.9,
|
||||
duration: ANIMATIONS.selectionFadeOut,
|
||||
ease: "Power2",
|
||||
onComplete: () => {
|
||||
ring.destroy();
|
||||
onComplete?.();
|
||||
},
|
||||
});
|
||||
createSelectionHideTween(this.scene, ring, () => onComplete?.());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ import type Phaser from "phaser";
|
|||
import {
|
||||
COLORS,
|
||||
FONTS,
|
||||
ANIMATIONS,
|
||||
MENU_BUTTON,
|
||||
getBoardCenter,
|
||||
getCardLabelPosition,
|
||||
colorToStr,
|
||||
createWinnerPulseTween,
|
||||
} from "@/config";
|
||||
import { prompts } from "@/game/onitama";
|
||||
import {
|
||||
|
|
@ -304,12 +304,6 @@ export class OnitamaScene extends GameHostScene<OnitamaState> {
|
|||
|
||||
this.winnerOverlay.add(winText);
|
||||
|
||||
this.tweens.add({
|
||||
targets: winText,
|
||||
scale: 1.2,
|
||||
duration: ANIMATIONS.winnerPulse,
|
||||
yoyo: true,
|
||||
repeat: 1,
|
||||
});
|
||||
createWinnerPulseTween(this, winText);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,18 @@ import type { Card } from "@/game/onitama";
|
|||
import type { OnitamaScene } from "@/scenes/OnitamaScene";
|
||||
import type { Spawner } from "boardgame-phaser";
|
||||
|
||||
import { CARD_WIDTH, CARD_HEIGHT, ANIMATIONS, getCardPosition } from "@/config";
|
||||
import {
|
||||
CARD_WIDTH,
|
||||
CARD_HEIGHT,
|
||||
getCardPosition,
|
||||
createCardMoveTween,
|
||||
createCardRotateTween,
|
||||
createCardSpawnTween,
|
||||
createCardDespawnTween,
|
||||
createSelectionFadeInTween,
|
||||
createSelectionPulseTween,
|
||||
createSelectionFadeOutTween,
|
||||
} from "@/config";
|
||||
import { CardRenderer } from "@/renderers";
|
||||
|
||||
// Re-export for backward compatibility
|
||||
|
|
@ -66,13 +77,12 @@ export class CardContainer extends Phaser.GameObjects.Container {
|
|||
setRotation(rotation: number, animated: boolean = false): this {
|
||||
if (animated) {
|
||||
const currentAngle = this.angle;
|
||||
const tween = this.scene.tweens.add({
|
||||
targets: this,
|
||||
angle: { from: currentAngle, to: rotation },
|
||||
duration: ANIMATIONS.cardRotate,
|
||||
ease: "Back.easeOut",
|
||||
});
|
||||
(this.scene as OnitamaScene).addTweenInterruption(tween);
|
||||
createCardRotateTween(
|
||||
this.scene as OnitamaScene,
|
||||
this,
|
||||
currentAngle,
|
||||
rotation,
|
||||
);
|
||||
} else {
|
||||
this.angle = rotation;
|
||||
}
|
||||
|
|
@ -88,32 +98,22 @@ export class CardContainer extends Phaser.GameObjects.Container {
|
|||
|
||||
if (!this.highlightRect) {
|
||||
// 创建高亮边框(初始透明)
|
||||
this.highlightRect = (this.scene as OnitamaScene).add
|
||||
const rect = (this.scene as OnitamaScene).add
|
||||
.rectangle(0, 0, CARD_WIDTH + 8, CARD_HEIGHT + 8, color, 0)
|
||||
.setStrokeStyle(lineWidth, color)
|
||||
.setAlpha(0)
|
||||
.setDepth(-1);
|
||||
this.highlightRect = rect;
|
||||
this.addAt(this.highlightRect, 0);
|
||||
|
||||
// 淡入动画
|
||||
this.scene.tweens.add({
|
||||
targets: this.highlightRect,
|
||||
alpha: 1,
|
||||
scale: 1.05,
|
||||
duration: ANIMATIONS.selectionFadeIn,
|
||||
ease: "Power2",
|
||||
onComplete: () => {
|
||||
// 淡入完成后开始脉冲动画
|
||||
this.highlightTween = this.scene.tweens.add({
|
||||
targets: this.highlightRect,
|
||||
alpha: 0.7,
|
||||
lineWidth: lineWidth + 1,
|
||||
duration: ANIMATIONS.selectionPulse,
|
||||
ease: "Sine.easeInOut",
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
});
|
||||
},
|
||||
createSelectionFadeInTween(this.scene as OnitamaScene, rect, () => {
|
||||
// 淡入完成后开始脉冲动画
|
||||
this.highlightTween = createSelectionPulseTween(
|
||||
this.scene as OnitamaScene,
|
||||
rect,
|
||||
lineWidth,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// 如果已经存在,停止当前动画并重新开始脉冲
|
||||
|
|
@ -121,15 +121,11 @@ export class CardContainer extends Phaser.GameObjects.Container {
|
|||
this.highlightTween.stop();
|
||||
}
|
||||
this.highlightRect.setStrokeStyle(lineWidth, color);
|
||||
this.highlightTween = this.scene.tweens.add({
|
||||
targets: this.highlightRect,
|
||||
alpha: 0.7,
|
||||
lineWidth: lineWidth + 1,
|
||||
duration: ANIMATIONS.selectionPulse,
|
||||
ease: "Sine.easeInOut",
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
});
|
||||
this.highlightTween = createSelectionPulseTween(
|
||||
this.scene as OnitamaScene,
|
||||
this.highlightRect,
|
||||
lineWidth,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -146,18 +142,15 @@ export class CardContainer extends Phaser.GameObjects.Container {
|
|||
this.scene.tweens.killTweensOf(this.highlightRect);
|
||||
|
||||
// 淡出动画
|
||||
this.scene.tweens.add({
|
||||
targets: this.highlightRect,
|
||||
alpha: 0,
|
||||
scale: 0.95,
|
||||
duration: ANIMATIONS.selectionFadeOut,
|
||||
ease: "Power2",
|
||||
onComplete: () => {
|
||||
createSelectionFadeOutTween(
|
||||
this.scene as OnitamaScene,
|
||||
this.highlightRect,
|
||||
() => {
|
||||
// 淡出完成后销毁矩形
|
||||
this.highlightRect?.destroy();
|
||||
this.highlightRect = null;
|
||||
},
|
||||
});
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -242,15 +235,7 @@ export class CardSpawner implements Spawner<CardSpawnData, CardContainer> {
|
|||
const pos = getCardPosition(data.position, data.index);
|
||||
|
||||
// 播放移动动画并添加中断
|
||||
const tween = this.scene.tweens.add({
|
||||
targets: obj,
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
duration: ANIMATIONS.cardMove,
|
||||
ease: "Back.easeOut",
|
||||
});
|
||||
|
||||
this.scene.addTweenInterruption(tween);
|
||||
createCardMoveTween(this.scene, obj, pos.x, pos.y);
|
||||
this.previousData.set(data.cardId, { ...data });
|
||||
}
|
||||
|
||||
|
|
@ -305,27 +290,13 @@ export class CardSpawner implements Spawner<CardSpawnData, CardContainer> {
|
|||
|
||||
// 初始状态为透明,然后淡入
|
||||
container.setAlpha(0);
|
||||
const tween = this.scene.tweens.add({
|
||||
targets: container,
|
||||
alpha: 1,
|
||||
duration: ANIMATIONS.cardSpawn,
|
||||
ease: "Power2",
|
||||
});
|
||||
this.scene.addTweenInterruption(tween);
|
||||
createCardSpawnTween(this.scene, container);
|
||||
|
||||
this.previousData.set(data.cardId, { ...data });
|
||||
return container;
|
||||
}
|
||||
|
||||
onDespawn(obj: CardContainer): void {
|
||||
const tween = this.scene.tweens.add({
|
||||
targets: obj,
|
||||
alpha: 0,
|
||||
scale: 0.8,
|
||||
duration: ANIMATIONS.cardDespawn,
|
||||
ease: "Power2",
|
||||
onComplete: () => obj.destroy(),
|
||||
});
|
||||
this.scene.addTweenInterruption(tween);
|
||||
createCardDespawnTween(this.scene, obj, () => obj.destroy());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,13 @@ import { Geom } from "phaser";
|
|||
import type { OnitamaScene } from "@/scenes/OnitamaScene";
|
||||
import type { Spawner } from "boardgame-phaser";
|
||||
|
||||
import { boardToScreen, CELL_SIZE, ANIMATIONS } from "@/config";
|
||||
import {
|
||||
boardToScreen,
|
||||
CELL_SIZE,
|
||||
createHighlightSpawnTween,
|
||||
createHighlightDespawnTween,
|
||||
createHighlightClickFeedbackTween,
|
||||
} from "@/config";
|
||||
import { HighlightRenderer } from "@/renderers";
|
||||
|
||||
export interface HighlightData {
|
||||
|
|
@ -86,28 +92,14 @@ export class HighlightSpawner implements Spawner<
|
|||
// 出现动画:从0缩放和透明淡入
|
||||
container.setAlpha(0);
|
||||
container.setScale(0);
|
||||
const spawnTween = this.scene.tweens.add({
|
||||
targets: container,
|
||||
alpha: 1,
|
||||
scale: 1,
|
||||
duration: ANIMATIONS.highlightSpawn,
|
||||
ease: "Back.easeOut",
|
||||
});
|
||||
this.scene.addTweenInterruption(spawnTween);
|
||||
createHighlightSpawnTween(this.scene, container);
|
||||
|
||||
// 使用 HighlightRenderer 设置脉冲动画
|
||||
this.renderer.setupPulseAnimations(container);
|
||||
|
||||
container.on("pointerdown", () => {
|
||||
// 点击时的反馈动画
|
||||
this.scene.tweens.add({
|
||||
targets: container,
|
||||
scale: 1.5,
|
||||
alpha: 0.8,
|
||||
duration: ANIMATIONS.clickFeedback,
|
||||
ease: "Power2",
|
||||
yoyo: true,
|
||||
});
|
||||
createHighlightClickFeedbackTween(this.scene, container);
|
||||
|
||||
this.scene.onHighlightClick(data);
|
||||
});
|
||||
|
|
@ -117,14 +109,6 @@ export class HighlightSpawner implements Spawner<
|
|||
|
||||
onDespawn(obj: Phaser.GameObjects.Container): void {
|
||||
// 消失动画:缩小并淡出
|
||||
const despawnTween = this.scene.tweens.add({
|
||||
targets: obj,
|
||||
scale: 0,
|
||||
alpha: 0,
|
||||
duration: ANIMATIONS.highlightDespawn,
|
||||
ease: "Back.easeIn",
|
||||
onComplete: () => obj.destroy(),
|
||||
});
|
||||
this.scene.addTweenInterruption(despawnTween);
|
||||
createHighlightDespawnTween(this.scene, obj, () => obj.destroy());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,12 @@ import type { Pawn } from "@/game/onitama";
|
|||
import type { OnitamaScene } from "@/scenes/OnitamaScene";
|
||||
import type { Spawner } from "boardgame-phaser";
|
||||
|
||||
import { boardToScreen, ANIMATIONS } from "@/config";
|
||||
import {
|
||||
boardToScreen,
|
||||
createPawnMoveTween,
|
||||
createPawnSpawnTween,
|
||||
createPawnDespawnTween,
|
||||
} from "@/config";
|
||||
import { PawnRenderer, SelectionRenderer } from "@/renderers";
|
||||
|
||||
// Re-export for backward compatibility
|
||||
|
|
@ -97,14 +102,12 @@ export class PawnContainer extends GameObjects.Container {
|
|||
const targetPos = boardToScreen(newPosition[0], newPosition[1]);
|
||||
|
||||
if (animated) {
|
||||
const tween = this.scene.tweens.add({
|
||||
targets: this,
|
||||
x: targetPos.x,
|
||||
y: targetPos.y,
|
||||
duration: ANIMATIONS.pawnMove,
|
||||
ease: "Back.easeOut",
|
||||
});
|
||||
(this.scene as OnitamaScene).addTweenInterruption(tween);
|
||||
createPawnMoveTween(
|
||||
this.scene as OnitamaScene,
|
||||
this,
|
||||
targetPos.x,
|
||||
targetPos.y,
|
||||
);
|
||||
} else {
|
||||
this.x = targetPos.x;
|
||||
this.y = targetPos.y;
|
||||
|
|
@ -153,28 +156,13 @@ export class PawnSpawner implements Spawner<Pawn, PawnContainer> {
|
|||
|
||||
// 淡入动画
|
||||
container.setScale(0);
|
||||
const tween = this.scene.tweens.add({
|
||||
targets: container,
|
||||
scale: 1,
|
||||
duration: ANIMATIONS.pawnSpawn,
|
||||
ease: "Back.easeOut",
|
||||
});
|
||||
this.scene.addTweenInterruption(tween);
|
||||
createPawnSpawnTween(this.scene, container);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
onDespawn(obj: PawnContainer): void {
|
||||
// 播放消失动画并添加中断
|
||||
const tween = this.scene.tweens.add({
|
||||
targets: obj,
|
||||
scale: 0,
|
||||
alpha: 0,
|
||||
duration: ANIMATIONS.pawnDespawn,
|
||||
ease: "Back.easeIn",
|
||||
onComplete: () => obj.destroy(),
|
||||
});
|
||||
|
||||
this.scene.addTweenInterruption(tween);
|
||||
createPawnDespawnTween(this.scene, obj, () => obj.destroy());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue