feat(game): 添加开会暂停系统——点击开会按钮暂停游戏,可安心发激励,结束开会后恢复
This commit is contained in:
@@ -474,13 +474,24 @@ export default function GamePage() {
|
||||
const [selectedTower, setSelectedTower] = useState<TowerType | null>(null)
|
||||
const [gameReady, setGameReady] = useState(false)
|
||||
const [waveStarted, setWaveStarted] = useState(false)
|
||||
// 召唤按钮状态(由 HUD 通过 window.__gameSetWaveBtn 驱动)
|
||||
const [inMeeting, setInMeeting] = useState(false) // 开会=游戏暂停
|
||||
const [waveBtn, setWaveBtn] = useState<{ text: string; disabled: boolean }>({
|
||||
text: '▶ 召唤下一波',
|
||||
disabled: false,
|
||||
})
|
||||
const selectedTowerRef = useRef<TowerType | null>(null)
|
||||
|
||||
const handleMeeting = useCallback(() => {
|
||||
if (!gameReady) return
|
||||
if (!inMeeting) {
|
||||
const ok = typeof window !== 'undefined' && (window as any).__gamePause?.()
|
||||
if (ok) setInMeeting(true)
|
||||
} else {
|
||||
const ok = typeof window !== 'undefined' && (window as any).__gameResume?.()
|
||||
if (ok) setInMeeting(false)
|
||||
}
|
||||
}, [gameReady, inMeeting])
|
||||
|
||||
const handleSelectTower = useCallback((type: TowerType) => {
|
||||
const next = selectedTowerRef.current === type ? null : type
|
||||
selectedTowerRef.current = next
|
||||
@@ -545,7 +556,8 @@ export default function GamePage() {
|
||||
;['__gameOnHCChange','__gameOnTowerDeselect','__gameSelectTower',
|
||||
'__gameReady','__gameDifficulty','__gamePuaBuff',
|
||||
'__gameGetHC','__gameSpendHC','__gameWaveStarted',
|
||||
'__gameSetWaveBtn','__gameWaveBtnState','__gameOnWaveClick'].forEach(k => {
|
||||
'__gameSetWaveBtn','__gameWaveBtnState','__gameOnWaveClick',
|
||||
'__gamePause','__gameResume','__gameIsPaused'].forEach(k => {
|
||||
delete (window as any)[k]
|
||||
})
|
||||
}
|
||||
@@ -565,79 +577,126 @@ export default function GamePage() {
|
||||
style={{ backgroundColor: '#0A1628' }}
|
||||
/>
|
||||
|
||||
{/* 右侧面板:HC + 召唤按钮 + PUA激励台 */}
|
||||
{/* 右侧面板:HC + 开会按钮 + 召唤按钮 + PUA激励台 */}
|
||||
<div style={{
|
||||
width: '240px',
|
||||
flexShrink: 0,
|
||||
backgroundColor: 'rgba(10,18,40,0.97)',
|
||||
borderLeft: '2px solid #1e3a5f',
|
||||
borderLeft: `2px solid ${inMeeting ? '#22C55E' : '#1e3a5f'}`,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
transition: 'border-color 0.2s',
|
||||
boxShadow: inMeeting ? 'inset 0 0 20px rgba(34,197,94,0.08)' : 'none',
|
||||
}}>
|
||||
{/* HC 数量显示 */}
|
||||
{/* HC 数量 + 开会按钮(同一行) */}
|
||||
<div style={{
|
||||
padding: '10px 12px 8px',
|
||||
padding: '8px 12px',
|
||||
borderBottom: '1px solid #1e3a5f',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<span style={{ fontFamily: 'VT323, monospace', fontSize: '13px', color: '#64748B' }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontFamily: 'VT323, monospace', fontSize: '11px', color: '#475569', marginBottom: '1px' }}>
|
||||
人才储备
|
||||
</span>
|
||||
<span style={{
|
||||
</div>
|
||||
<div style={{
|
||||
fontFamily: "'Press Start 2P', monospace",
|
||||
fontSize: '12px',
|
||||
fontSize: '11px',
|
||||
color: '#A78BFA',
|
||||
letterSpacing: '1px',
|
||||
}}>
|
||||
{hc} HC
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* 开会按钮 */}
|
||||
<button
|
||||
onClick={handleMeeting}
|
||||
disabled={!gameReady || !waveStarted}
|
||||
title={inMeeting ? '结束开会,恢复游戏' : '开会暂停,发起激励'}
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
padding: '6px 10px',
|
||||
backgroundColor: inMeeting ? '#14532D' : '#0F1B2D',
|
||||
border: `2px solid ${inMeeting ? '#22C55E' : '#1e3a5f'}`,
|
||||
borderRadius: '8px',
|
||||
color: inMeeting ? '#4ADE80' : (!gameReady || !waveStarted ? '#334155' : '#94A3B8'),
|
||||
fontFamily: 'VT323, monospace',
|
||||
fontSize: '14px',
|
||||
cursor: !gameReady || !waveStarted ? 'not-allowed' : 'pointer',
|
||||
transition: 'all 0.15s',
|
||||
whiteSpace: 'nowrap',
|
||||
lineHeight: 1.2,
|
||||
boxShadow: inMeeting ? '0 0 10px rgba(34,197,94,0.3)' : 'none',
|
||||
}}
|
||||
>
|
||||
{inMeeting ? '📋 结束开会' : '📋 开会'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 召唤下一波按钮 */}
|
||||
<div style={{ padding: '10px 12px', borderBottom: '2px solid #1e3a5f' }}>
|
||||
{/* 开会中提示条 */}
|
||||
{inMeeting && (
|
||||
<div style={{
|
||||
backgroundColor: '#14532D',
|
||||
borderBottom: '1px solid #22C55E',
|
||||
padding: '5px 12px',
|
||||
fontFamily: 'VT323, monospace',
|
||||
fontSize: '13px',
|
||||
color: '#86EFAC',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
}}>
|
||||
<span style={{ animation: 'pulse 1.5s ease-in-out infinite' }}>●</span>
|
||||
游戏已暂停,可安心激励
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 召唤下一波按钮(开会时禁用) */}
|
||||
<div style={{ padding: '8px 12px', borderBottom: '2px solid #1e3a5f' }}>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (inMeeting) return
|
||||
if (typeof window !== 'undefined') {
|
||||
(window as any).__gameOnWaveClick?.()
|
||||
}
|
||||
}}
|
||||
disabled={waveBtn.disabled || !gameReady}
|
||||
disabled={waveBtn.disabled || !gameReady || inMeeting}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px 8px',
|
||||
backgroundColor: waveBtn.disabled || !gameReady ? '#0F172A' : '#1e3a5f',
|
||||
border: `2px solid ${waveBtn.disabled || !gameReady ? '#1e293b' : '#7C3AED'}`,
|
||||
backgroundColor: waveBtn.disabled || !gameReady || inMeeting ? '#0F172A' : '#1e3a5f',
|
||||
border: `2px solid ${waveBtn.disabled || !gameReady || inMeeting ? '#1e293b' : '#7C3AED'}`,
|
||||
borderRadius: '8px',
|
||||
color: waveBtn.disabled || !gameReady ? '#4B5563' : '#C4B5FD',
|
||||
color: waveBtn.disabled || !gameReady || inMeeting ? '#4B5563' : '#C4B5FD',
|
||||
fontFamily: 'VT323, monospace',
|
||||
fontSize: '20px',
|
||||
cursor: waveBtn.disabled || !gameReady ? 'not-allowed' : 'pointer',
|
||||
cursor: waveBtn.disabled || !gameReady || inMeeting ? 'not-allowed' : 'pointer',
|
||||
transition: 'all 0.15s ease',
|
||||
letterSpacing: '1px',
|
||||
lineHeight: 1.2,
|
||||
boxShadow: waveBtn.disabled || !gameReady ? 'none' : '0 0 12px rgba(124,58,237,0.3)',
|
||||
boxShadow: waveBtn.disabled || !gameReady || inMeeting ? 'none' : '0 0 12px rgba(124,58,237,0.3)',
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
if (!waveBtn.disabled && gameReady) {
|
||||
if (!waveBtn.disabled && gameReady && !inMeeting) {
|
||||
(e.currentTarget as HTMLButtonElement).style.backgroundColor = '#2d3a5e'
|
||||
;(e.currentTarget as HTMLButtonElement).style.boxShadow = '0 0 18px rgba(124,58,237,0.5)'
|
||||
}
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
if (!waveBtn.disabled && gameReady) {
|
||||
if (!waveBtn.disabled && gameReady && !inMeeting) {
|
||||
(e.currentTarget as HTMLButtonElement).style.backgroundColor = '#1e3a5f'
|
||||
;(e.currentTarget as HTMLButtonElement).style.boxShadow = '0 0 12px rgba(124,58,237,0.3)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{waveBtn.text}
|
||||
{inMeeting ? '开会中...' : waveBtn.text}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* PUA 激励台 */}
|
||||
<PuaPanel gameReady={gameReady} hc={hc} waveStarted={waveStarted} />
|
||||
<PuaPanel gameReady={gameReady} hc={hc} waveStarted={waveStarted || inMeeting} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -725,3 +725,8 @@
|
||||
|
||||
/* 深色背景(终端/代码块)保留不覆盖:bg-slate-900 bg-slate-950 */
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.3; }
|
||||
}
|
||||
|
||||
@@ -102,6 +102,22 @@ export class GameManager {
|
||||
this.onKPIChange.forEach(cb => cb(this.kpi))
|
||||
}
|
||||
|
||||
/** 暂停游戏(仅 playing 状态下有效) */
|
||||
pause(): boolean {
|
||||
if (this.gameState !== 'playing') return false
|
||||
this.gameState = 'paused'
|
||||
return true
|
||||
}
|
||||
|
||||
/** 恢复游戏(仅 paused 状态下有效) */
|
||||
resume(): boolean {
|
||||
if (this.gameState !== 'paused') return false
|
||||
this.gameState = 'playing'
|
||||
return true
|
||||
}
|
||||
|
||||
get isPaused(): boolean { return this.gameState === 'paused' }
|
||||
|
||||
/** 触发胜利 */
|
||||
triggerVictory(): void {
|
||||
if (this.gameState === 'playing') {
|
||||
|
||||
@@ -64,6 +64,10 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
|
||||
private bgObject: Phaser.GameObjects.Image | null = null
|
||||
private mapInTransition: boolean = false
|
||||
|
||||
// 暂停遮罩
|
||||
private pauseOverlay: Phaser.GameObjects.Graphics | null = null
|
||||
private pauseText: Phaser.GameObjects.Text | null = null
|
||||
|
||||
constructor() { super({ key: 'GameScene' }) }
|
||||
|
||||
preload(): void {
|
||||
@@ -124,19 +128,34 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
|
||||
audio.startBGM()
|
||||
})
|
||||
|
||||
// 注册 PUA buff 接口 + HC 查询/扣除接口供 React 层调用
|
||||
// 注册 PUA buff 接口 + HC 查询/扣除接口 + 暂停/恢复接口
|
||||
if (typeof window !== 'undefined') {
|
||||
;(window as any).__gamePuaBuff = (
|
||||
effect: string, score: number, title: string
|
||||
) => {
|
||||
this.applyPuaBuff(effect, score, title)
|
||||
}
|
||||
// 查询当前 HC
|
||||
) => { this.applyPuaBuff(effect, score, title) }
|
||||
|
||||
;(window as any).__gameGetHC = () => this.manager.hc
|
||||
// 尝试扣除 HC,成功返回 true,不足返回 false
|
||||
;(window as any).__gameSpendHC = (amount: number): boolean => {
|
||||
return this.manager.spendHC(amount)
|
||||
;(window as any).__gameSpendHC = (amount: number): boolean =>
|
||||
this.manager.spendHC(amount)
|
||||
|
||||
;(window as any).__gamePause = (): boolean => {
|
||||
if (!this.manager.pause()) return false
|
||||
this.showPauseOverlay()
|
||||
// 暂停 Phaser 物理与 Tween
|
||||
this.physics.pause()
|
||||
this.tweens.pauseAll()
|
||||
return true
|
||||
}
|
||||
|
||||
;(window as any).__gameResume = (): boolean => {
|
||||
if (!this.manager.resume()) return false
|
||||
this.hidePauseOverlay()
|
||||
this.physics.resume()
|
||||
this.tweens.resumeAll()
|
||||
return true
|
||||
}
|
||||
|
||||
;(window as any).__gameIsPaused = (): boolean => this.manager.isPaused
|
||||
}
|
||||
|
||||
this.setupInteraction()
|
||||
@@ -145,10 +164,34 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
|
||||
}
|
||||
|
||||
private createMuteButton(_audio: AudioEngine): void {
|
||||
// 静音按钮已移除,音乐默认开启
|
||||
void _audio
|
||||
}
|
||||
|
||||
private showPauseOverlay(): void {
|
||||
const { width, height } = this.scale
|
||||
// 半透明深色遮罩
|
||||
this.pauseOverlay = this.add.graphics().setDepth(90)
|
||||
this.pauseOverlay.fillStyle(0x000000, 0.6)
|
||||
this.pauseOverlay.fillRect(0, 0, width, height)
|
||||
|
||||
// "开会中" 提示卡片
|
||||
this.pauseText = this.add.text(width / 2, height / 2, '📋 开会中...\n游戏已暂停', {
|
||||
fontFamily: 'VT323, monospace',
|
||||
fontSize: '32px',
|
||||
color: '#FCD34D',
|
||||
backgroundColor: '#1e3a5f',
|
||||
padding: { x: 32, y: 18 },
|
||||
align: 'center',
|
||||
}).setOrigin(0.5, 0.5).setDepth(91)
|
||||
}
|
||||
|
||||
private hidePauseOverlay(): void {
|
||||
this.pauseOverlay?.destroy()
|
||||
this.pauseOverlay = null
|
||||
this.pauseText?.destroy()
|
||||
this.pauseText = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用 PUA buff 效果到游戏
|
||||
* effect: attack_boost | speed_boost | money_rain | rage_mode | backfire
|
||||
@@ -324,6 +367,7 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
|
||||
private readonly AUTO_WAVE_DELAY = 2000 // 自动开始等待:3s→2s,缩短喘息时间
|
||||
|
||||
update(_time: number, delta: number): void {
|
||||
if (this.manager.gameState === 'paused') return // 暂停时完全跳过
|
||||
if (this.manager.gameState !== 'playing' && this.manager.gameState !== 'idle') return
|
||||
if (this.mapInTransition) return
|
||||
|
||||
|
||||
Reference in New Issue
Block a user