diff --git a/game/GameScene.ts b/game/GameScene.ts index afb0d71..6762800 100644 --- a/game/GameScene.ts +++ b/game/GameScene.ts @@ -13,11 +13,20 @@ import { 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 -/** - * 创建主游戏场景类(工厂函数,接收动态导入的 Phaser 实例) - * 这样可以保证 SSR 安全(只在客户端执行) - */ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene { class GameScene extends PhaserLib.Scene { private manager!: GameManager @@ -27,6 +36,15 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene { 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' }) } @@ -36,33 +54,100 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene { this.manager.reset() this.manager.gameState = 'playing' - // 渲染地图 + // Render map this.tileGraphics = this.add.graphics() drawAllTiles(this.tileGraphics, null) - - // 地图装饰标签 renderMapLabels(this) - // HUD(在地图之上) - const hud = renderHUD(this) - this.kpiBar = hud.kpiBar - this.kpiText = hud.kpiText - this.hcText = hud.hcText + // 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 事件回调 → 更新 HUD + // 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() @@ -72,22 +157,31 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene { 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) { - if (!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) - } - return + 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() } } ) @@ -100,17 +194,56 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene { if ( col >= 0 && col < MAP_COLS && - row >= 0 && row < MAP_ROWS && - !PATH_TILES.has(`${col},${row}`) + row >= 0 && row < MAP_ROWS ) { - this.showBuildPrompt(col, row, cellW, cellH) + this.handleTileClick(col, row, cellW, cellH) } } ) } - /** 点击可建格子时的占位提示(Phase 1) */ - private showBuildPrompt( + 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, @@ -119,30 +252,39 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene { const x = col * cellW + cellW / 2 const y = HUD_HEIGHT + row * cellH + cellH / 2 const tip = this.add - .text(x, y - 20, '建塔 (即将开放)', { + .text(x, y - 20, '从底部面板选择塔', { fontFamily: 'VT323, monospace', - fontSize: '18px', - color: '#F43F5E', + fontSize: '16px', + color: '#A78BFA', backgroundColor: '#0a1628', padding: { x: 8, y: 4 }, }) .setOrigin(0.5, 1) .setDepth(20) + this.time.delayedCall(1200, () => tip.destroy()) + } - this.time.delayedCall(1500, () => { - 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()) } } - // 避免 unused variable 警告 - void MAP_ROWS - void GAME_HEIGHT - void GAME_WIDTH - void BAR_X - void BAR_Y - void BAR_W - void BAR_H - return GameScene }