import type Phaser from 'phaser' import { MAP_COLS, MAP_ROWS, HUD_HEIGHT, GAME_HEIGHT, GAME_WIDTH } from './constants' import { GameManager } from './GameManager' import { PATH_TILES, getCellSize, drawAllTiles, renderMapLabels, renderHUD, updateKPIBar, BAR_X, BAR_Y, BAR_W, BAR_H, } from './mapRenderer' import { TowerManager, type TowerType } from './towers/TowerManager' import { WaveManager } from './enemies/WaveManager' import { TowerPanel } from './ui/TowerPanel' import { HUD } from './ui/HUD' // Suppress unused-variable warnings for exported constants void MAP_ROWS void GAME_HEIGHT void GAME_WIDTH void BAR_X void BAR_Y void BAR_W void BAR_H export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene { class GameScene extends PhaserLib.Scene { private manager!: GameManager private kpiBar!: Phaser.GameObjects.Graphics private kpiText!: Phaser.GameObjects.Text private hcText!: Phaser.GameObjects.Text private hoveredTile: { col: number; row: number } | null = null private tileGraphics!: Phaser.GameObjects.Graphics // New systems private towerManager!: TowerManager private waveManager!: WaveManager private towerPanel!: TowerPanel private hud!: HUD private selectedTowerType: TowerType | null = null private buildModeGraphics!: Phaser.GameObjects.Graphics private isWaveRunning: boolean = false constructor() { super({ key: 'GameScene' }) } create(): void { this.manager = GameManager.getInstance() this.manager.reset() this.manager.gameState = 'playing' // Render map this.tileGraphics = this.add.graphics() drawAllTiles(this.tileGraphics, null) renderMapLabels(this) // Render HUD const hudObjs = renderHUD(this) this.kpiBar = hudObjs.kpiBar this.kpiText = hudObjs.kpiText this.hcText = hudObjs.hcText // Build mode preview overlay this.buildModeGraphics = this.add.graphics() this.buildModeGraphics.setDepth(5) // Initialize game systems this.towerManager = new TowerManager(this) this.waveManager = new WaveManager( this, () => this.hud.showWeeklyReportAlert(), () => this.onAllWavesComplete(), () => this.towerManager.removeRandomTower() ) // HUD helper this.hud = new HUD(this) this.hud.createWaveButton(() => this.onWaveButtonClick()) // DOM tower panel this.towerPanel = new TowerPanel('game-container') this.towerPanel.onSelect((type) => { this.selectedTowerType = type if (!type) this.buildModeGraphics.clear() }) // Mouse interaction this.setupInteraction() // GameManager callbacks → update HUD this.manager.onKPIChange.push((kpi: number) => { updateKPIBar(this.kpiBar, kpi) this.kpiText.setText(`${kpi}%`) }) this.manager.onHCChange.push((hc: number) => { this.hcText.setText(`HC: ${hc}`) this.towerPanel.refreshCardStates() }) this.manager.onGameOver.push(() => { this.hud.showGameOver() this.towerPanel.destroy() }) this.manager.onVictory.push(() => { this.hud.showVictory() this.towerPanel.destroy() }) } update(_time: number, delta: number): void { if ( this.manager.gameState !== 'playing' && this.manager.gameState !== 'idle' ) return this.towerManager.update(delta, this.waveManager.getAllActiveEnemies()) this.waveManager.update(delta) // Re-enable wave button when wave clears if ( this.isWaveRunning && this.waveManager.getAllActiveEnemies().length === 0 && this.waveManager.hasMoreWaves() ) { this.isWaveRunning = false this.hud.enableWaveButton() this.hud.setWaveButtonText('▶ 召唤下一波') } } private onWaveButtonClick(): void { if (!this.waveManager.hasMoreWaves() || this.isWaveRunning) return this.isWaveRunning = true this.hud.disableWaveButton() this.hud.setWaveButtonText('波次进行中...') this.waveManager.startNextWave() const waveNum = this.waveManager.getCurrentWaveNumber() this.hud.showWaveBanner(waveNum, this.waveManager.totalWaves) } private onAllWavesComplete(): void { this.manager.triggerVictory() this.hud.disableWaveButton() } private setupInteraction(): void { const { cellW, cellH } = getCellSize() this.input.on( 'pointermove', (pointer: Phaser.Input.Pointer) => { const col = Math.floor(pointer.x / cellW) const row = Math.floor((pointer.y - HUD_HEIGHT) / cellH) if ( col >= 0 && col < MAP_COLS && row >= 0 && row < MAP_ROWS && !PATH_TILES.has(`${col},${row}`) ) { if ( !this.hoveredTile || this.hoveredTile.col !== col || this.hoveredTile.row !== row ) { this.hoveredTile = { col, row } drawAllTiles(this.tileGraphics, this.hoveredTile) } // Show build preview if in build mode if (this.selectedTowerType) { this.drawBuildPreview(col, row, cellW, cellH) } return } if (this.hoveredTile !== null) { this.hoveredTile = null drawAllTiles(this.tileGraphics, null) this.buildModeGraphics.clear() } } ) this.input.on( 'pointerdown', (pointer: Phaser.Input.Pointer) => { const col = Math.floor(pointer.x / cellW) const row = Math.floor((pointer.y - HUD_HEIGHT) / cellH) if ( col >= 0 && col < MAP_COLS && row >= 0 && row < MAP_ROWS ) { this.handleTileClick(col, row, cellW, cellH) } } ) } private drawBuildPreview( col: number, row: number, cellW: number, cellH: number ): void { this.buildModeGraphics.clear() if (!this.towerManager.canPlace(col, row)) return const x = col * cellW const y = HUD_HEIGHT + row * cellH this.buildModeGraphics.lineStyle(2, 0xa78bfa, 0.9) this.buildModeGraphics.strokeRect(x + 2, y + 2, cellW - 4, cellH - 4) } private handleTileClick( col: number, row: number, cellW: number, cellH: number ): void { // If in build mode, try to place tower if (this.selectedTowerType) { if (PATH_TILES.has(`${col},${row}`)) return const placed = this.towerManager.placeTower(col, row, this.selectedTowerType) if (placed) { this.buildModeGraphics.clear() this.towerPanel.deselect() this.selectedTowerType = null } else { this.showPlaceFail(col, row, cellW, cellH) } return } // Otherwise check if clicking an existing tower const hasTower = this.towerManager.handleTileClick(col, row) if (!hasTower && !PATH_TILES.has(`${col},${row}`)) { this.showBuildHint(col, row, cellW, cellH) } } private showBuildHint( col: number, row: number, cellW: number, cellH: number ): void { const x = col * cellW + cellW / 2 const y = HUD_HEIGHT + row * cellH + cellH / 2 const tip = this.add .text(x, y - 20, '从底部面板选择塔', { fontFamily: 'VT323, monospace', fontSize: '16px', color: '#A78BFA', backgroundColor: '#0a1628', padding: { x: 8, y: 4 }, }) .setOrigin(0.5, 1) .setDepth(20) this.time.delayedCall(1200, () => tip.destroy()) } private showPlaceFail( col: number, row: number, cellW: number, cellH: number ): void { const x = col * cellW + cellW / 2 const y = HUD_HEIGHT + row * cellH + cellH / 2 const tip = this.add .text(x, y - 20, 'HC不足或格子已占用', { fontFamily: 'VT323, monospace', fontSize: '16px', color: '#EF4444', backgroundColor: '#0a1628', padding: { x: 8, y: 4 }, }) .setOrigin(0.5, 1) .setDepth(20) this.time.delayedCall(1200, () => tip.destroy()) } } return GameScene }