feat(framework): add hoverEffect utility

Add a `hoverEffect` utility to `boardgame-phaser` to simplify
handling pointerover and pointerout events. Refactor
`CardContainer` in `sts-like-viewer` to use this new utility and
centralize card dimensions in `CARD_CONFIG`.
This commit is contained in:
hypercross 2026-04-22 15:13:33 +08:00
parent 5d84c42b78
commit dda290bf9c
5 changed files with 60 additions and 40 deletions

View File

@ -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";

View File

@ -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;
}

View File

@ -6,3 +6,4 @@ export {
type DragDropEvent,
type DragDropCallback,
} from "./dnd";
export * from "./hover";

View File

@ -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,

View File

@ -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);