diff --git a/packages/framework/src/index.ts b/packages/framework/src/index.ts index a7c126a..e2ebaf7 100644 --- a/packages/framework/src/index.ts +++ b/packages/framework/src/index.ts @@ -5,6 +5,7 @@ export type { IDisposable, DisposableItem } from "./utils"; // Drag & drop utilities export { dragDropEventEffect, DragDropEventType } from "./utils"; export type { DragDropEvent, DragDropCallback } from "./utils"; +export { hoverEffect } from "./utils"; // Data-driven object spawning export { spawnEffect } from "./spawner"; diff --git a/packages/framework/src/utils/hover.ts b/packages/framework/src/utils/hover.ts new file mode 100644 index 0000000..f3fe67e --- /dev/null +++ b/packages/framework/src/utils/hover.ts @@ -0,0 +1,28 @@ +type HoverCallback = (hovering: boolean) => void; +export function hoverEffect( + gameObject: Phaser.GameObjects.GameObject, + callback: HoverCallback, +) { + let isHovering = false; + const onPointerOver = () => { + if (isHovering) return; + isHovering = true; + callback(true); + }; + const onPointerOut = () => { + if (!isHovering) return; + isHovering = false; + callback(false); + }; + + gameObject.on("pointerover", onPointerOver); + gameObject.on("pointerout", onPointerOut); + const cleanup = () => { + gameObject.off("pointerover", onPointerOver); + gameObject.off("pointerout", onPointerOut); + gameObject.off("destroy", cleanup); + }; + + gameObject.once("destroy", cleanup); + return cleanup; +} diff --git a/packages/framework/src/utils/index.ts b/packages/framework/src/utils/index.ts index 4847db9..ed5ac9f 100644 --- a/packages/framework/src/utils/index.ts +++ b/packages/framework/src/utils/index.ts @@ -6,3 +6,4 @@ export { type DragDropEvent, type DragDropCallback, } from "./dnd"; +export * from "./hover"; diff --git a/packages/sts-like-viewer/src/config/index.ts b/packages/sts-like-viewer/src/config/index.ts index 038c17f..5d80261 100644 --- a/packages/sts-like-viewer/src/config/index.ts +++ b/packages/sts-like-viewer/src/config/index.ts @@ -156,6 +156,12 @@ export const NEGATIVE_EFFECTS = new Set([ "poison", ]); +export const CARD_CONFIG = { + WIDTH: 140, + HEIGHT: 200, + CORNER_RADIUS: 8, +} as const; + export const GAME_CONFIG: Phaser.Types.Core.GameConfig = { width: 1920, height: 1080, diff --git a/packages/sts-like-viewer/src/gameobjects/CardContainer.ts b/packages/sts-like-viewer/src/gameobjects/CardContainer.ts index 9e0343e..755903a 100644 --- a/packages/sts-like-viewer/src/gameobjects/CardContainer.ts +++ b/packages/sts-like-viewer/src/gameobjects/CardContainer.ts @@ -1,8 +1,7 @@ -import Phaser from "phaser"; -import type { - CardData, - GameCard, -} from "boardgame-core/samples/slay-the-spire-like"; +import Phaser, { Tweens } from "phaser"; +import type { GameCard } from "boardgame-core/samples/slay-the-spire-like"; +import { CARD_CONFIG } from "@/config"; +import { hoverEffect } from "boardgame-phaser"; export interface CardContainerOptions { card: GameCard; @@ -10,9 +9,9 @@ export interface CardContainerOptions { playable?: boolean; } -const CARD_WIDTH = 140; -const CARD_HEIGHT = 200; -const CORNER_RADIUS = 8; +const CARD_WIDTH = CARD_CONFIG.WIDTH; +const CARD_HEIGHT = CARD_CONFIG.HEIGHT; +const CORNER_RADIUS = CARD_CONFIG.CORNER_RADIUS; export class CardContainer extends Phaser.GameObjects.Container { private bg!: Phaser.GameObjects.Rectangle; @@ -110,16 +109,9 @@ export class CardContainer extends Phaser.GameObjects.Container { this.input.cursor = options.playable ? "pointer" : "not-allowed"; } - this.on("pointerover", () => { - if (options.playable && !this._selected) { - this.hoverIn(); - } - }); - - this.on("pointerout", () => { - if (!this._selected) { - this.hoverOut(); - } + hoverEffect(this, (hovering) => { + if (!options.playable) return; + this.playHoverTween(hovering); }); this.on("pointerdown", () => { @@ -129,28 +121,6 @@ export class CardContainer extends Phaser.GameObjects.Container { }); } - private hoverIn(): void { - this.scene.tweens.add({ - targets: this, - y: this.y - 10, - scale: 1.08, - duration: 150, - ease: "Power2", - }); - this.bg.setStrokeStyle(3, 0x88aaff); - } - - private hoverOut(): void { - this.scene.tweens.add({ - targets: this, - y: this.y + 10, - scale: 1, - duration: 150, - ease: "Power2", - }); - this.bg.setStrokeStyle(2, 0x555577); - } - setSelected(selected: boolean): void { this._selected = selected; if (selected) { @@ -212,6 +182,20 @@ export class CardContainer extends Phaser.GameObjects.Container { }); } + private playHoverTween(isHovering: boolean) { + this.scene.tweens.add({ + targets: this, + y: isHovering ? this.y - 10 : this.y + 10, + scale: isHovering ? 1.08 : 1, + duration: 150, + ease: "Power2", + }); + this.bg.setStrokeStyle( + isHovering ? 3 : 2, + isHovering ? 0x88aaff : 0x555577, + ); + } + destroy(fromScene?: boolean): void { this.highlight = null; super.destroy(fromScene);