refactor: global state for scenes
This commit is contained in:
@@ -1,18 +1,16 @@
|
||||
import Phaser from 'phaser';
|
||||
import { ReactiveScene } from 'boardgame-phaser';
|
||||
import { MutableSignal } from 'boardgame-core';
|
||||
import {
|
||||
createRunState,
|
||||
canMoveTo,
|
||||
moveToNode,
|
||||
getCurrentNode,
|
||||
getReachableChildren,
|
||||
isAtEndNode,
|
||||
isAtStartNode,
|
||||
type RunState,
|
||||
type MapNode,
|
||||
type EncounterResult,
|
||||
type EncounterState,
|
||||
} from 'boardgame-core/samples/slay-the-spire-like';
|
||||
import { PlaceholderEncounterScene, type EncounterData } from './PlaceholderEncounterScene';
|
||||
|
||||
const NODE_COLORS: Record<string, number> = {
|
||||
start: 0x44aa44,
|
||||
@@ -37,8 +35,8 @@ const NODE_LABELS: Record<string, string> = {
|
||||
};
|
||||
|
||||
export class GameFlowScene extends ReactiveScene {
|
||||
private runState: RunState;
|
||||
private seed: number;
|
||||
/** 全局游戏状态(由 App.tsx 注入) */
|
||||
private gameState: MutableSignal<RunState>;
|
||||
|
||||
// Layout constants
|
||||
private readonly LAYER_HEIGHT = 110;
|
||||
@@ -63,10 +61,9 @@ export class GameFlowScene extends ReactiveScene {
|
||||
private hoveredNode: string | null = null;
|
||||
private nodeGraphics: Map<string, Phaser.GameObjects.Graphics> = new Map();
|
||||
|
||||
constructor() {
|
||||
constructor(gameState: MutableSignal<RunState>) {
|
||||
super('GameFlowScene');
|
||||
this.seed = Date.now();
|
||||
this.runState = createRunState(this.seed);
|
||||
this.gameState = gameState;
|
||||
}
|
||||
|
||||
create(): void {
|
||||
@@ -114,7 +111,8 @@ export class GameFlowScene extends ReactiveScene {
|
||||
}
|
||||
|
||||
private updateHUD(): void {
|
||||
const { player, currentNodeId, map } = this.runState;
|
||||
const state = this.gameState.value;
|
||||
const { player, currentNodeId, map } = state;
|
||||
const currentNode = map.nodes.get(currentNodeId);
|
||||
|
||||
this.hpText.setText(`HP: ${player.currentHp}/${player.maxHp}`);
|
||||
@@ -129,12 +127,13 @@ export class GameFlowScene extends ReactiveScene {
|
||||
|
||||
private drawMap(): void {
|
||||
const { width, height } = this.scale;
|
||||
const state = this.gameState.value;
|
||||
|
||||
// Calculate map bounds
|
||||
// Calculate map bounds (left-to-right: layers along X, nodes along Y)
|
||||
const maxLayer = 9;
|
||||
const maxNodesInLayer = 5;
|
||||
const mapWidth = (maxNodesInLayer - 1) * this.NODE_SPACING + 200;
|
||||
const mapHeight = maxLayer * this.LAYER_HEIGHT + 200;
|
||||
const mapWidth = maxLayer * this.LAYER_HEIGHT + 200;
|
||||
const mapHeight = (maxNodesInLayer - 1) * this.NODE_SPACING + 200;
|
||||
|
||||
// Create scrollable container
|
||||
this.mapContainer = this.add.container(width / 2, height / 2 + 50);
|
||||
@@ -146,8 +145,8 @@ export class GameFlowScene extends ReactiveScene {
|
||||
const graphics = this.add.graphics();
|
||||
this.mapContainer.add(graphics);
|
||||
|
||||
const { map, currentNodeId } = this.runState;
|
||||
const reachableChildren = getReachableChildren(this.runState);
|
||||
const { map, currentNodeId } = state;
|
||||
const reachableChildren = getReachableChildren(state);
|
||||
const reachableIds = new Set(reachableChildren.map(n => n.id));
|
||||
|
||||
// Draw edges
|
||||
@@ -212,10 +211,11 @@ export class GameFlowScene extends ReactiveScene {
|
||||
);
|
||||
}
|
||||
|
||||
// Make reachable nodes interactive
|
||||
// Make reachable nodes interactive (add hitZone to mapContainer so positions match)
|
||||
if (isReachable) {
|
||||
const hitZone = this.add.circle(posX, posY, this.NODE_RADIUS, 0x000000, 0)
|
||||
.setInteractive({ useHandCursor: true });
|
||||
this.mapContainer.add(hitZone);
|
||||
|
||||
hitZone.on('pointerover', () => {
|
||||
this.hoveredNode = nodeId;
|
||||
@@ -272,12 +272,13 @@ export class GameFlowScene extends ReactiveScene {
|
||||
}
|
||||
|
||||
private async onNodeClick(nodeId: string): Promise<void> {
|
||||
if (!canMoveTo(this.runState, nodeId)) {
|
||||
const state = this.gameState.value;
|
||||
if (!canMoveTo(state, nodeId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to target node
|
||||
const result = moveToNode(this.runState, nodeId);
|
||||
const result = moveToNode(state, nodeId);
|
||||
if (!result.success) {
|
||||
console.warn(`无法移动到节点: ${result.reason}`);
|
||||
return;
|
||||
@@ -288,50 +289,25 @@ export class GameFlowScene extends ReactiveScene {
|
||||
this.redrawMapHighlights();
|
||||
|
||||
// Check if at end node
|
||||
if (isAtEndNode(this.runState)) {
|
||||
if (isAtEndNode(state)) {
|
||||
this.showEndScreen();
|
||||
return;
|
||||
}
|
||||
|
||||
// Launch encounter scene
|
||||
const currentNode = getCurrentNode(this.runState);
|
||||
const currentNode = getCurrentNode(state);
|
||||
if (!currentNode || !currentNode.encounter) {
|
||||
console.warn('当前节点没有遭遇数据');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create encounter data
|
||||
const encounterData: EncounterData = {
|
||||
runState: this.runState,
|
||||
nodeId: currentNode.id,
|
||||
encounter: {
|
||||
type: currentNode.type,
|
||||
name: currentNode.encounter.name,
|
||||
description: currentNode.encounter.description,
|
||||
},
|
||||
onComplete: (result: EncounterResult) => {
|
||||
// Encounter completed, update HUD
|
||||
this.updateHUD();
|
||||
this.redrawMapHighlights();
|
||||
},
|
||||
};
|
||||
|
||||
// Re-add encounter scene with new data
|
||||
const phaserGame = this.phaserGame.value.game;
|
||||
const encounterScene = new PlaceholderEncounterScene();
|
||||
if (!phaserGame.scene.getScene('PlaceholderEncounterScene')) {
|
||||
phaserGame.scene.add('PlaceholderEncounterScene', encounterScene, false, {
|
||||
...encounterData,
|
||||
phaserGame: this.phaserGame,
|
||||
sceneController: this.sceneController,
|
||||
});
|
||||
}
|
||||
|
||||
await this.sceneController.launch('PlaceholderEncounterScene');
|
||||
}
|
||||
|
||||
private redrawMapHighlights(): void {
|
||||
const { map, currentNodeId } = this.runState;
|
||||
const reachableChildren = getReachableChildren(this.runState);
|
||||
const state = this.gameState.value;
|
||||
const { map, currentNodeId } = state;
|
||||
const reachableChildren = getReachableChildren(state);
|
||||
const reachableIds = new Set(reachableChildren.map(n => n.id));
|
||||
|
||||
for (const [nodeId, nodeGraphics] of this.nodeGraphics) {
|
||||
@@ -360,6 +336,7 @@ export class GameFlowScene extends ReactiveScene {
|
||||
|
||||
private showEndScreen(): void {
|
||||
const { width, height } = this.scale;
|
||||
const state = this.gameState.value;
|
||||
|
||||
// Overlay
|
||||
const overlay = this.add.rectangle(width / 2, height / 2, width, height, 0x000000, 0.7).setDepth(300);
|
||||
@@ -371,7 +348,7 @@ export class GameFlowScene extends ReactiveScene {
|
||||
fontStyle: 'bold',
|
||||
}).setOrigin(0.5).setDepth(300);
|
||||
|
||||
const { player } = this.runState;
|
||||
const { player } = state;
|
||||
this.add.text(width / 2, height / 2 + 20, `剩余 HP: ${player.currentHp}/${player.maxHp}\n剩余金币: ${player.gold}`, {
|
||||
fontSize: '20px',
|
||||
color: '#ffffff',
|
||||
@@ -384,15 +361,18 @@ export class GameFlowScene extends ReactiveScene {
|
||||
}
|
||||
|
||||
private getNodeX(node: MapNode): number {
|
||||
const layer = this.runState.map.layers[node.layerIndex];
|
||||
const nodeIndex = layer.nodeIds.indexOf(node.id);
|
||||
const totalNodes = layer.nodeIds.length;
|
||||
const layerWidth = (totalNodes - 1) * this.NODE_SPACING;
|
||||
return -layerWidth / 2 + nodeIndex * this.NODE_SPACING;
|
||||
// Layers go left-to-right along X axis
|
||||
return -500 + node.layerIndex * this.LAYER_HEIGHT;
|
||||
}
|
||||
|
||||
private getNodeY(node: MapNode): number {
|
||||
return -600 + node.layerIndex * this.LAYER_HEIGHT;
|
||||
// Nodes within a layer are spread vertically along Y axis
|
||||
const state = this.gameState.value;
|
||||
const layer = state.map.layers[node.layerIndex];
|
||||
const nodeIndex = layer.nodeIds.indexOf(node.id);
|
||||
const totalNodes = layer.nodeIds.length;
|
||||
const layerHeight = (totalNodes - 1) * this.NODE_SPACING;
|
||||
return -layerHeight / 2 + nodeIndex * this.NODE_SPACING;
|
||||
}
|
||||
|
||||
private brightenColor(color: number): number {
|
||||
|
||||
Reference in New Issue
Block a user