feat(GameScene): 集成防御塔系统与波次系统,替换占位提示逻辑
This commit is contained in:
@@ -13,11 +13,20 @@ import {
|
|||||||
BAR_W,
|
BAR_W,
|
||||||
BAR_H,
|
BAR_H,
|
||||||
} from './mapRenderer'
|
} 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 {
|
export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
|
||||||
class GameScene extends PhaserLib.Scene {
|
class GameScene extends PhaserLib.Scene {
|
||||||
private manager!: GameManager
|
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 hoveredTile: { col: number; row: number } | null = null
|
||||||
private tileGraphics!: Phaser.GameObjects.Graphics
|
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() {
|
constructor() {
|
||||||
super({ key: 'GameScene' })
|
super({ key: 'GameScene' })
|
||||||
}
|
}
|
||||||
@@ -36,33 +54,100 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
|
|||||||
this.manager.reset()
|
this.manager.reset()
|
||||||
this.manager.gameState = 'playing'
|
this.manager.gameState = 'playing'
|
||||||
|
|
||||||
// 渲染地图
|
// Render map
|
||||||
this.tileGraphics = this.add.graphics()
|
this.tileGraphics = this.add.graphics()
|
||||||
drawAllTiles(this.tileGraphics, null)
|
drawAllTiles(this.tileGraphics, null)
|
||||||
|
|
||||||
// 地图装饰标签
|
|
||||||
renderMapLabels(this)
|
renderMapLabels(this)
|
||||||
|
|
||||||
// HUD(在地图之上)
|
// Render HUD
|
||||||
const hud = renderHUD(this)
|
const hudObjs = renderHUD(this)
|
||||||
this.kpiBar = hud.kpiBar
|
this.kpiBar = hudObjs.kpiBar
|
||||||
this.kpiText = hud.kpiText
|
this.kpiText = hudObjs.kpiText
|
||||||
this.hcText = hud.hcText
|
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()
|
this.setupInteraction()
|
||||||
|
|
||||||
// GameManager 事件回调 → 更新 HUD
|
// GameManager callbacks → update HUD
|
||||||
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.onHCChange.push((hc: number) => {
|
this.manager.onHCChange.push((hc: number) => {
|
||||||
this.hcText.setText(`HC: ${hc}`)
|
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 {
|
private setupInteraction(): void {
|
||||||
const { cellW, cellH } = getCellSize()
|
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 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) {
|
if (
|
||||||
if (!PATH_TILES.has(`${col},${row}`)) {
|
col >= 0 && col < MAP_COLS &&
|
||||||
if (
|
row >= 0 && row < MAP_ROWS &&
|
||||||
!this.hoveredTile ||
|
!PATH_TILES.has(`${col},${row}`)
|
||||||
this.hoveredTile.col !== col ||
|
) {
|
||||||
this.hoveredTile.row !== row
|
if (
|
||||||
) {
|
!this.hoveredTile ||
|
||||||
this.hoveredTile = { col, row }
|
this.hoveredTile.col !== col ||
|
||||||
drawAllTiles(this.tileGraphics, this.hoveredTile)
|
this.hoveredTile.row !== row
|
||||||
}
|
) {
|
||||||
return
|
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) {
|
if (this.hoveredTile !== null) {
|
||||||
this.hoveredTile = null
|
this.hoveredTile = null
|
||||||
drawAllTiles(this.tileGraphics, null)
|
drawAllTiles(this.tileGraphics, null)
|
||||||
|
this.buildModeGraphics.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -100,17 +194,56 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
col >= 0 && col < MAP_COLS &&
|
col >= 0 && col < MAP_COLS &&
|
||||||
row >= 0 && row < MAP_ROWS &&
|
row >= 0 && row < MAP_ROWS
|
||||||
!PATH_TILES.has(`${col},${row}`)
|
|
||||||
) {
|
) {
|
||||||
this.showBuildPrompt(col, row, cellW, cellH)
|
this.handleTileClick(col, row, cellW, cellH)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 点击可建格子时的占位提示(Phase 1) */
|
private drawBuildPreview(
|
||||||
private showBuildPrompt(
|
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,
|
col: number,
|
||||||
row: number,
|
row: number,
|
||||||
cellW: number,
|
cellW: number,
|
||||||
@@ -119,30 +252,39 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
|
|||||||
const x = col * cellW + cellW / 2
|
const x = col * cellW + cellW / 2
|
||||||
const y = HUD_HEIGHT + row * cellH + cellH / 2
|
const y = HUD_HEIGHT + row * cellH + cellH / 2
|
||||||
const tip = this.add
|
const tip = this.add
|
||||||
.text(x, y - 20, '建塔 (即将开放)', {
|
.text(x, y - 20, '从底部面板选择塔', {
|
||||||
fontFamily: 'VT323, monospace',
|
fontFamily: 'VT323, monospace',
|
||||||
fontSize: '18px',
|
fontSize: '16px',
|
||||||
color: '#F43F5E',
|
color: '#A78BFA',
|
||||||
backgroundColor: '#0a1628',
|
backgroundColor: '#0a1628',
|
||||||
padding: { x: 8, y: 4 },
|
padding: { x: 8, y: 4 },
|
||||||
})
|
})
|
||||||
.setOrigin(0.5, 1)
|
.setOrigin(0.5, 1)
|
||||||
.setDepth(20)
|
.setDepth(20)
|
||||||
|
this.time.delayedCall(1200, () => tip.destroy())
|
||||||
|
}
|
||||||
|
|
||||||
this.time.delayedCall(1500, () => {
|
private showPlaceFail(
|
||||||
tip.destroy()
|
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
|
return GameScene
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user