feat(sts-viewer): support multiple inventory surfaces

Refactor `InventoryTestScene` to support multiple inventory signals
simultaneously. This introduces `InventorySurface` to manage
positioning and signal mapping for multiple grids, allowing for
testing item movement between different inventory areas.
This commit is contained in:
hypercross 2026-04-21 16:48:45 +08:00
parent 422ddde200
commit c97c2c4150
2 changed files with 80 additions and 66 deletions

View File

@ -1,19 +1,20 @@
import { ReactiveScene } from "boardgame-phaser"; import { ReactiveScene } from "boardgame-phaser";
import { createButton } from "@/utils/createButton"; import { createButton } from "@/utils/createButton";
import { GRID_CONFIG } from "@/config"; import { GRID_CONFIG } from "@/config";
import { createInventorySignal, moveItem } from "@/state/inventory"; import { createInventorySignal, type InventorySignal } from "@/state/inventory";
import { import { createItemIn, data } from "boardgame-core/samples/slay-the-spire-like";
createItemIn,
data,
removeItemFromGrid,
} from "boardgame-core/samples/slay-the-spire-like";
import { createInventoryItemSpawner } from "@/gameobjects/InventoryItemSpawner"; import { createInventoryItemSpawner } from "@/gameobjects/InventoryItemSpawner";
import { SceneKey } from "./types"; import { SceneKey } from "./types";
import { InventorySurface } from "@/state/inventorySurfaceState";
export class InventoryTestScene extends ReactiveScene { export class InventoryTestScene extends ReactiveScene {
private inventorySignal = createInventorySignal(); private readonly _surfaces: InventorySurface[] = [];
private gridOffsetX = 0; private _leftInv!: InventorySignal;
private gridOffsetY = 0; private _rightInv!: InventorySignal;
private _leftGridOffsetX = 0;
private _leftGridOffsetY = 0;
private _rightGridOffsetX = 0;
private _rightGridOffsetY = 0;
constructor() { constructor() {
super("InventoryTestScene"); super("InventoryTestScene");
@ -23,22 +24,62 @@ export class InventoryTestScene extends ReactiveScene {
super.create(); super.create();
const { width, height } = this.scale; const { width, height } = this.scale;
const inventory = this.inventorySignal.value;
const invWidth = inventory.width;
const invHeight = inventory.height;
this.gridOffsetX = (width - invWidth * GRID_CONFIG.VIEWER_CELL_SIZE) / 2; this._leftInv = createInventorySignal();
this.gridOffsetY = this._rightInv = createInventorySignal();
(height - invHeight * GRID_CONFIG.VIEWER_CELL_SIZE) / 2 + 40;
this.drawGrid(invWidth, invHeight); const invWidth = 4;
const invHeight = 6;
const cellSize = GRID_CONFIG.VIEWER_CELL_SIZE;
const gap = 80;
this._leftGridOffsetX =
(width / 2 - gap / 2) / 2 - (invWidth * cellSize) / 2;
this._leftGridOffsetY = (height - invHeight * cellSize) / 2 + 40;
this._rightGridOffsetX =
width / 2 +
gap / 2 +
(width / 2 - gap / 2) / 2 -
(invWidth * cellSize) / 2;
this._rightGridOffsetY = (height - invHeight * cellSize) / 2 + 40;
this._surfaces.push(
{
invSignal: this._leftInv,
gridOffsetX: this._leftGridOffsetX,
gridOffsetY: this._leftGridOffsetY,
cellSize,
},
{
invSignal: this._rightInv,
gridOffsetX: this._rightGridOffsetX,
gridOffsetY: this._rightGridOffsetY,
cellSize,
},
);
this.drawGrid(
this._leftInv,
invWidth,
invHeight,
this._leftGridOffsetX,
this._leftGridOffsetY,
);
this.drawGrid(
this._rightInv,
invWidth,
invHeight,
this._rightGridOffsetX,
this._rightGridOffsetY,
);
this.setupItemSpawner(); this.setupItemSpawner();
this.add this.add
.text( .text(
width / 2, width / 2,
30, 30,
"Inventory Signal Test (4x6)", "Inventory Signal Test (4x6 x2)",
GRID_CONFIG.TITLE_STYLE, GRID_CONFIG.TITLE_STYLE,
) )
.setOrigin(0.5); .setOrigin(0.5);
@ -49,24 +90,28 @@ export class InventoryTestScene extends ReactiveScene {
.text( .text(
width / 2, width / 2,
height - 40, height - 40,
"Drag items to move them", "Drag items between inventories",
GRID_CONFIG.SUBTITLE_STYLE, GRID_CONFIG.SUBTITLE_STYLE,
) )
.setOrigin(0.5); .setOrigin(0.5);
} }
private drawGrid(invWidth: number, invHeight: number): void { private drawGrid(
inv: InventorySignal,
invWidth: number,
invHeight: number,
offsetX: number,
offsetY: number,
): void {
const graphics = this.add.graphics(); const graphics = this.add.graphics();
this.addEffect(() => { this.addEffect(() => {
for (let y = 0; y < invHeight; y++) { for (let y = 0; y < invHeight; y++) {
for (let x = 0; x < invWidth; x++) { for (let x = 0; x < invWidth; x++) {
const px = this.gridOffsetX + x * GRID_CONFIG.VIEWER_CELL_SIZE; const px = offsetX + x * GRID_CONFIG.VIEWER_CELL_SIZE;
const py = this.gridOffsetY + y * GRID_CONFIG.VIEWER_CELL_SIZE; const py = offsetY + y * GRID_CONFIG.VIEWER_CELL_SIZE;
const isOccupied = this.inventorySignal.value.occupiedCells.has( const isOccupied = inv.value.occupiedCells.has(`${x},${y}`);
`${x},${y}`,
);
graphics.fillStyle( graphics.fillStyle(
isOccupied isOccupied
? GRID_CONFIG.CELL_OCCUPIED_COLOR ? GRID_CONFIG.CELL_OCCUPIED_COLOR
@ -91,28 +136,7 @@ export class InventoryTestScene extends ReactiveScene {
} }
private setupItemSpawner(): void { private setupItemSpawner(): void {
const spawner = createInventoryItemSpawner( const spawner = createInventoryItemSpawner(this, this._surfaces);
this,
this.inventorySignal,
this.gridOffsetX,
this.gridOffsetY,
{
onMoveItem: (
itemId: string,
newX: number,
newY: number,
newRotation: number,
) => {
return moveItem(
this.inventorySignal,
itemId,
newX,
newY,
newRotation,
);
},
},
);
this.disposables.add(spawner); this.disposables.add(spawner);
} }
@ -131,29 +155,29 @@ export class InventoryTestScene extends ReactiveScene {
createButton({ createButton({
scene: this, scene: this,
label: "添加道具", label: "添加道具(左)",
x: width - 300, x: width - 350,
y: 40, y: 40,
onClick: () => { onClick: () => {
this.addRandomItem(); this.addRandomItem(this._leftInv);
}, },
}); });
createButton({ createButton({
scene: this, scene: this,
label: "移除最后一个", label: "添加道具(右)",
x: width - 150, x: width - 180,
y: 40, y: 40,
onClick: () => { onClick: () => {
this.removeLastItem(); this.addRandomItem(this._rightInv);
}, },
}); });
} }
private addRandomItem(): void { private addRandomItem(inv: InventorySignal): void {
const items = data.desert.getItems(); const items = data.desert.getItems();
this.inventorySignal.produce((inventory) => { inv.produce((inventory) => {
const usedIndices = new Set<number>(); const usedIndices = new Set<number>();
for (const item of inventory.items.values()) { for (const item of inventory.items.values()) {
@ -177,14 +201,4 @@ export class InventoryTestScene extends ReactiveScene {
createItemIn(inventory, id, itemData); createItemIn(inventory, id, itemData);
}); });
} }
private removeLastItem(): void {
this.inventorySignal.produce((inventory) => {
const ids = [...inventory.items.keys()];
const last = ids.length > 0 ? ids[ids.length - 1] : null;
console.log({ last });
if (!last) return;
removeItemFromGrid(inventory, last);
});
}
} }

View File

@ -45,8 +45,8 @@ export function sceneToInventory(
x: number, x: number,
y: number, y: number,
): { x: number; y: number } | null { ): { x: number; y: number } | null {
const invX = Math.round(x / surface.cellSize); const invX = Math.round((x - surface.gridOffsetX) / surface.cellSize);
const invY = Math.round(y / surface.cellSize); const invY = Math.round((y - surface.gridOffsetY) / surface.cellSize);
const { width, height } = surface.invSignal.peek(); const { width, height } = surface.invSignal.peek();
if (invX < 0 || invY < 0 || invX >= width || invY >= height) { if (invX < 0 || invY < 0 || invX >= width || invY >= height) {