refactor: viewr
This commit is contained in:
parent
e91e8d2ab7
commit
c639218b53
|
|
@ -27,9 +27,25 @@ export class MapViewerScene extends ReactiveScene {
|
||||||
private seed: number = Date.now();
|
private seed: number = Date.now();
|
||||||
|
|
||||||
// Layout constants
|
// Layout constants
|
||||||
private readonly LAYER_HEIGHT = 100;
|
private readonly LAYER_HEIGHT = 110;
|
||||||
private readonly NODE_SPACING = 120;
|
private readonly NODE_SPACING = 140;
|
||||||
private readonly NODE_RADIUS = 25;
|
private readonly NODE_RADIUS = 28;
|
||||||
|
|
||||||
|
// Scroll state
|
||||||
|
private mapContainer!: Phaser.GameObjects.Container;
|
||||||
|
private isDragging = false;
|
||||||
|
private dragStartX = 0;
|
||||||
|
private dragStartContainerX = 0;
|
||||||
|
private dragStartY = 0;
|
||||||
|
private dragStartContainerY = 0;
|
||||||
|
|
||||||
|
// Fixed UI (always visible, not scrolled)
|
||||||
|
private titleText!: Phaser.GameObjects.Text;
|
||||||
|
private backButtonBg!: Phaser.GameObjects.Rectangle;
|
||||||
|
private backButtonText!: Phaser.GameObjects.Text;
|
||||||
|
private regenButtonBg!: Phaser.GameObjects.Rectangle;
|
||||||
|
private regenButtonText!: Phaser.GameObjects.Text;
|
||||||
|
private legendContainer!: Phaser.GameObjects.Container;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super('MapViewerScene');
|
super('MapViewerScene');
|
||||||
|
|
@ -37,28 +53,129 @@ export class MapViewerScene extends ReactiveScene {
|
||||||
|
|
||||||
create(): void {
|
create(): void {
|
||||||
super.create();
|
super.create();
|
||||||
|
this.drawFixedUI();
|
||||||
this.drawMap();
|
this.drawMap();
|
||||||
this.createControls();
|
}
|
||||||
|
|
||||||
|
private drawFixedUI(): void {
|
||||||
|
const { width } = this.scale;
|
||||||
|
|
||||||
|
// Title
|
||||||
|
this.titleText = this.add.text(width / 2, 30, '', {
|
||||||
|
fontSize: '24px',
|
||||||
|
color: '#ffffff',
|
||||||
|
fontStyle: 'bold',
|
||||||
|
}).setOrigin(0.5).setDepth(100);
|
||||||
|
|
||||||
|
// Back button
|
||||||
|
this.backButtonBg = this.add.rectangle(100, 40, 140, 36, 0x444466)
|
||||||
|
.setStrokeStyle(2, 0x7777aa)
|
||||||
|
.setInteractive({ useHandCursor: true })
|
||||||
|
.setDepth(100);
|
||||||
|
this.backButtonText = this.add.text(100, 40, '返回菜单', {
|
||||||
|
fontSize: '16px',
|
||||||
|
color: '#ffffff',
|
||||||
|
}).setOrigin(0.5).setDepth(100);
|
||||||
|
|
||||||
|
this.backButtonBg.on('pointerover', () => {
|
||||||
|
this.backButtonBg.setFillStyle(0x555588);
|
||||||
|
this.backButtonText.setScale(1.05);
|
||||||
|
});
|
||||||
|
this.backButtonBg.on('pointerout', () => {
|
||||||
|
this.backButtonBg.setFillStyle(0x444466);
|
||||||
|
this.backButtonText.setScale(1);
|
||||||
|
});
|
||||||
|
this.backButtonBg.on('pointerdown', () => {
|
||||||
|
this.scene.start('IndexScene');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Regenerate button
|
||||||
|
this.regenButtonBg = this.add.rectangle(width - 120, 40, 140, 36, 0x444466)
|
||||||
|
.setStrokeStyle(2, 0x7777aa)
|
||||||
|
.setInteractive({ useHandCursor: true })
|
||||||
|
.setDepth(100);
|
||||||
|
this.regenButtonText = this.add.text(width - 120, 40, '重新生成', {
|
||||||
|
fontSize: '16px',
|
||||||
|
color: '#ffffff',
|
||||||
|
}).setOrigin(0.5).setDepth(100);
|
||||||
|
|
||||||
|
this.regenButtonBg.on('pointerover', () => {
|
||||||
|
this.regenButtonBg.setFillStyle(0x555588);
|
||||||
|
this.regenButtonText.setScale(1.05);
|
||||||
|
});
|
||||||
|
this.regenButtonBg.on('pointerout', () => {
|
||||||
|
this.regenButtonBg.setFillStyle(0x444466);
|
||||||
|
this.regenButtonText.setScale(1);
|
||||||
|
});
|
||||||
|
this.regenButtonBg.on('pointerdown', () => {
|
||||||
|
this.seed = Date.now();
|
||||||
|
this.mapContainer.destroy();
|
||||||
|
this.drawMap();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Legend (bottom-left, fixed)
|
||||||
|
this.legendContainer = this.add.container(20, this.scale.height - 200).setDepth(100);
|
||||||
|
const legendBg = this.add.rectangle(75, 90, 150, 180, 0x222222, 0.8);
|
||||||
|
this.legendContainer.add(legendBg);
|
||||||
|
|
||||||
|
this.legendContainer.add(
|
||||||
|
this.add.text(10, 5, '图例:', { fontSize: '14px', color: '#ffffff', fontStyle: 'bold' })
|
||||||
|
);
|
||||||
|
|
||||||
|
let offsetY = 30;
|
||||||
|
for (const [type, color] of Object.entries(NODE_COLORS)) {
|
||||||
|
this.legendContainer.add(
|
||||||
|
this.add.circle(20, offsetY, 8, color)
|
||||||
|
);
|
||||||
|
this.legendContainer.add(
|
||||||
|
this.add.text(40, offsetY - 5, NODE_LABELS[type as MapNodeType], { fontSize: '12px', color: '#ffffff' })
|
||||||
|
);
|
||||||
|
offsetY += 22;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hint text
|
||||||
|
this.add.text(width / 2, this.scale.height - 20, '拖拽滚动查看地图', {
|
||||||
|
fontSize: '14px',
|
||||||
|
color: '#888888',
|
||||||
|
}).setOrigin(0.5).setDepth(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
private drawMap(): void {
|
private drawMap(): void {
|
||||||
this.children.removeAll();
|
|
||||||
this.map = generatePointCrawlMap(this.seed);
|
this.map = generatePointCrawlMap(this.seed);
|
||||||
|
|
||||||
const { width, height } = this.scale;
|
const { width, height } = this.scale;
|
||||||
const graphics = this.add.graphics();
|
|
||||||
|
|
||||||
// Draw edges first
|
// Update title
|
||||||
|
this.titleText.setText(`Map Viewer (Seed: ${this.seed})`);
|
||||||
|
|
||||||
|
// Calculate map bounds
|
||||||
|
const maxLayer = 12; // TOTAL_LAYERS - 1
|
||||||
|
const maxNodesInLayer = 6; // widest layer
|
||||||
|
const mapWidth = (maxNodesInLayer - 1) * this.NODE_SPACING + 200;
|
||||||
|
const mapHeight = maxLayer * this.LAYER_HEIGHT + 200;
|
||||||
|
|
||||||
|
// Create scrollable container
|
||||||
|
this.mapContainer = this.add.container(width / 2, height / 2);
|
||||||
|
|
||||||
|
// Background panel for the map area
|
||||||
|
const bg = this.add.rectangle(0, 0, mapWidth, mapHeight, 0x111122, 0.5)
|
||||||
|
.setOrigin(0.5);
|
||||||
|
this.mapContainer.add(bg);
|
||||||
|
|
||||||
|
const graphics = this.add.graphics();
|
||||||
|
this.mapContainer.add(graphics);
|
||||||
|
|
||||||
|
// Draw edges
|
||||||
graphics.lineStyle(2, 0x666666);
|
graphics.lineStyle(2, 0x666666);
|
||||||
for (const [nodeId, node] of this.map.nodes) {
|
for (const [nodeId, node] of this.map.nodes) {
|
||||||
const posX = this.getNodeX(node, width);
|
const posX = this.getNodeX(node);
|
||||||
const posY = this.getNodeY(node, height);
|
const posY = this.getNodeY(node);
|
||||||
|
|
||||||
for (const childId of node.childIds) {
|
for (const childId of node.childIds) {
|
||||||
const child = this.map.nodes.get(childId);
|
const child = this.map.nodes.get(childId);
|
||||||
if (child) {
|
if (child) {
|
||||||
const childX = this.getNodeX(child, width);
|
const childX = this.getNodeX(child);
|
||||||
const childY = this.getNodeY(child, height);
|
const childY = this.getNodeY(child);
|
||||||
graphics.lineBetween(posX, posY, childX, childY);
|
graphics.lineBetween(posX, posY, childX, childY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -66,8 +183,8 @@ export class MapViewerScene extends ReactiveScene {
|
||||||
|
|
||||||
// Draw nodes
|
// Draw nodes
|
||||||
for (const [nodeId, node] of this.map.nodes) {
|
for (const [nodeId, node] of this.map.nodes) {
|
||||||
const posX = this.getNodeX(node, width);
|
const posX = this.getNodeX(node);
|
||||||
const posY = this.getNodeY(node, height);
|
const posY = this.getNodeY(node);
|
||||||
const color = NODE_COLORS[node.type as MapNodeType] ?? 0x888888;
|
const color = NODE_COLORS[node.type as MapNodeType] ?? 0x888888;
|
||||||
|
|
||||||
// Node circle
|
// Node circle
|
||||||
|
|
@ -78,107 +195,57 @@ export class MapViewerScene extends ReactiveScene {
|
||||||
|
|
||||||
// Node label
|
// Node label
|
||||||
const label = NODE_LABELS[node.type as MapNodeType] ?? node.type;
|
const label = NODE_LABELS[node.type as MapNodeType] ?? node.type;
|
||||||
this.add.text(posX, posY, label, {
|
this.mapContainer.add(
|
||||||
fontSize: '12px',
|
this.add.text(posX, posY, label, {
|
||||||
color: '#ffffff',
|
fontSize: '12px',
|
||||||
}).setOrigin(0.5);
|
color: '#ffffff',
|
||||||
|
}).setOrigin(0.5)
|
||||||
|
);
|
||||||
|
|
||||||
// Encounter name (if available)
|
// Encounter name (if available)
|
||||||
if ('encounter' in node && node.encounter) {
|
if ('encounter' in node && (node as any).encounter) {
|
||||||
this.add.text(posX, posY + this.NODE_RADIUS + 10, (node as any).encounter.name ?? '', {
|
this.mapContainer.add(
|
||||||
fontSize: '10px',
|
this.add.text(posX, posY + this.NODE_RADIUS + 12, (node as any).encounter.name ?? '', {
|
||||||
color: '#cccccc',
|
fontSize: '10px',
|
||||||
}).setOrigin(0.5);
|
color: '#cccccc',
|
||||||
|
}).setOrigin(0.5)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Title
|
// Setup drag-to-scroll
|
||||||
this.add.text(width / 2, 30, `Map Viewer (Seed: ${this.seed})`, {
|
this.input.on('pointerdown', (pointer: Phaser.Input.Pointer) => {
|
||||||
fontSize: '24px',
|
this.isDragging = true;
|
||||||
color: '#ffffff',
|
this.dragStartX = pointer.x;
|
||||||
fontStyle: 'bold',
|
this.dragStartY = pointer.y;
|
||||||
}).setOrigin(0.5);
|
this.dragStartContainerX = this.mapContainer.x;
|
||||||
|
this.dragStartContainerY = this.mapContainer.y;
|
||||||
|
});
|
||||||
|
|
||||||
// Legend
|
this.input.on('pointermove', (pointer: Phaser.Input.Pointer) => {
|
||||||
this.drawLegend(20, height - 200);
|
if (!this.isDragging) return;
|
||||||
|
this.mapContainer.x = this.dragStartContainerX + (pointer.x - this.dragStartX);
|
||||||
|
this.mapContainer.y = this.dragStartContainerY + (pointer.y - this.dragStartY);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.input.on('pointerup', () => {
|
||||||
|
this.isDragging = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.input.on('pointerout', () => {
|
||||||
|
this.isDragging = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getNodeX(node: any, sceneWidth: number): number {
|
private getNodeX(node: any): number {
|
||||||
const layer = this.map!.layers[node.layerIndex];
|
const layer = this.map!.layers[node.layerIndex];
|
||||||
const nodeIndex = layer.nodeIds.indexOf(node.id);
|
const nodeIndex = layer.nodeIds.indexOf(node.id);
|
||||||
const totalNodes = layer.nodeIds.length;
|
const totalNodes = layer.nodeIds.length;
|
||||||
const layerWidth = (totalNodes - 1) * this.NODE_SPACING;
|
const layerWidth = (totalNodes - 1) * this.NODE_SPACING;
|
||||||
const startX = sceneWidth / 2 - layerWidth / 2;
|
return -layerWidth / 2 + nodeIndex * this.NODE_SPACING;
|
||||||
return startX + nodeIndex * this.NODE_SPACING;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getNodeY(node: any, sceneHeight: number): number {
|
private getNodeY(node: any): number {
|
||||||
return 80 + node.layerIndex * this.LAYER_HEIGHT;
|
return -600 + node.layerIndex * this.LAYER_HEIGHT;
|
||||||
}
|
|
||||||
|
|
||||||
private drawLegend(x: number, y: number): void {
|
|
||||||
const graphics = this.add.graphics();
|
|
||||||
graphics.fillStyle(0x222222, 0.8);
|
|
||||||
graphics.fillRect(x, y, 150, 180);
|
|
||||||
|
|
||||||
let offsetY = y + 15;
|
|
||||||
this.add.text(x + 10, offsetY, '图例:', {
|
|
||||||
fontSize: '14px',
|
|
||||||
color: '#ffffff',
|
|
||||||
fontStyle: 'bold',
|
|
||||||
});
|
|
||||||
offsetY += 25;
|
|
||||||
|
|
||||||
for (const [type, color] of Object.entries(NODE_COLORS)) {
|
|
||||||
graphics.fillStyle(color);
|
|
||||||
graphics.fillCircle(x + 20, offsetY, 8);
|
|
||||||
this.add.text(x + 40, offsetY - 5, NODE_LABELS[type as MapNodeType], {
|
|
||||||
fontSize: '12px',
|
|
||||||
color: '#ffffff',
|
|
||||||
});
|
|
||||||
offsetY += 22;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private createControls(): void {
|
|
||||||
const { width, height } = this.scale;
|
|
||||||
|
|
||||||
// Back button
|
|
||||||
this.createButton('返回菜单', 100, 40, () => {
|
|
||||||
this.scene.start('IndexScene');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Regenerate button
|
|
||||||
this.createButton('重新生成', width - 120, 40, () => {
|
|
||||||
this.seed = Date.now();
|
|
||||||
this.drawMap();
|
|
||||||
this.createControls();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private createButton(label: string, x: number, y: number, onClick: () => void): void {
|
|
||||||
const buttonWidth = 140;
|
|
||||||
const buttonHeight = 36;
|
|
||||||
|
|
||||||
const bg = this.add.rectangle(x, y, buttonWidth, buttonHeight, 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export default function App() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-screen">
|
<div className="flex flex-col h-screen">
|
||||||
<div className="flex-1 flex relative justify-center items-center">
|
<div className="flex-1 flex relative justify-center items-center">
|
||||||
<PhaserGame initialScene="IndexScene" config={{ width: 960, height: 720 }}>
|
<PhaserGame initialScene="IndexScene" config={{ width: 1920, height: 1080 }}>
|
||||||
<PhaserScene sceneKey="IndexScene" scene={indexScene} />
|
<PhaserScene sceneKey="IndexScene" scene={indexScene} />
|
||||||
<PhaserScene sceneKey="MapViewerScene" scene={mapViewerScene} />
|
<PhaserScene sceneKey="MapViewerScene" scene={mapViewerScene} />
|
||||||
<PhaserScene sceneKey="GridViewerScene" scene={gridViewerScene} />
|
<PhaserScene sceneKey="GridViewerScene" scene={gridViewerScene} />
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue