✨ add F3 debug view (Issue #6)
F3 toggles a debug overlay with: - FPS - Mouse world/tile coordinates - Tile type under cursor - Resources, buildings, crops on hovered tile - Nisse count broken down by AI state (idle/walking/working/sleeping) - Active jobs by type (chop/mine/farm) - Pathfinding visualization: cyan lines + destination highlight drawn in world space via DebugSystem Added DebugSystem to GameScene. VillagerSystem exposes getActivePaths() for the path visualization. JSDoc added to all previously undocumented methods in VillagerSystem, WorldSystem, GameScene, and UIScene.
This commit is contained in:
164
src/systems/DebugSystem.ts
Normal file
164
src/systems/DebugSystem.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import Phaser from 'phaser'
|
||||
import { TILE_SIZE } from '../config'
|
||||
import { TileType } from '../types'
|
||||
import { stateManager } from '../StateManager'
|
||||
import type { VillagerSystem } from './VillagerSystem'
|
||||
import type { WorldSystem } from './WorldSystem'
|
||||
|
||||
/** All data collected each frame for the debug panel. */
|
||||
export interface DebugData {
|
||||
fps: number
|
||||
mouseWorld: { x: number; y: number }
|
||||
mouseTile: { tileX: number; tileY: number }
|
||||
tileType: string
|
||||
resourcesOnTile: Array<{ kind: string; hp: number }>
|
||||
buildingsOnTile: string[]
|
||||
cropsOnTile: Array<{ kind: string; stage: number; maxStage: number }>
|
||||
nisseTotal: number
|
||||
nisseByState: { idle: number; walking: number; working: number; sleeping: number }
|
||||
jobsByType: { chop: number; mine: number; farm: number }
|
||||
activePaths: number
|
||||
}
|
||||
|
||||
/** Human-readable names for TileType enum values. */
|
||||
const TILE_NAMES: Record<number, string> = {
|
||||
[TileType.DEEP_WATER]: 'DEEP_WATER',
|
||||
[TileType.SHALLOW_WATER]: 'SHALLOW_WATER',
|
||||
[TileType.SAND]: 'SAND',
|
||||
[TileType.GRASS]: 'GRASS',
|
||||
[TileType.DARK_GRASS]: 'DARK_GRASS',
|
||||
[TileType.FOREST]: 'FOREST',
|
||||
[TileType.ROCK]: 'ROCK',
|
||||
[TileType.FLOOR]: 'FLOOR',
|
||||
[TileType.WALL]: 'WALL',
|
||||
[TileType.TILLED_SOIL]: 'TILLED_SOIL',
|
||||
[TileType.WATERED_SOIL]: 'WATERED_SOIL',
|
||||
}
|
||||
|
||||
export class DebugSystem {
|
||||
private scene: Phaser.Scene
|
||||
private villagerSystem: VillagerSystem
|
||||
private worldSystem: WorldSystem
|
||||
private pathGraphics!: Phaser.GameObjects.Graphics
|
||||
private active = false
|
||||
|
||||
/**
|
||||
* @param scene - The Phaser scene this system belongs to
|
||||
* @param villagerSystem - Used to read active paths for visualization
|
||||
* @param worldSystem - Used to read tile types under the mouse
|
||||
*/
|
||||
constructor(scene: Phaser.Scene, villagerSystem: VillagerSystem, worldSystem: WorldSystem) {
|
||||
this.scene = scene
|
||||
this.villagerSystem = villagerSystem
|
||||
this.worldSystem = worldSystem
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the world-space Graphics object used for pathfinding visualization.
|
||||
* Starts hidden until toggled on.
|
||||
*/
|
||||
create(): void {
|
||||
this.pathGraphics = this.scene.add.graphics().setDepth(50)
|
||||
this.pathGraphics.setVisible(false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles debug mode on or off.
|
||||
* Shows or hides the pathfinding overlay graphics accordingly.
|
||||
*/
|
||||
toggle(): void {
|
||||
this.active = !this.active
|
||||
this.pathGraphics.setVisible(this.active)
|
||||
if (!this.active) this.pathGraphics.clear()
|
||||
}
|
||||
|
||||
/** Returns whether debug mode is currently active. */
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
|
||||
/**
|
||||
* Redraws pathfinding lines for all currently walking Nisse.
|
||||
* Should be called every frame while debug mode is active.
|
||||
*/
|
||||
update(): void {
|
||||
if (!this.active) return
|
||||
this.pathGraphics.clear()
|
||||
|
||||
const paths = this.villagerSystem.getActivePaths()
|
||||
this.pathGraphics.lineStyle(1, 0x00ffff, 0.65)
|
||||
|
||||
for (const entry of paths) {
|
||||
if (entry.path.length === 0) continue
|
||||
this.pathGraphics.beginPath()
|
||||
this.pathGraphics.moveTo(entry.x, entry.y)
|
||||
for (const step of entry.path) {
|
||||
this.pathGraphics.lineTo(
|
||||
(step.tileX + 0.5) * TILE_SIZE,
|
||||
(step.tileY + 0.5) * TILE_SIZE,
|
||||
)
|
||||
}
|
||||
this.pathGraphics.strokePath()
|
||||
|
||||
// Mark the destination tile
|
||||
const last = entry.path[entry.path.length - 1]
|
||||
this.pathGraphics.fillStyle(0x00ffff, 0.4)
|
||||
this.pathGraphics.fillRect(
|
||||
last.tileX * TILE_SIZE,
|
||||
last.tileY * TILE_SIZE,
|
||||
TILE_SIZE,
|
||||
TILE_SIZE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects and returns all debug data for the current frame.
|
||||
* Called by UIScene to populate the debug panel.
|
||||
* @param ptr - The active pointer, used to resolve world position
|
||||
* @returns Snapshot of game state for display
|
||||
*/
|
||||
getDebugData(ptr: Phaser.Input.Pointer): DebugData {
|
||||
const state = stateManager.getState()
|
||||
const villagers = Object.values(state.world.villagers)
|
||||
const tileX = Math.floor(ptr.worldX / TILE_SIZE)
|
||||
const tileY = Math.floor(ptr.worldY / TILE_SIZE)
|
||||
const tileType = this.worldSystem.getTileType(tileX, tileY)
|
||||
|
||||
const nisseByState = { idle: 0, walking: 0, working: 0, sleeping: 0 }
|
||||
const jobsByType = { chop: 0, mine: 0, farm: 0 }
|
||||
|
||||
for (const v of villagers) {
|
||||
nisseByState[v.aiState as keyof typeof nisseByState]++
|
||||
if (v.job && (v.aiState === 'working' || v.aiState === 'walking')) {
|
||||
jobsByType[v.job.type as keyof typeof jobsByType]++
|
||||
}
|
||||
}
|
||||
|
||||
const resourcesOnTile = Object.values(state.world.resources)
|
||||
.filter(r => r.tileX === tileX && r.tileY === tileY)
|
||||
.map(r => ({ kind: r.kind, hp: r.hp }))
|
||||
|
||||
const buildingsOnTile = Object.values(state.world.buildings)
|
||||
.filter(b => b.tileX === tileX && b.tileY === tileY)
|
||||
.map(b => b.kind)
|
||||
|
||||
const cropsOnTile = Object.values(state.world.crops)
|
||||
.filter(c => c.tileX === tileX && c.tileY === tileY)
|
||||
.map(c => ({ kind: c.kind, stage: c.stage, maxStage: c.maxStage }))
|
||||
|
||||
return {
|
||||
fps: Math.round(this.scene.game.loop.actualFps),
|
||||
mouseWorld: { x: ptr.worldX, y: ptr.worldY },
|
||||
mouseTile: { tileX, tileY },
|
||||
tileType: TILE_NAMES[tileType] ?? `UNKNOWN(${tileType})`,
|
||||
resourcesOnTile,
|
||||
buildingsOnTile,
|
||||
cropsOnTile,
|
||||
nisseTotal: villagers.length,
|
||||
nisseByState,
|
||||
jobsByType,
|
||||
activePaths: this.villagerSystem.getActivePaths().length,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user