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

View File

@ -33,7 +33,7 @@ export class InventoryItemState {
this.previewRotation = computed(() => {
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;
}
addPreviewRotation(rotation: number): void {
this._previewRotation.value += rotation;
}
private computeColor(itemId: string): number {
const hash = itemId.split("").reduce((acc, c) => acc + c.charCodeAt(0), 0);
return ITEM_COLORS[hash % ITEM_COLORS.length];