fix: inventory preview not actually there

This commit is contained in:
hypercross 2026-04-14 15:45:34 +08:00
parent 5342f1c09d
commit 706de4e33a
1 changed files with 168 additions and 222 deletions

View File

@ -13,24 +13,16 @@ import {
/**
*
*
* gameState
*
* encounter.type
* - MapNodeType.Minion / Elite CombatEncounterScene
* - MapNodeType.Shop ShopEncounterScene
* - MapNodeType.Camp CampEncounterScene
* - MapNodeType.Event EventEncounterScene
* - MapNodeType.Curio CurioEncounterScene
* 80x80
*/
export class PlaceholderEncounterScene extends ReactiveScene {
/** 全局游戏状态(由 App.tsx 注入) */
private gameState: MutableSignal<RunState>;
// Grid constants
private readonly CELL_SIZE = 80;
private readonly GRID_PADDING = 40;
private gridOffsetX = 0;
private gridOffsetY = 0;
private readonly GRID_GAP = 2;
private gridX = 0;
private gridY = 0;
constructor(gameState: MutableSignal<RunState>) {
super('PlaceholderEncounterScene');
@ -42,175 +34,174 @@ export class PlaceholderEncounterScene extends ReactiveScene {
const { width, height } = this.scale;
const state = this.gameState.value;
// Calculate grid position (left side, vertically centered)
this.gridOffsetX = this.GRID_PADDING;
const gridHeight = 4 * this.CELL_SIZE;
this.gridOffsetY = (height - gridHeight) / 2;
// ── Layout: split screen into left (grid) and right (encounter) ──
const gridCols = state.inventory.width; // 6
const gridRows = state.inventory.height; // 4
const gridW = gridCols * this.CELL_SIZE;
const gridH = gridRows * this.CELL_SIZE;
const leftPanelW = gridW + 40; // panel padding
// Draw inventory grid
this.drawInventoryGrid();
this.gridX = 60;
this.gridY = (height - gridH) / 2 + 20;
// Read encounter data from global state
// Ensure camera shows the full grid
this.cameras.main.setBounds(0, 0, width, height);
this.cameras.main.setScroll(0, 0);
// ── LEFT PANEL: inventory grid ──
this.drawLeftPanel(leftPanelW, gridW, gridH);
// ── RIGHT PANEL: encounter info ──
const node = state.map.nodes.get(state.currentNodeId);
if (!node || !node.encounter) {
this.add.text(width / 2, height / 2, '没有遭遇数据', {
fontSize: '24px',
color: '#ff4444',
const rightX = leftPanelW + 80;
this.add.text(rightX + 300, height / 2, '没有遭遇数据', {
fontSize: '24px', color: '#ff4444',
}).setOrigin(0.5);
return;
}
const encounter = {
type: node.type,
name: node.encounter.name,
description: node.encounter.description,
};
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
this.add.text(centerX, centerY - 150, '遭遇', {
fontSize: '32px',
color: '#ffffff',
fontStyle: 'bold',
}).setOrigin(0.5);
// Encounter type badge
const typeLabel = this.getEncounterTypeLabel(encounter.type);
this.add.rectangle(centerX, centerY - 80, 120, 36, this.getEncounterTypeColor(encounter.type));
this.add.text(centerX, centerY - 80, typeLabel, {
fontSize: '16px',
color: '#ffffff',
fontStyle: 'bold',
}).setOrigin(0.5);
// Encounter name
this.add.text(centerX, centerY - 30, encounter.name, {
fontSize: '24px',
color: '#ffffff',
}).setOrigin(0.5);
// Encounter description
this.add.text(centerX, centerY + 20, encounter.description || '(暂无描述)', {
fontSize: '16px',
color: '#aaaaaa',
wordWrap: { width: 500 },
align: 'center',
}).setOrigin(0.5);
// Node ID info
this.add.text(centerX, centerY + 80, `节点: ${nodeId}`, {
fontSize: '12px',
color: '#666666',
}).setOrigin(0.5);
// Placeholder notice
this.add.text(centerX, centerY + 130, '(此为占位符遭遇,后续将替换为真实遭遇场景)', {
fontSize: '14px',
color: '#ff8844',
fontStyle: 'italic',
}).setOrigin(0.5);
// Complete button
this.createButton('完成遭遇', centerX, centerY + 200, 200, 50, async () => {
await this.completeEncounter();
});
// Back button (without resolving)
this.createButton('暂不处理', centerX, centerY + 270, 200, 40, async () => {
await this.sceneController.launch('GameFlowScene');
});
this.drawRightPanel(node, leftPanelW, width, height);
}
private drawInventoryGrid(): void {
const state = this.gameState.value;
const { width, height } = state.inventory;
// ───────────────────── LEFT PANEL ─────────────────────
// 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;
private drawLeftPanel(panelW: number, gridW: number, gridH: number): void {
// Panel background
this.add.rectangle(
this.gridX + panelW / 2, this.gridY + gridH / 2,
panelW + 10, gridH + 50,
0x111122, 0.9
).setStrokeStyle(2, 0x5555aa);
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',
// "背包" title
this.add.text(this.gridX + gridW / 2, this.gridY - 20, '背包', {
fontSize: '22px', 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);
// Draw empty cell backgrounds
for (let y = 0; y < 4; y++) {
for (let x = 0; x < 6; x++) {
const px = this.gridX + x * this.CELL_SIZE;
const py = this.gridY + y * this.CELL_SIZE;
// Dark cell fill
graphics.fillStyle(0x1a1a2e);
graphics.fillRect(px + 1, py + 1, this.CELL_SIZE - 2, this.CELL_SIZE - 2);
// Cell border
graphics.lineStyle(2, 0x444477);
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++;
// Draw items on top
this.drawItems(graphics);
}
const itemColor = itemColorMap.get(itemId)!;
// Get occupied cells for this item
private drawItems(graphics: Phaser.GameObjects.Graphics): void {
const state = this.gameState.value;
const palette = [0x3388ff, 0xff8833, 0x33ff88, 0xff3388, 0x8833ff, 0x33ffff, 0xffff33, 0xff6633];
const colorMap = new Map<string, number>();
let idx = 0;
for (const [id, item] of state.inventory.items) {
const color = colorMap.get(id) ?? palette[idx++ % palette.length];
colorMap.set(id, color);
const cells = this.getItemCells(item);
if (cells.length === 0) continue;
// 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;
// Filled cells
for (const c of cells) {
const px = this.gridX + c.x * this.CELL_SIZE;
const py = this.gridY + c.y * this.CELL_SIZE;
graphics.fillStyle(itemColor);
graphics.fillStyle(color);
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;
// Item name
const first = cells[0];
const name = item.meta?.itemData?.name ?? item.id;
this.add.text(
this.gridX + first.x * this.CELL_SIZE + this.CELL_SIZE / 2,
this.gridY + first.y * this.CELL_SIZE + this.CELL_SIZE / 2,
name, { fontSize: '12px', color: '#fff', fontStyle: 'bold' }
).setOrigin(0.5);
}
}
this.add.text(px + this.CELL_SIZE / 2, py + this.CELL_SIZE / 2, itemName, {
fontSize: '12px',
color: '#ffffff',
fontStyle: 'bold',
// ───────────────────── RIGHT PANEL ─────────────────────
private drawRightPanel(node: any, leftPanelW: number, width: number, height: number): void {
const encounter = {
type: node.type as MapNodeType,
name: node.encounter.name as string,
description: node.encounter.description as string,
};
const nodeId = node.id as string;
const rightX = leftPanelW + 60;
const rightW = width - rightX - 40;
const cx = rightX + rightW / 2;
const cy = height / 2;
// Title
this.add.text(cx, cy - 180, '遭遇', {
fontSize: '36px', color: '#fff', fontStyle: 'bold',
}).setOrigin(0.5);
// Type badge
const typeLabel = this.getTypeLabel(encounter.type);
const badgeColor = this.getTypeColor(encounter.type);
this.add.rectangle(cx, cy - 110, 140, 40, badgeColor);
this.add.text(cx, cy - 110, typeLabel, {
fontSize: '18px', color: '#fff', fontStyle: 'bold',
}).setOrigin(0.5);
// Name
this.add.text(cx, cy - 50, encounter.name, {
fontSize: '28px', color: '#fff',
}).setOrigin(0.5);
// Description
this.add.text(cx, cy + 10, encounter.description || '(暂无描述)', {
fontSize: '18px', color: '#bbb',
wordWrap: { width: rightW - 40 }, align: 'center',
}).setOrigin(0.5);
// Node id
this.add.text(cx, cy + 80, `节点: ${nodeId}`, {
fontSize: '14px', color: '#666',
}).setOrigin(0.5);
// Placeholder notice
this.add.text(cx, cy + 130, '(此为占位符遭遇,后续将替换为真实遭遇场景)', {
fontSize: '14px', color: '#ff8844', fontStyle: 'italic',
}).setOrigin(0.5);
// Buttons
this.createButton('完成遭遇', cx, cy + 200, 220, 50, async () => {
await this.completeEncounter();
});
this.createButton('暂不处理', cx, cy + 270, 220, 40, async () => {
await this.sceneController.launch('GameFlowScene');
});
}
}
}
// ───────────────────── Helpers ─────────────────────
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]) {
for (let y = 0; y < item.shape.height; y++) {
for (let x = 0; x < item.shape.width; x++) {
if (item.shape.grid[y]?.[x]) {
cells.push({ x: x + offset.x, y: y + offset.y });
}
}
@ -218,96 +209,51 @@ export class PlaceholderEncounterScene extends ReactiveScene {
return cells;
}
private getTypeLabel(type: MapNodeType): string {
const m: Record<MapNodeType, string> = {
start: '起点', end: '终点', minion: '战斗', elite: '精英战斗',
event: '事件', camp: '营地', shop: '商店', curio: '奇遇',
};
return m[type] ?? type;
}
private getTypeColor(type: MapNodeType): number {
const m: Record<MapNodeType, number> = {
start: 0x44aa44, end: 0xcc8844, minion: 0xcc4444, elite: 0xcc44cc,
event: 0xaaaa44, camp: 0x44cccc, shop: 0x4488cc, curio: 0x8844cc,
};
return m[type] ?? 0x888888;
}
private createButton(label: string, x: number, y: number, w: number, h: number, onClick: () => void): void {
const bg = this.add.rectangle(x, y, w, h, 0x444466)
.setStrokeStyle(2, 0x7777aa).setInteractive({ useHandCursor: true });
const txt = this.add.text(x, y, label, { fontSize: '16px', color: '#fff' }).setOrigin(0.5);
bg.on('pointerover', () => { bg.setFillStyle(0x555588); txt.setScale(1.05); });
bg.on('pointerout', () => { bg.setFillStyle(0x444466); txt.setScale(1); });
bg.on('pointerdown', onClick);
}
private async completeEncounter(): Promise<void> {
const state = this.gameState.value;
// Get current encounter info
const node = state.map.nodes.get(state.currentNodeId);
if (!node || !node.encounter) return;
// 生成模拟遭遇结果
const result: EncounterResult = this.generatePlaceholderResult(node.type);
// 调用进度管理器结算遭遇
resolveEncounter(state, result);
// 返回游戏流程场景
await this.sceneController.launch('GameFlowScene');
}
private generatePlaceholderResult(type: MapNodeType): EncounterResult {
// 根据遭遇类型生成不同的模拟结果
switch (type) {
case 'minion':
case 'elite':
return { hpLost: type === 'elite' ? 15 : 8, goldEarned: type === 'elite' ? 30 : 15 };
case 'camp':
return { hpGained: 15 };
case 'shop':
return { goldEarned: 0 };
case 'minion': return { hpLost: 8, goldEarned: 15 };
case 'elite': return { hpLost: 15, goldEarned: 30 };
case 'camp': return { hpGained: 15 };
case 'shop': return { goldEarned: 0 };
case 'curio':
case 'event':
return { goldEarned: 20 };
default:
return {};
case 'event': return { goldEarned: 20 };
default: return {};
}
}
private getEncounterTypeLabel(type: MapNodeType): string {
const labels: Record<MapNodeType, string> = {
start: '起点',
end: '终点',
minion: '战斗',
elite: '精英战斗',
event: '事件',
camp: '营地',
shop: '商店',
curio: '奇遇',
};
return labels[type] ?? type;
}
private getEncounterTypeColor(type: MapNodeType): number {
const colors: Record<MapNodeType, number> = {
start: 0x44aa44,
end: 0xcc8844,
minion: 0xcc4444,
elite: 0xcc44cc,
event: 0xaaaa44,
camp: 0x44cccc,
shop: 0x4488cc,
curio: 0x8844cc,
};
return colors[type] ?? 0x888888;
}
private createButton(
label: string,
x: number,
y: number,
width: number,
height: number,
onClick: () => void
): void {
const bg = this.add.rectangle(x, y, width, height, 0x444466)
.setStrokeStyle(2, 0x7777aa)
.setInteractive({ useHandCursor: true });
const text = this.add.text(x, y, label, {
fontSize: '16px',
color: '#ffffff',
}).setOrigin(0.5);
bg.on('pointerover', () => {
bg.setFillStyle(0x555588);
text.setScale(1.05);
});
bg.on('pointerout', () => {
bg.setFillStyle(0x444466);
text.setScale(1);
});
bg.on('pointerdown', onClick);
}
}