fix(game): 修复输了能进下一关的Bug、换关后塔残留、召唤按钮太小、战斗前不能发激励

This commit is contained in:
Cloud Bot
2026-03-24 08:30:03 +00:00
parent d7253c45be
commit 653c54c06f
4 changed files with 53 additions and 23 deletions

View File

@@ -48,7 +48,7 @@ function calcPuaCost(hc: number): number {
} }
// ── PUA 输入面板 ───────────────────────────────────────────────────────────── // ── PUA 输入面板 ─────────────────────────────────────────────────────────────
function PuaPanel({ gameReady, hc }: { gameReady: boolean; hc: number }) { function PuaPanel({ gameReady, hc, waveStarted }: { gameReady: boolean; hc: number; waveStarted: boolean }) {
const [text, setText] = useState('') const [text, setText] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [result, setResult] = useState<PuaResult | null>(null) const [result, setResult] = useState<PuaResult | null>(null)
@@ -61,7 +61,7 @@ function PuaPanel({ gameReady, hc }: { gameReady: boolean; hc: number }) {
const canAfford = hc >= cost const canAfford = hc >= cost
const handleSubmit = useCallback(async () => { const handleSubmit = useCallback(async () => {
if (!text.trim() || loading || !gameReady) return if (!text.trim() || loading || !gameReady || !waveStarted) return
// 先从游戏扣除 HC扣不到则拒绝 // 先从游戏扣除 HC扣不到则拒绝
const spendHC: ((n: number) => boolean) | undefined = const spendHC: ((n: number) => boolean) | undefined =
@@ -102,7 +102,7 @@ function PuaPanel({ gameReady, hc }: { gameReady: boolean; hc: number }) {
} finally { } finally {
setLoading(false) setLoading(false)
} }
}, [text, loading, gameReady, hc]) }, [text, loading, gameReady, hc, waveStarted])
const handleKeyDown = (e: React.KeyboardEvent) => { const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) { if (e.key === 'Enter' && !e.shiftKey) {
@@ -143,7 +143,7 @@ function PuaPanel({ gameReady, hc }: { gameReady: boolean; hc: number }) {
color: '#475569', color: '#475569',
marginTop: '2px', marginTop: '2px',
}}> }}>
AI判断鸡血值 {!waveStarted ? '⚠ 召唤第一波后才能激励' : '输入打鸡血的话,AI判断鸡血值'}
</div> </div>
</div> </div>
@@ -210,12 +210,12 @@ function PuaPanel({ gameReady, hc }: { gameReady: boolean; hc: number }) {
onChange={e => setText(e.target.value)} onChange={e => setText(e.target.value)}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
placeholder={placeholder} placeholder={placeholder}
disabled={loading || !gameReady} disabled={loading || !gameReady || !waveStarted}
rows={4} rows={4}
style={{ style={{
width: '100%', width: '100%',
backgroundColor: '#0F1B2D', backgroundColor: '#0F1B2D',
border: `1px solid ${canAfford ? '#1e3a5f' : '#7F1D1D'}`, border: `1px solid ${!waveStarted ? '#1e3a5f' : canAfford ? '#1e3a5f' : '#7F1D1D'}`,
borderRadius: '6px', borderRadius: '6px',
color: '#E2E8F0', color: '#E2E8F0',
fontFamily: 'VT323, monospace', fontFamily: 'VT323, monospace',
@@ -224,7 +224,7 @@ function PuaPanel({ gameReady, hc }: { gameReady: boolean; hc: number }) {
resize: 'none', resize: 'none',
outline: 'none', outline: 'none',
lineHeight: 1.4, lineHeight: 1.4,
opacity: gameReady ? 1 : 0.5, opacity: gameReady && waveStarted ? 1 : 0.4,
transition: 'border-color 0.2s', transition: 'border-color 0.2s',
}} }}
/> />
@@ -232,23 +232,23 @@ function PuaPanel({ gameReady, hc }: { gameReady: boolean; hc: number }) {
{/* 提交按钮 */} {/* 提交按钮 */}
<button <button
onClick={handleSubmit} onClick={handleSubmit}
disabled={loading || !text.trim() || !gameReady || !canAfford} disabled={loading || !text.trim() || !gameReady || !canAfford || !waveStarted}
style={{ style={{
width: '100%', width: '100%',
padding: '8px', padding: '8px',
backgroundColor: loading ? '#1e3a5f' : canAfford ? '#7C3AED' : '#4C1D95', backgroundColor: loading ? '#1e3a5f' : !waveStarted ? '#1e3a5f' : canAfford ? '#7C3AED' : '#4C1D95',
border: 'none', border: 'none',
borderRadius: '6px', borderRadius: '6px',
color: '#E2E8F0', color: '#E2E8F0',
fontFamily: "'Press Start 2P', monospace", fontFamily: "'Press Start 2P', monospace",
fontSize: '8px', fontSize: '8px',
cursor: loading || !text.trim() || !gameReady || !canAfford ? 'not-allowed' : 'pointer', cursor: loading || !text.trim() || !gameReady || !canAfford || !waveStarted ? 'not-allowed' : 'pointer',
opacity: !text.trim() || !gameReady || !canAfford ? 0.45 : 1, opacity: !text.trim() || !gameReady || !canAfford || !waveStarted ? 0.45 : 1,
transition: 'all 0.15s', transition: 'all 0.15s',
letterSpacing: '0.5px', letterSpacing: '0.5px',
}} }}
> >
{loading ? '分析中...' : !canAfford ? 'HC不足' : `发起激励 -${cost}HC`} {loading ? '分析中...' : !waveStarted ? '战斗开始后可用' : !canAfford ? 'HC不足' : `发起激励 -${cost}HC`}
</button> </button>
{/* 当前结果 */} {/* 当前结果 */}
@@ -397,6 +397,7 @@ export default function GamePage() {
const [hc, setHc] = useState(200) const [hc, setHc] = useState(200)
const [selectedTower, setSelectedTower] = useState<TowerType | null>(null) const [selectedTower, setSelectedTower] = useState<TowerType | null>(null)
const [gameReady, setGameReady] = useState(false) const [gameReady, setGameReady] = useState(false)
const [waveStarted, setWaveStarted] = useState(false) // 第一波开始后才允许激励
const selectedTowerRef = useRef<TowerType | null>(null) const selectedTowerRef = useRef<TowerType | null>(null)
const handleSelectTower = useCallback((type: TowerType) => { const handleSelectTower = useCallback((type: TowerType) => {
@@ -438,6 +439,13 @@ export default function GamePage() {
if (mounted) { selectedTowerRef.current = null; setSelectedTower(null) } if (mounted) { selectedTowerRef.current = null; setSelectedTower(null) }
} }
;(window as any).__gameReady = () => { if (mounted) setGameReady(true) } ;(window as any).__gameReady = () => { if (mounted) setGameReady(true) }
// 轮询 __gameWaveStartedPhaser 设置后通知 React
const checkWaveStarted = setInterval(() => {
if ((window as any).__gameWaveStarted) {
if (mounted) setWaveStarted(true)
clearInterval(checkWaveStarted)
}
}, 200)
} }
gameRef.current = new Phaser.Game(config) gameRef.current = new Phaser.Game(config)
@@ -450,7 +458,7 @@ export default function GamePage() {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
;['__gameOnHCChange','__gameOnTowerDeselect','__gameSelectTower', ;['__gameOnHCChange','__gameOnTowerDeselect','__gameSelectTower',
'__gameReady','__gameDifficulty','__gamePuaBuff', '__gameReady','__gameDifficulty','__gamePuaBuff',
'__gameGetHC','__gameSpendHC'].forEach(k => { '__gameGetHC','__gameSpendHC','__gameWaveStarted'].forEach(k => {
delete (window as any)[k] delete (window as any)[k]
}) })
} }
@@ -470,7 +478,7 @@ export default function GamePage() {
style={{ backgroundColor: '#0A1628' }} style={{ backgroundColor: '#0A1628' }}
/> />
{/* PUA 激励台(右侧) */} {/* PUA 激励台(右侧) */}
<PuaPanel gameReady={gameReady} hc={hc} /> <PuaPanel gameReady={gameReady} hc={hc} waveStarted={waveStarted} />
</div> </div>
{/* 底部塔选择面板 */} {/* 底部塔选择面板 */}

View File

@@ -365,8 +365,11 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
this.waveManager.startNextWave() this.waveManager.startNextWave()
const waveNum = this.waveManager.getCurrentWaveNumber() const waveNum = this.waveManager.getCurrentWaveNumber()
this.hud.showWaveBanner(waveNum, this.waveManager.totalWaves) this.hud.showWaveBanner(waveNum, this.waveManager.totalWaves)
// 波次开始音效
AudioEngine.getInstance().playWaveStart() AudioEngine.getInstance().playWaveStart()
// 通知 React 层:战斗已开始,允许发起激励
if (typeof window !== 'undefined') {
;(window as any).__gameWaveStarted = true
}
} }
private onWeeklyReport(): void { private onWeeklyReport(): void {
@@ -375,7 +378,11 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
} }
private onMapCleared(): void { private onMapCleared(): void {
// 若游戏已失败,不触发地图切换
if (this.manager.gameState === 'defeat') return
this.mapInTransition = true this.mapInTransition = true
this.autoNextWaveTimer = -1
this.isWaveRunning = false this.isWaveRunning = false
this.hud.disableWaveButton() this.hud.disableWaveButton()
this.hud.setWaveButtonText('关卡完成!') this.hud.setWaveButtonText('关卡完成!')
@@ -390,6 +397,8 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
this.manager.totalWaveCleared += currentMap.waveCount this.manager.totalWaveCleared += currentMap.waveCount
this.manager.currentMapIndex = nextIndex this.manager.currentMapIndex = nextIndex
this.waveManager.clearAllEnemies() this.waveManager.clearAllEnemies()
// 清除上一关所有防御塔
this.towerManager.clearAllTowers()
this.loadMap(ALL_MAPS[nextIndex]) this.loadMap(ALL_MAPS[nextIndex])
this.hud.enableWaveButton() this.hud.enableWaveButton()
this.hud.setWaveButtonText('▶ 召唤下一波') this.hud.setWaveButtonText('▶ 召唤下一波')

View File

@@ -258,6 +258,17 @@ export class TowerManager {
return this.towers return this.towers
} }
/** 切换地图时清除所有防御塔(静默销毁,不播放音效) */
clearAllTowers(): void {
for (const tower of this.towers) {
tower.destroy()
}
this.towers = []
this.occupiedCells.clear()
this.infoLabel?.destroy()
this.infoLabel = null
}
hasTowerAt(gridX: number, gridY: number): boolean { hasTowerAt(gridX: number, gridY: number): boolean {
return this.occupiedCells.has(`${gridX},${gridY}`) return this.occupiedCells.has(`${gridX},${gridY}`)
} }

View File

@@ -23,25 +23,27 @@ export class HUD {
*/ */
createWaveButton(onClick: () => void): void { createWaveButton(onClick: () => void): void {
if (this.waveBtn) this.waveBtn.destroy() if (this.waveBtn) this.waveBtn.destroy()
this._onClick = onClick // 保存引用,供 enableWaveButton 重新绑定 this._onClick = onClick
this.waveBtn = this.scene.add this.waveBtn = this.scene.add
.text(GAME_WIDTH - 160, HUD_HEIGHT + 20, '▶ 召唤下一波', { .text(GAME_WIDTH / 2, HUD_HEIGHT + 16, '▶ 召唤下一波', {
fontFamily: "'Press Start 2P', monospace", fontFamily: 'VT323, monospace',
fontSize: '9px', fontSize: '26px',
color: '#A78BFA', color: '#A78BFA',
backgroundColor: '#1e3a5f', backgroundColor: '#1e3a5f',
padding: { x: 10, y: 6 }, padding: { x: 20, y: 8 },
stroke: '#7C3AED',
strokeThickness: 1,
}) })
.setOrigin(0, 0) .setOrigin(0.5, 0)
.setDepth(20) .setDepth(20)
.setInteractive({ useHandCursor: true }) .setInteractive({ useHandCursor: true })
this.waveBtn.on('pointerover', () => { this.waveBtn.on('pointerover', () => {
if (this.waveBtn) this.waveBtn.setStyle({ backgroundColor: '#2d5a8e' }) if (this.waveBtn) this.waveBtn.setStyle({ backgroundColor: '#2d5a8e', color: '#C4B5FD' })
}) })
this.waveBtn.on('pointerout', () => { this.waveBtn.on('pointerout', () => {
if (this.waveBtn) this.waveBtn.setStyle({ backgroundColor: '#1e3a5f' }) if (this.waveBtn) this.waveBtn.setStyle({ backgroundColor: '#1e3a5f', color: '#A78BFA' })
}) })
this.waveBtn.on('pointerdown', () => onClick()) this.waveBtn.on('pointerdown', () => onClick())
} }