refactor(game/GameScene): 重构为多地图流程,支持地图切换和背景/装饰渲染

This commit is contained in:
Cloud Bot
2026-03-21 09:45:32 +00:00
parent 9f3b0bdbf1
commit 0f335e7722

View File

@@ -2,10 +2,12 @@ import type Phaser from 'phaser'
import { MAP_COLS, MAP_ROWS, HUD_HEIGHT, GAME_HEIGHT, GAME_WIDTH } from './constants' import { MAP_COLS, MAP_ROWS, HUD_HEIGHT, GAME_HEIGHT, GAME_WIDTH } from './constants'
import { GameManager } from './GameManager' import { GameManager } from './GameManager'
import { import {
PATH_TILES, buildPathTiles,
getCellSize, getCellSize,
drawAllTiles, drawAllTiles,
renderMapLabels, renderMapLabels,
renderMapBackground,
renderDecorations,
renderHUD, renderHUD,
updateKPIBar, updateKPIBar,
BAR_X, BAR_Y, BAR_W, BAR_H, BAR_X, BAR_Y, BAR_W, BAR_H,
@@ -14,10 +16,10 @@ import { TowerManager, type TowerType } from './towers/TowerManager'
import { WaveManager } from './enemies/WaveManager' import { WaveManager } from './enemies/WaveManager'
import { HUD } from './ui/HUD' import { HUD } from './ui/HUD'
import { WeeklyReportModal } from './ui/WeeklyReportModal' import { WeeklyReportModal } from './ui/WeeklyReportModal'
import { MapTransitionModal } from './ui/MapTransitionModal'
import { ALL_MAPS, type MapConfig } from './data/mapConfigs'
// 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
void MAP_ROWS; void GAME_HEIGHT; void GAME_WIDTH
void BAR_X; void BAR_Y; void BAR_W; void BAR_H
/** 所有游戏精灵的 key → public path 映射 */ /** 所有游戏精灵的 key → public path 映射 */
const SPRITE_ASSETS: Record<string, string> = { const SPRITE_ASSETS: Record<string, string> = {
@@ -29,6 +31,9 @@ const SPRITE_ASSETS: Record<string, string> = {
'enemy-old': '/game-assets/enemy-old.png', 'enemy-old': '/game-assets/enemy-old.png',
'enemy-trouble': '/game-assets/enemy-trouble.png', 'enemy-trouble': '/game-assets/enemy-trouble.png',
'enemy-boss': '/game-assets/enemy-boss.png', 'enemy-boss': '/game-assets/enemy-boss.png',
'deco-coffee': '/game-assets/deco-coffee.png',
'deco-monitor': '/game-assets/deco-monitor.png',
'deco-desk': '/game-assets/deco-desk.png',
} }
export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene { export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
@@ -43,51 +48,62 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
private waveManager!: WaveManager private waveManager!: WaveManager
private hud!: HUD private hud!: HUD
private weeklyModal!: WeeklyReportModal private weeklyModal!: WeeklyReportModal
private mapTransitionModal!: MapTransitionModal
private selectedTowerType: TowerType | null = null private selectedTowerType: TowerType | null = null
private buildModeGraphics!: Phaser.GameObjects.Graphics private buildModeGraphics!: Phaser.GameObjects.Graphics
private isWaveRunning: boolean = false private isWaveRunning: boolean = false
// 多地图状态
private currentPathTiles: Set<string> = new Set()
private decorationObjects: Phaser.GameObjects.Image[] = []
private labelObjects: Phaser.GameObjects.Text[] = []
private bgObject: Phaser.GameObjects.Image | null = null
private mapInTransition: boolean = false
constructor() { super({ key: 'GameScene' }) } constructor() { super({ key: 'GameScene' }) }
preload(): void { preload(): void {
// 加载所有 AI 生成的角色图片
for (const [key, path] of Object.entries(SPRITE_ASSETS)) { for (const [key, path] of Object.entries(SPRITE_ASSETS)) {
this.load.image(key, path) this.load.image(key, path)
} }
// 预加载所有地图背景
for (const map of ALL_MAPS) {
if (!this.textures.exists(map.bgKey)) {
this.load.image(map.bgKey, map.bgPath)
}
}
} }
create(): void { create(): void {
this.manager = GameManager.getInstance() this.manager = GameManager.getInstance()
this.manager.reset() this.manager.reset()
// 读取难度React 层通过 window.__gameDifficulty 传入)
if (typeof window !== 'undefined') {
const diff = (window as any).__gameDifficulty
if (diff === 'easy' || diff === 'normal' || diff === 'hard') {
this.manager.setDifficulty(diff)
}
}
this.manager.gameState = 'playing' this.manager.gameState = 'playing'
this.tileGraphics = this.add.graphics() this.tileGraphics = this.add.graphics()
drawAllTiles(this.tileGraphics, null) this.buildModeGraphics = this.add.graphics().setDepth(5)
renderMapLabels(this)
const hudObjs = renderHUD(this) const hudObjs = renderHUD(this)
this.kpiBar = hudObjs.kpiBar this.kpiBar = hudObjs.kpiBar
this.kpiText = hudObjs.kpiText this.kpiText = hudObjs.kpiText
this.hcText = hudObjs.hcText this.hcText = hudObjs.hcText
this.buildModeGraphics = this.add.graphics().setDepth(5)
this.towerManager = new TowerManager(this) this.towerManager = new TowerManager(this)
this.weeklyModal = new WeeklyReportModal({ this.weeklyModal = new WeeklyReportModal({ onBossInspection: () => this.freezeAllTowers(3000),
onBossInspection: () => this.freezeAllTowers(3000),
onFullStamina: () => this.refillAllStamina(), onFullStamina: () => this.refillAllStamina(),
}) })
this.waveManager = new WaveManager( this.mapTransitionModal = new MapTransitionModal()
this,
() => this.onWeeklyReport(),
() => this.onAllWavesComplete(),
() => this.towerManager.removeRandomTower()
)
this.hud = new HUD(this) this.hud = new HUD(this)
this.hud.createWaveButton(() => this.onWaveButtonClick()) this.hud.createWaveButton(() => this.onWaveButtonClick())
this.loadMap(ALL_MAPS[0])
// 接收 React 层传来的选塔事件
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
;(window as any).__gameSelectTower = (type: TowerType | null) => { ;(window as any).__gameSelectTower = (type: TowerType | null) => {
this.selectedTowerType = type this.selectedTowerType = type
@@ -96,34 +112,51 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
} }
this.setupInteraction() this.setupInteraction()
this.setupManagerCallbacks()
if (typeof window !== 'undefined') { ;(window as any).__gameReady?.() }
}
// HC/KPI 变化时同步到 React HUD private setupManagerCallbacks(): void {
this.manager.onHCChange.push((hc: number) => { this.manager.onHCChange.push((hc: number) => {
this.hcText.setText(`HC: ${hc}`) this.hcText.setText(`HC: ${hc}`)
// 通知 React 层更新塔面板可用状态 if (typeof window !== 'undefined') { ;(window as any).__gameOnHCChange?.(hc) }
if (typeof window !== 'undefined') {
;(window as any).__gameOnHCChange?.(hc)
}
}) })
this.manager.onKPIChange.push((kpi: number) => { this.manager.onKPIChange.push((kpi: number) => {
updateKPIBar(this.kpiBar, kpi) updateKPIBar(this.kpiBar, kpi)
this.kpiText.setText(`${kpi}%`) this.kpiText.setText(`${kpi}%`)
}) })
this.manager.onGameOver.push(() => { this.manager.onGameOver.push(() => { this.hud.showGameOver() })
this.hud.showGameOver() this.manager.onVictory.push(() => { this.hud.showVictory() })
})
this.manager.onVictory.push(() => {
this.hud.showVictory()
})
// 通知 React 层游戏已就绪
if (typeof window !== 'undefined') {
;(window as any).__gameReady?.()
} }
/**
* 加载指定地图:渲染背景、路径、装饰物,重置 WaveManager
*/
private loadMap(mapConfig: MapConfig): void {
this.currentPathTiles = buildPathTiles(mapConfig.waypoints)
this.bgObject?.destroy()
this.bgObject = renderMapBackground(this, mapConfig.bgKey)
drawAllTiles(this.tileGraphics, null, this.currentPathTiles, mapConfig)
this.labelObjects.forEach(l => l.destroy())
this.labelObjects = renderMapLabels(this, mapConfig.labels)
this.decorationObjects.forEach(d => d.destroy())
this.decorationObjects = renderDecorations(this, mapConfig.decorations, this.currentPathTiles)
this.waveManager = new WaveManager(
this, mapConfig.waves, this.manager.difficulty,
{
onWaveComplete: () => this.onWeeklyReport(),
onAllWavesComplete: () => this.onMapCleared(),
onDestroyRandomTower: () => this.towerManager.removeRandomTower(),
},
mapConfig.waypoints
)
this.mapInTransition = false
} }
update(_time: number, delta: number): void { update(_time: number, delta: number): void {
if (this.manager.gameState !== 'playing' && this.manager.gameState !== 'idle') return if (this.manager.gameState !== 'playing' && this.manager.gameState !== 'idle') return
if (this.mapInTransition) return
this.towerManager.update(delta, this.waveManager.getAllActiveEnemies()) this.towerManager.update(delta, this.waveManager.getAllActiveEnemies())
this.waveManager.update(delta) this.waveManager.update(delta)
@@ -139,7 +172,7 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
} }
private onWaveButtonClick(): void { private onWaveButtonClick(): void {
if (!this.waveManager.hasMoreWaves() || this.isWaveRunning) return if (!this.waveManager.hasMoreWaves() || this.isWaveRunning || this.mapInTransition) return
this.isWaveRunning = true this.isWaveRunning = true
this.hud.disableWaveButton() this.hud.disableWaveButton()
this.hud.setWaveButtonText('波次进行中...') this.hud.setWaveButtonText('波次进行中...')
@@ -153,7 +186,28 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
this.time.delayedCall(600, () => { this.weeklyModal.show() }) this.time.delayedCall(600, () => { this.weeklyModal.show() })
} }
/** 禁锢全场塔(老板视察效果) */ private onMapCleared(): void {
this.mapInTransition = true
this.isWaveRunning = false
this.hud.disableWaveButton()
this.hud.setWaveButtonText('关卡完成!')
const currentMap = ALL_MAPS[this.manager.currentMapIndex]
const nextIndex = this.manager.currentMapIndex + 1
if (nextIndex >= ALL_MAPS.length) {
this.manager.totalWaveCleared += currentMap.waveCount
this.mapTransitionModal.show(currentMap, () => { this.manager.triggerVictory() })
return
}
this.mapTransitionModal.show(currentMap, () => {
this.manager.totalWaveCleared += currentMap.waveCount
this.manager.currentMapIndex = nextIndex
this.waveManager.clearAllEnemies()
this.loadMap(ALL_MAPS[nextIndex])
this.hud.enableWaveButton()
this.hud.setWaveButtonText('▶ 召唤下一波')
})
}
freezeAllTowers(duration: number = 3000): void { freezeAllTowers(duration: number = 3000): void {
this.towerManager.getAllTowers().forEach(tower => { this.towerManager.getAllTowers().forEach(tower => {
tower.isFrozen = true tower.isFrozen = true
@@ -161,7 +215,6 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
}) })
} }
/** 补满全场塔精力 */
refillAllStamina(): void { refillAllStamina(): void {
this.towerManager.getAllTowers().forEach(tower => { this.towerManager.getAllTowers().forEach(tower => {
tower.stamina = tower.maxStamina tower.stamina = tower.maxStamina
@@ -169,27 +222,23 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
}) })
} }
private onAllWavesComplete(): void {
this.manager.triggerVictory()
this.hud.disableWaveButton()
}
private setupInteraction(): void { private setupInteraction(): void {
const { cellW, cellH } = getCellSize() const { cellW, cellH } = getCellSize()
this.input.on('pointermove', (pointer: Phaser.Input.Pointer) => { this.input.on('pointermove', (pointer: Phaser.Input.Pointer) => {
const col = Math.floor(pointer.x / cellW) const col = Math.floor(pointer.x / cellW)
const row = Math.floor((pointer.y - HUD_HEIGHT) / cellH) 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}`)) { const inBounds = col >= 0 && col < MAP_COLS && row >= 0 && row < MAP_ROWS
if (inBounds && !this.currentPathTiles.has(`${col},${row}`)) {
if (!this.hoveredTile || this.hoveredTile.col !== col || this.hoveredTile.row !== row) { if (!this.hoveredTile || this.hoveredTile.col !== col || this.hoveredTile.row !== row) {
this.hoveredTile = { col, row } this.hoveredTile = { col, row }
drawAllTiles(this.tileGraphics, this.hoveredTile) drawAllTiles(this.tileGraphics, this.hoveredTile, this.currentPathTiles, ALL_MAPS[this.manager.currentMapIndex])
} }
if (this.selectedTowerType) this.drawBuildPreview(col, row, cellW, cellH) if (this.selectedTowerType) this.drawBuildPreview(col, row, cellW, cellH)
return return
} }
if (this.hoveredTile !== null) { if (this.hoveredTile !== null) {
this.hoveredTile = null this.hoveredTile = null
drawAllTiles(this.tileGraphics, null) drawAllTiles(this.tileGraphics, null, this.currentPathTiles, ALL_MAPS[this.manager.currentMapIndex])
this.buildModeGraphics.clear() this.buildModeGraphics.clear()
} }
}) })
@@ -209,29 +258,25 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
const y = HUD_HEIGHT + row * cellH const y = HUD_HEIGHT + row * cellH
this.buildModeGraphics.lineStyle(2, 0xa78bfa, 0.9) this.buildModeGraphics.lineStyle(2, 0xa78bfa, 0.9)
this.buildModeGraphics.strokeRect(x + 2, y + 2, cellW - 4, cellH - 4) this.buildModeGraphics.strokeRect(x + 2, y + 2, cellW - 4, cellH - 4)
// 填充半透明预览
this.buildModeGraphics.fillStyle(0xa78bfa, 0.15) this.buildModeGraphics.fillStyle(0xa78bfa, 0.15)
this.buildModeGraphics.fillRect(x + 2, y + 2, cellW - 4, cellH - 4) this.buildModeGraphics.fillRect(x + 2, y + 2, cellW - 4, cellH - 4)
} }
private handleTileClick(col: number, row: number, cellW: number, cellH: number): void { private handleTileClick(col: number, row: number, cellW: number, cellH: number): void {
if (this.selectedTowerType) { if (this.selectedTowerType) {
if (PATH_TILES.has(`${col},${row}`)) return if (this.currentPathTiles.has(`${col},${row}`)) return
const placed = this.towerManager.placeTower(col, row, this.selectedTowerType) const placed = this.towerManager.placeTower(col, row, this.selectedTowerType)
if (placed) { if (placed) {
this.buildModeGraphics.clear() this.buildModeGraphics.clear()
this.selectedTowerType = null this.selectedTowerType = null
// 通知 React 取消选中状态 if (typeof window !== 'undefined') { ;(window as any).__gameOnTowerDeselect?.() }
if (typeof window !== 'undefined') {
;(window as any).__gameOnTowerDeselect?.()
}
} else { } else {
this.showTip(col, row, cellW, cellH, 'HC不足或格子已占用', '#EF4444') this.showTip(col, row, cellW, cellH, 'HC不足或格子已占用', '#EF4444')
} }
return return
} }
const hasTower = this.towerManager.handleTileClick(col, row) const hasTower = this.towerManager.handleTileClick(col, row)
if (!hasTower && !PATH_TILES.has(`${col},${row}`)) { if (!hasTower && !this.currentPathTiles.has(`${col},${row}`)) {
this.showTip(col, row, cellW, cellH, '请先从底部选择塔', '#A78BFA') this.showTip(col, row, cellW, cellH, '请先从底部选择塔', '#A78BFA')
} }
} }