From 1473542f653aaaad8cb49b0d7201275413b37a81 Mon Sep 17 00:00:00 2001 From: Cloud Bot Date: Tue, 24 Mar 2026 08:36:38 +0000 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E5=B0=86=E5=8F=AC=E5=94=A4?= =?UTF-8?q?=E4=B8=8B=E4=B8=80=E6=B3=A2=E6=8C=89=E9=92=AE=E7=A7=BB=E8=87=B3?= =?UTF-8?q?=E5=8F=B3=E4=BE=A7=E9=9D=A2=E6=9D=BFHC=E4=B8=8B=E6=96=B9?= =?UTF-8?q?=EF=BC=8C=E6=94=B9=E4=B8=BAReact=20DOM=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=EF=BC=8C=E7=A7=BB=E9=99=A4Phaser=20Canvas=E5=86=85=E6=8C=89?= =?UTF-8?q?=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/game/page.tsx | 105 ++++++++++++++++++++++++++++++++++++++++------ game/ui/HUD.ts | 83 +++++++++++++++++------------------- 2 files changed, 131 insertions(+), 57 deletions(-) diff --git a/app/game/page.tsx b/app/game/page.tsx index 9597d37..f5f45f9 100644 --- a/app/game/page.tsx +++ b/app/game/page.tsx @@ -116,15 +116,12 @@ function PuaPanel({ gameReady, hc, waveStarted }: { gameReady: boolean; hc: numb return (
{/* 标题 */}
@@ -397,7 +394,12 @@ export default function GamePage() { const [hc, setHc] = useState(200) const [selectedTower, setSelectedTower] = useState(null) const [gameReady, setGameReady] = useState(false) - const [waveStarted, setWaveStarted] = useState(false) // 第一波开始后才允许激励 + const [waveStarted, setWaveStarted] = useState(false) + // 召唤按钮状态(由 HUD 通过 window.__gameSetWaveBtn 驱动) + const [waveBtn, setWaveBtn] = useState<{ text: string; disabled: boolean }>({ + text: '▶ 召唤下一波', + disabled: false, + }) const selectedTowerRef = useRef(null) const handleSelectTower = useCallback((type: TowerType) => { @@ -439,6 +441,11 @@ export default function GamePage() { if (mounted) { selectedTowerRef.current = null; setSelectedTower(null) } } ;(window as any).__gameReady = () => { if (mounted) setGameReady(true) } + // HUD 通过此回调更新召唤按钮状态 + ;(window as any).__gameSetWaveBtn = (s: { text: string; disabled: boolean }) => { + ;(window as any).__gameWaveBtnState = s + if (mounted) setWaveBtn({ ...s }) + } // 轮询 __gameWaveStarted(Phaser 设置后通知 React) const checkWaveStarted = setInterval(() => { if ((window as any).__gameWaveStarted) { @@ -458,7 +465,8 @@ export default function GamePage() { if (typeof window !== 'undefined') { ;['__gameOnHCChange','__gameOnTowerDeselect','__gameSelectTower', '__gameReady','__gameDifficulty','__gamePuaBuff', - '__gameGetHC','__gameSpendHC','__gameWaveStarted'].forEach(k => { + '__gameGetHC','__gameSpendHC','__gameWaveStarted', + '__gameSetWaveBtn','__gameWaveBtnState','__gameOnWaveClick'].forEach(k => { delete (window as any)[k] }) } @@ -469,7 +477,7 @@ export default function GamePage() {
- {/* 中间行:游戏画布 + PUA面板 */} + {/* 中间行:游戏画布 + 右侧控制面板 */}
{/* 游戏画布 */}
- {/* PUA 激励台(右侧) */} - + + {/* 右侧面板:HC + 召唤按钮 + PUA激励台 */} +
+ {/* HC 数量显示 */} +
+
+ + 人才储备 + + + {hc} HC + +
+
+ + {/* 召唤下一波按钮 */} +
+ +
+ + {/* PUA 激励台 */} + +
{/* 底部塔选择面板 */} diff --git a/game/ui/HUD.ts b/game/ui/HUD.ts index e68824d..1f2a0d2 100644 --- a/game/ui/HUD.ts +++ b/game/ui/HUD.ts @@ -5,74 +5,62 @@ import { showVictoryModal, showDefeatModal } from './EndScreenModal' /** * 游戏 HUD 辅助工具 - * 负责管理"召唤下一波"按钮、波次提示横幅、胜利/失败结算弹窗 + * 召唤下一波按钮已移至 React 右侧面板,HUD 只负责横幅提示和结算弹窗 */ export class HUD { private scene: Phaser.Scene - private waveBtn: Phaser.GameObjects.Text | null = null private waveBannerTimeout: (() => void) | null = null - private _onClick: (() => void) | null = null + // 按钮状态通过 window 回调同步到 React + private _onWaveClick: (() => void) | null = null constructor(scene: Phaser.Scene) { this.scene = scene } /** - * 创建"召唤下一波"按钮 - * @param onClick 点击回调 + * 注册"召唤下一波"逻辑,并把控制接口暴露给 React 层 */ createWaveButton(onClick: () => void): void { - if (this.waveBtn) this.waveBtn.destroy() - this._onClick = onClick + this._onWaveClick = onClick + if (typeof window === 'undefined') return - this.waveBtn = this.scene.add - .text(GAME_WIDTH / 2, HUD_HEIGHT + 16, '▶ 召唤下一波', { - fontFamily: 'VT323, monospace', - fontSize: '26px', - color: '#A78BFA', - backgroundColor: '#1e3a5f', - padding: { x: 20, y: 8 }, - stroke: '#7C3AED', - strokeThickness: 1, - }) - .setOrigin(0.5, 0) - .setDepth(20) - .setInteractive({ useHandCursor: true }) - - this.waveBtn.on('pointerover', () => { - if (this.waveBtn) this.waveBtn.setStyle({ backgroundColor: '#2d5a8e', color: '#C4B5FD' }) - }) - this.waveBtn.on('pointerout', () => { - if (this.waveBtn) this.waveBtn.setStyle({ backgroundColor: '#1e3a5f', color: '#A78BFA' }) - }) - this.waveBtn.on('pointerdown', () => onClick()) + // React 层通过 window.__gameOnWaveClick() 触发 + ;(window as any).__gameOnWaveClick = () => { + this._onWaveClick?.() + } + // 初始状态:可用 + this._notifyReact('▶ 召唤下一波', false) + } + + private _notifyReact(text: string, disabled: boolean): void { + if (typeof window !== 'undefined') { + ;(window as any).__gameSetWaveBtn?.({ text, disabled }) + } } - /** 更新按钮文字(如禁用状态) */ setWaveButtonText(text: string): void { - this.waveBtn?.setText(text) + // 保持当前 disabled 状态,只更新文字 + if (typeof window !== 'undefined') { + const cur = (window as any).__gameWaveBtnState + const disabled = cur?.disabled ?? false + this._notifyReact(text, disabled) + } } disableWaveButton(): void { - if (!this.waveBtn) return - this.waveBtn.setStyle({ color: '#4B5563', backgroundColor: '#0F172A' }) - this.waveBtn.removeAllListeners('pointerdown') + this._notifyReact((typeof window !== 'undefined' + ? (window as any).__gameWaveBtnState?.text ?? '波次进行中...' + : '波次进行中...'), true) } enableWaveButton(): void { - if (!this.waveBtn) return - this.waveBtn.setStyle({ color: '#A78BFA', backgroundColor: '#1e3a5f' }) - // 重新绑定点击事件(disableWaveButton 会 removeAllListeners) - this.waveBtn.removeAllListeners('pointerdown') - if (this._onClick) { - this.waveBtn.on('pointerdown', () => this._onClick!()) - } + this._notifyReact((typeof window !== 'undefined' + ? (window as any).__gameWaveBtnState?.text ?? '▶ 召唤下一波' + : '▶ 召唤下一波'), false) } /** * 显示波次开始横幅 - * @param waveNumber 当前波次(1-based) - * @param totalWaves 总波次数 */ showWaveBanner(waveNumber: number, totalWaves: number): void { const isBoss = waveNumber === totalWaves @@ -128,7 +116,7 @@ export class HUD { }) } - /** 显示胜利画面(完整绩效评级弹窗,委托给 EndScreenModal) */ + /** 显示胜利画面 */ showVictory(): void { const manager = GameManager.getInstance() const kpi = manager.kpi @@ -159,13 +147,18 @@ export class HUD { showVictoryModal({ kpi, hc, grade, gradeColor, gradeDesc }) } - /** 显示失败画面(仿钉钉退群通知,委托给 EndScreenModal) */ + /** 显示失败画面 */ showGameOver(): void { showDefeatModal() } destroy(): void { - this.waveBtn?.destroy() if (this.waveBannerTimeout) this.waveBannerTimeout() + if (typeof window !== 'undefined') { + delete (window as any).__gameOnWaveClick + delete (window as any).__gameSetWaveBtn + delete (window as any).__gameWaveBtnState + } } } +