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:
parent
c9f573ef86
commit
30e2d9ac36
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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];
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue