Files
test1/app/game/page.tsx

172 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useEffect, useRef, useState, useCallback } from 'react'
const TOWER_META = [
{ type: 'intern', name: '00后实习生', cost: 50, desc: '近战 15伤 1.5/s', color: '#22C55E', img: '/game-assets/tower-intern.png', tip: '整顿职场5%概率秒杀' },
{ type: 'senior', name: 'P6资深开发', cost: 120, desc: '远程 30伤 5格射程', color: '#3B82F6', img: '/game-assets/tower-senior.png', tip: '代码屎山:附带持续伤害' },
{ type: 'ppt', name: 'PPT大师', cost: 100, desc: 'AOE减速 5伤', color: '#F59E0B', img: '/game-assets/tower-ppt.png', tip: '黑话领域减速40%' },
{ type: 'hrbp', name: 'HRBP', cost: 80, desc: '辅助 +20%攻速', color: '#EC4899', img: '/game-assets/tower-hrbp.png', tip: '打鸡血:周围塔攻速+20%' },
] as const
type TowerType = 'intern' | 'senior' | 'ppt' | 'hrbp'
export default function GamePage() {
const gameRef = useRef<{ destroy: (removeCanvas: boolean) => void } | null>(null)
const [hc, setHc] = useState(200)
const [selectedTower, setSelectedTower] = useState<TowerType | null>(null)
const [gameReady, setGameReady] = useState(false)
// expose setter to game scene via window
const selectedTowerRef = useRef<TowerType | null>(null)
const handleSelectTower = useCallback((type: TowerType) => {
const next = selectedTowerRef.current === type ? null : type
selectedTowerRef.current = next
setSelectedTower(next)
// notify scene
if (typeof window !== 'undefined') {
(window as any).__gameSelectTower?.(next)
}
}, [])
useEffect(() => {
let mounted = true
const initGame = async () => {
const Phaser = (await import('phaser')).default
const { createGameConfig } = await import('@/game/config')
const { createGameScene } = await import('@/game/GameScene')
if (!mounted) return
const GameScene = createGameScene(Phaser)
const config = createGameConfig('game-canvas-container')
config.scene = [GameScene]
if (config.scale) {
config.scale.mode = Phaser.Scale.FIT
config.scale.autoCenter = Phaser.Scale.CENTER_BOTH
}
config.type = Phaser.AUTO
// expose HC update to React
if (typeof window !== 'undefined') {
(window as any).__gameOnHCChange = (val: number) => {
if (mounted) setHc(val)
}
(window as any).__gameOnTowerDeselect = () => {
if (mounted) {
selectedTowerRef.current = null
setSelectedTower(null)
}
}
;(window as any).__gameReady = () => {
if (mounted) setGameReady(true)
}
}
gameRef.current = new Phaser.Game(config)
}
initGame().catch(console.error)
return () => {
mounted = false
gameRef.current?.destroy(true)
gameRef.current = null
if (typeof window !== 'undefined') {
delete (window as any).__gameOnHCChange
delete (window as any).__gameOnTowerDeselect
delete (window as any).__gameSelectTower
delete (window as any).__gameReady
}
}
}, [])
return (
<div
className="w-full h-screen flex flex-col overflow-hidden"
style={{ backgroundColor: '#0A1628' }}
>
{/* Phaser canvas 区域flex-1 填满剩余高度 */}
<div
id="game-canvas-container"
className="flex-1 min-h-0 w-full"
style={{ backgroundColor: '#0A1628' }}
/>
{/* 底部塔选择面板 — 纯 React DOM不被 Canvas 遮挡 */}
<div
style={{
backgroundColor: 'rgba(10,18,40,0.97)',
borderTop: '2px solid #1e3a5f',
padding: '8px 16px',
display: 'flex',
gap: '10px',
justifyContent: 'center',
alignItems: 'center',
flexShrink: 0,
zIndex: 50,
boxShadow: '0 -4px 20px rgba(124,58,237,0.15)',
}}
>
{/* 选塔提示 */}
<div style={{ fontFamily: "'Press Start 2P', monospace", fontSize: '8px', color: '#7C3AED', marginRight: '8px', whiteSpace: 'nowrap', opacity: gameReady ? 1 : 0.3 }}>
{selectedTower ? '点击格子建造' : '选择塔 ▼'}
</div>
{TOWER_META.map((meta) => {
const canAfford = hc >= meta.cost
const isSelected = selectedTower === meta.type
return (
<button
key={meta.type}
title={meta.tip}
onClick={() => canAfford && handleSelectTower(meta.type)}
disabled={!canAfford}
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '3px',
padding: '6px 10px',
backgroundColor: isSelected ? '#1e3a5f' : '#0F1B2D',
border: `2px solid ${isSelected ? meta.color : '#1e3a5f'}`,
borderRadius: '8px',
cursor: canAfford ? 'pointer' : 'not-allowed',
minWidth: '90px',
opacity: canAfford ? 1 : 0.4,
transition: 'all 0.15s ease',
boxShadow: isSelected ? `0 0 12px ${meta.color}66` : 'none',
transform: isSelected ? 'translateY(-3px)' : 'none',
}}
>
{/* 角色图片 */}
<div style={{ width: '48px', height: '48px', position: 'relative', flexShrink: 0 }}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={meta.img}
alt={meta.name}
style={{ width: '100%', height: '100%', objectFit: 'contain', imageRendering: 'auto' }}
/>
</div>
{/* 名称 */}
<span style={{ fontFamily: 'VT323, monospace', fontSize: '13px', color: meta.color, textAlign: 'center', lineHeight: 1.1 }}>
{meta.name}
</span>
{/* 费用 */}
<span style={{ fontFamily: "'Press Start 2P', monospace", fontSize: '8px', color: '#A78BFA' }}>
{meta.cost} HC
</span>
{/* 描述 */}
<span style={{ fontFamily: 'VT323, monospace', fontSize: '11px', color: '#64748B', textAlign: 'center' }}>
{meta.desc}
</span>
</button>
)
})}
</div>
</div>
)
}