refactor: improve inventory item interaction and state

- Decouple hit area calculation from the rendering effect in
  `InventoryItemContainer`.
- Implement a dedicated `hitArea` property to track cell rectangles.
- Update `setupInteractive` to use the container itself as the hit area
  with a custom callback.
- Move drag-and-drop logic into a dedicated `setupDnDEffect` method.
- Add `addPreviewRotation` to `InventoryItemState` for cleaner rotation
  updates.
- Normalize `previewRotation` to stay within 0-360 degrees.
This commit is contained in:
hypercross 2026-04-20 21:59:08 +08:00
parent c9f573ef86
commit 30e2d9ac36
2 changed files with 27 additions and 24 deletions

View File

@ -2,15 +2,14 @@ import Phaser from "phaser";
import { dragDropEventEffect, DragDropEventType } from "boardgame-phaser"; import { dragDropEventEffect, DragDropEventType } from "boardgame-phaser";
import { GRID_CONFIG } from "@/config"; import { GRID_CONFIG } from "@/config";
import { import {
IDENTITY_TRANSFORM,
ParsedShape, ParsedShape,
Point2D,
Transform2D, Transform2D,
transformShape, transformShape,
type GameItem, type GameItem,
} from "boardgame-core/samples/slay-the-spire-like"; } from "boardgame-core/samples/slay-the-spire-like";
import { DisposableBag } from "../../../framework/dist"; import { DisposableBag } from "../../../framework/dist";
import { InventoryItemState } from "@/state/InventoryItemState"; import { InventoryItemState } from "@/state/InventoryItemState";
import { effect } from "@preact/signals-core";
export interface InventoryItemContainerCallbacks { export interface InventoryItemContainerCallbacks {
onMoveItem: ( onMoveItem: (
@ -23,6 +22,7 @@ export interface InventoryItemContainerCallbacks {
export class InventoryItemContainer extends Phaser.GameObjects.Container { export class InventoryItemContainer extends Phaser.GameObjects.Container {
private itemState: InventoryItemState; private itemState: InventoryItemState;
private hitArea: Point2D[] = [];
constructor( constructor(
scene: Phaser.Scene, scene: Phaser.Scene,
@ -41,6 +41,7 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container {
GRID_CONFIG.ITEM_NAME_STYLE, GRID_CONFIG.ITEM_NAME_STYLE,
); );
this.add([graphics, label]); this.add([graphics, label]);
this.setupInteractive();
this.itemState = new InventoryItemState(); this.itemState = new InventoryItemState();
@ -50,20 +51,18 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container {
label.setText(this.itemState.name.value); label.setText(this.itemState.name.value);
}); });
disposables.add( disposables.add(this.setupDnDEffect());
effect(() => {
graphics.clear();
if (!this.itemState.shape.value) return;
const cellRects = this.renderGraphics(
graphics,
this.itemState.shape.value,
this.itemState.color.value,
this.itemState.previewRotation.value,
);
return this.setupInteractive(cellRects); disposables.addEffect(() => {
}), graphics.clear();
); if (!this.itemState.shape.value) return;
this.hitArea = this.renderGraphics(
graphics,
this.itemState.shape.value,
this.itemState.color.value,
this.itemState.previewRotation.value,
);
});
disposables.addEffect(() => { disposables.addEffect(() => {
if (!this.itemState.transform.value) return; if (!this.itemState.transform.value) return;
@ -81,7 +80,7 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container {
shape: ParsedShape, shape: ParsedShape,
itemColor: number, itemColor: number,
rotation: number, rotation: number,
): { x: number; y: number }[] { ) {
const transform: Transform2D = { const transform: Transform2D = {
offset: { x: 0, y: 0 }, offset: { x: 0, y: 0 },
rotation, rotation,
@ -109,17 +108,17 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container {
return cellRects; return cellRects;
} }
private setupInteractive(cellRects: { x: number; y: number }[]) { private setupInteractive() {
this.setScrollFactor(0); this.setScrollFactor(0);
const cellSize = GRID_CONFIG.VIEWER_CELL_SIZE; const cellSize = GRID_CONFIG.VIEWER_CELL_SIZE;
this.setInteractive({ this.setInteractive({
hitArea: cellRects, hitArea: this,
hitAreaCallback: ( hitAreaCallback: (
hitArea: { x: number; y: number }[], hitArea: InventoryItemContainer,
x: number, x: number,
y: number, y: number,
) => ) =>
hitArea.some( hitArea.hitArea.some(
(cell) => (cell) =>
x >= cell.x && x >= cell.x &&
x < cell.x + cellSize && x < cell.x + cellSize &&
@ -128,11 +127,12 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container {
), ),
useHandCursor: true, useHandCursor: true,
} as Phaser.Types.Input.InputConfiguration); } as Phaser.Types.Input.InputConfiguration);
}
private setupDnDEffect() {
let startX = 0; let startX = 0;
let startY = 0; let startY = 0;
return dragDropEventEffect(this, (event) => { return dragDropEventEffect(this, (event) => {
console.log(event.type);
if (event.type === DragDropEventType.DOWN) { if (event.type === DragDropEventType.DOWN) {
startX = this.x; startX = this.x;
startY = this.y; startY = this.y;
@ -141,8 +141,7 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container {
this.x = startX + event.deltaX; this.x = startX + event.deltaX;
this.y = startY + event.deltaY; this.y = startY + event.deltaY;
} else if (event.type === DragDropEventType.ALTBUTTON) { } else if (event.type === DragDropEventType.ALTBUTTON) {
const current = this.itemState.previewRotation.peek(); this.itemState.addPreviewRotation(90);
this.itemState.setPreviewRotation(current + 90);
} else if (event.type === DragDropEventType.UP) { } else if (event.type === DragDropEventType.UP) {
this.setAlpha(1); this.setAlpha(1);
const finalRotation = this.itemState.previewRotation.peek(); const finalRotation = this.itemState.previewRotation.peek();

View File

@ -33,7 +33,7 @@ export class InventoryItemState {
this.previewRotation = computed(() => { this.previewRotation = computed(() => {
const base = this._item.value?.transform?.rotation ?? 0; const base = this._item.value?.transform?.rotation ?? 0;
return base + this._previewRotation.value; return (base + this._previewRotation.value) % 360;
}); });
} }
@ -50,6 +50,10 @@ export class InventoryItemState {
this._previewRotation.value = rotation; this._previewRotation.value = rotation;
} }
addPreviewRotation(rotation: number): void {
this._previewRotation.value += rotation;
}
private computeColor(itemId: string): number { private computeColor(itemId: string): number {
const hash = itemId.split("").reduce((acc, c) => acc + c.charCodeAt(0), 0); const hash = itemId.split("").reduce((acc, c) => acc + c.charCodeAt(0), 0);
return ITEM_COLORS[hash % ITEM_COLORS.length]; return ITEM_COLORS[hash % ITEM_COLORS.length];