feat: inventory preview?

This commit is contained in:
hypercross 2026-04-14 15:22:22 +08:00
parent 3ca2a16e29
commit 5342f1c09d
1 changed files with 118 additions and 7 deletions

View File

@ -6,12 +6,14 @@ import {
type RunState, type RunState,
type EncounterResult, type EncounterResult,
type MapNodeType, type MapNodeType,
type InventoryItem,
type GameItemMeta,
} from 'boardgame-core/samples/slay-the-spire-like'; } from 'boardgame-core/samples/slay-the-spire-like';
/** /**
* *
* *
* gameState * gameState
* *
* encounter.type * encounter.type
* - MapNodeType.Minion / Elite CombatEncounterScene * - MapNodeType.Minion / Elite CombatEncounterScene
@ -24,6 +26,12 @@ export class PlaceholderEncounterScene extends ReactiveScene {
/** 全局游戏状态(由 App.tsx 注入) */ /** 全局游戏状态(由 App.tsx 注入) */
private gameState: MutableSignal<RunState>; private gameState: MutableSignal<RunState>;
// Grid constants
private readonly CELL_SIZE = 80;
private readonly GRID_PADDING = 40;
private gridOffsetX = 0;
private gridOffsetY = 0;
constructor(gameState: MutableSignal<RunState>) { constructor(gameState: MutableSignal<RunState>) {
super('PlaceholderEncounterScene'); super('PlaceholderEncounterScene');
this.gameState = gameState; this.gameState = gameState;
@ -32,14 +40,20 @@ export class PlaceholderEncounterScene extends ReactiveScene {
create(): void { create(): void {
super.create(); super.create();
const { width, height } = this.scale; const { width, height } = this.scale;
const centerX = width / 2; const state = this.gameState.value;
const centerY = height / 2;
// Calculate grid position (left side, vertically centered)
this.gridOffsetX = this.GRID_PADDING;
const gridHeight = 4 * this.CELL_SIZE;
this.gridOffsetY = (height - gridHeight) / 2;
// Draw inventory grid
this.drawInventoryGrid();
// Read encounter data from global state // Read encounter data from global state
const state = this.gameState.value;
const node = state.map.nodes.get(state.currentNodeId); const node = state.map.nodes.get(state.currentNodeId);
if (!node || !node.encounter) { if (!node || !node.encounter) {
this.add.text(centerX, centerY, '没有遭遇数据', { this.add.text(width / 2, height / 2, '没有遭遇数据', {
fontSize: '24px', fontSize: '24px',
color: '#ff4444', color: '#ff4444',
}).setOrigin(0.5); }).setOrigin(0.5);
@ -53,6 +67,11 @@ export class PlaceholderEncounterScene extends ReactiveScene {
}; };
const nodeId = node.id; const nodeId = node.id;
// Right side panel for encounter info
const rightPanelX = this.gridOffsetX + state.inventory.width * this.CELL_SIZE + 60;
const centerX = rightPanelX + 300;
const centerY = height / 2;
// Title // Title
this.add.text(centerX, centerY - 150, '遭遇', { this.add.text(centerX, centerY - 150, '遭遇', {
fontSize: '32px', fontSize: '32px',
@ -62,7 +81,7 @@ export class PlaceholderEncounterScene extends ReactiveScene {
// Encounter type badge // Encounter type badge
const typeLabel = this.getEncounterTypeLabel(encounter.type); const typeLabel = this.getEncounterTypeLabel(encounter.type);
const typeBg = this.add.rectangle(centerX, centerY - 80, 120, 36, this.getEncounterTypeColor(encounter.type)); this.add.rectangle(centerX, centerY - 80, 120, 36, this.getEncounterTypeColor(encounter.type));
this.add.text(centerX, centerY - 80, typeLabel, { this.add.text(centerX, centerY - 80, typeLabel, {
fontSize: '16px', fontSize: '16px',
color: '#ffffff', color: '#ffffff',
@ -79,7 +98,7 @@ export class PlaceholderEncounterScene extends ReactiveScene {
this.add.text(centerX, centerY + 20, encounter.description || '(暂无描述)', { this.add.text(centerX, centerY + 20, encounter.description || '(暂无描述)', {
fontSize: '16px', fontSize: '16px',
color: '#aaaaaa', color: '#aaaaaa',
wordWrap: { width: 600 }, wordWrap: { width: 500 },
align: 'center', align: 'center',
}).setOrigin(0.5); }).setOrigin(0.5);
@ -107,6 +126,98 @@ export class PlaceholderEncounterScene extends ReactiveScene {
}); });
} }
private drawInventoryGrid(): void {
const state = this.gameState.value;
const { width, height } = state.inventory;
// Background panel for the grid area
const panelWidth = width * this.CELL_SIZE + 20;
const panelHeight = height * this.CELL_SIZE + 60;
const panelX = this.gridOffsetX - 10;
const panelY = this.gridOffsetY - 40;
this.add.rectangle(panelX + panelWidth / 2, panelY + panelHeight / 2, panelWidth, panelHeight, 0x1a1a2e)
.setStrokeStyle(2, 0x333355);
// Title
this.add.text(this.gridOffsetX + (width * this.CELL_SIZE) / 2, panelY + 10, '背包', {
fontSize: '18px',
color: '#ffffff',
fontStyle: 'bold',
}).setOrigin(0.5);
const gridY = this.gridOffsetY + 15;
// Draw empty cells
const graphics = this.add.graphics();
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const px = this.gridOffsetX + x * this.CELL_SIZE;
const py = gridY + y * this.CELL_SIZE;
graphics.lineStyle(1, 0x444466);
graphics.strokeRect(px, py, this.CELL_SIZE, this.CELL_SIZE);
}
}
// Draw items
const itemColors = [0x4488cc, 0xcc8844, 0x44cc88, 0xcc4488, 0x8844cc, 0x44cccc, 0xcccc44, 0x8888cc];
let colorIndex = 0;
const itemColorMap = new Map<string, number>();
for (const [itemId, item] of state.inventory.items) {
// Assign a color to this item
if (!itemColorMap.has(itemId)) {
itemColorMap.set(itemId, itemColors[colorIndex % itemColors.length]);
colorIndex++;
}
const itemColor = itemColorMap.get(itemId)!;
// Get occupied cells for this item
const cells = this.getItemCells(item);
// Draw filled cells
for (const cell of cells) {
const px = this.gridOffsetX + cell.x * this.CELL_SIZE;
const py = gridY + cell.y * this.CELL_SIZE;
graphics.fillStyle(itemColor);
graphics.fillRect(px + 2, py + 2, this.CELL_SIZE - 4, this.CELL_SIZE - 4);
graphics.lineStyle(2, 0xffffff);
graphics.strokeRect(px, py, this.CELL_SIZE, this.CELL_SIZE);
}
// Item name label (at first cell)
if (cells.length > 0) {
const firstCell = cells[0];
const px = this.gridOffsetX + firstCell.x * this.CELL_SIZE;
const py = gridY + firstCell.y * this.CELL_SIZE;
const itemName = item.meta?.itemData?.name ?? item.id;
this.add.text(px + this.CELL_SIZE / 2, py + this.CELL_SIZE / 2, itemName, {
fontSize: '12px',
color: '#ffffff',
fontStyle: 'bold',
}).setOrigin(0.5);
}
}
}
private getItemCells(item: InventoryItem<GameItemMeta>): { x: number; y: number }[] {
const cells: { x: number; y: number }[] = [];
const shape = item.shape;
const { offset } = item.transform;
for (let y = 0; y < shape.height; y++) {
for (let x = 0; x < shape.width; x++) {
if (shape.grid[y]?.[x]) {
cells.push({ x: x + offset.x, y: y + offset.y });
}
}
}
return cells;
}
private async completeEncounter(): Promise<void> { private async completeEncounter(): Promise<void> {
const state = this.gameState.value; const state = this.gameState.value;