feat(sts-like-viewer): animate inventory item rotation

Introduce a tween for the item's rotation and update the hit area
calculation to account for the preview rotation. The graphics are now
centered within the container to support these transformations.
This commit is contained in:
hypercross 2026-04-20 22:15:05 +08:00
parent 30e2d9ac36
commit fe0621cedf
1 changed files with 38 additions and 17 deletions

View File

@ -2,6 +2,7 @@ import Phaser from "phaser";
import { dragDropEventEffect, DragDropEventType } from "boardgame-phaser";
import { GRID_CONFIG } from "@/config";
import {
IDENTITY_TRANSFORM,
ParsedShape,
Point2D,
Transform2D,
@ -34,6 +35,10 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container {
scene.add.existing(this);
const graphics = this.scene.add.graphics();
graphics.setPosition(
GRID_CONFIG.VIEWER_CELL_SIZE / 2,
GRID_CONFIG.VIEWER_CELL_SIZE / 2,
);
const label = this.scene.add.text(
GRID_CONFIG.VIEWER_CELL_SIZE / 2,
GRID_CONFIG.VIEWER_CELL_SIZE / 2,
@ -56,14 +61,23 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container {
disposables.addEffect(() => {
graphics.clear();
if (!this.itemState.shape.value) return;
this.hitArea = this.renderGraphics(
this.renderGraphics(
graphics,
this.itemState.shape.value,
this.itemState.color.value,
this.itemState.previewRotation.value,
);
});
disposables.addEffect(() => {
this.scene.tweens.add({
targets: graphics,
rotation: -(this.itemState.previewRotation.value * Math.PI) / 180,
duration: 150,
ease: "Power2",
});
this.hitArea = this.updateHitArea(this.itemState.previewRotation.value);
});
disposables.addEffect(() => {
if (!this.itemState.transform.value) return;
this.snapBack(this.itemState.transform.value);
@ -71,6 +85,25 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container {
});
}
updateHitArea(value: number): Point2D[] {
const shape = this.itemState.shape.value;
if (!shape) return [];
const rotation = value;
const cells = transformShape(shape, {
rotation,
offset: { x: 0, y: 0 },
flipX: false,
flipY: false,
});
const cellSize = GRID_CONFIG.VIEWER_CELL_SIZE;
return cells.map((cell) => ({
x: (cell.x - cells[0].x) * cellSize,
y: (cell.y - cells[0].y) * cellSize,
}));
}
setItem(item: GameItem): void {
this.itemState.setItem(item);
}
@ -79,16 +112,8 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container {
graphics: Phaser.GameObjects.Graphics,
shape: ParsedShape,
itemColor: number,
rotation: number,
) {
const transform: Transform2D = {
offset: { x: 0, y: 0 },
rotation,
flipX: false,
flipY: false,
};
const cells = transformShape(shape, transform);
const cellRects: { x: number; y: number }[] = [];
const cells = transformShape(shape, IDENTITY_TRANSFORM);
for (const cell of cells) {
const localX = (cell.x - cells[0].x) * GRID_CONFIG.VIEWER_CELL_SIZE;
@ -96,16 +121,12 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container {
graphics.fillStyle(itemColor);
graphics.fillRect(
localX + 2,
localY + 2,
localX + 2 - GRID_CONFIG.VIEWER_CELL_SIZE / 2,
localY + 2 - GRID_CONFIG.VIEWER_CELL_SIZE / 2,
GRID_CONFIG.VIEWER_CELL_SIZE - 4,
GRID_CONFIG.VIEWER_CELL_SIZE - 4,
);
cellRects.push({ x: localX, y: localY });
}
return cellRects;
}
private setupInteractive() {