Files
test1/app/game/page.tsx

179 lines
7.1 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: 'outsource', name: '外包程序员', cost: 30, desc: '近战 8伤 0.7/s', color: '#94A3B8', img: '/game-assets/tower-outsource.png', tip: '廉价但30%丢空5%自伤' },
{ type: 'intern', name: '00后实习生', cost: 50, desc: '近战 15伤 1.5/s', color: '#22C55E', img: '/game-assets/tower-intern.png', tip: '整顿职场5%概率秒杀' },
{ type: 'hrbp', name: 'HRBP', cost: 80, desc: '辅助 +20%攻速', color: '#EC4899', img: '/game-assets/tower-hrbp.png', tip: '打鸡血:周围塔攻速+20%' },
{ type: 'ops', name: '运营专员', cost: 90, desc: '远程 18伤 范围溅射', color: '#8B5CF6', img: '/game-assets/tower-ops.png', tip: '增长黑客20%双倍HC' },
{ type: 'ppt', name: 'PPT大师', cost: 100, desc: 'AOE减速 5伤', color: '#F59E0B', img: '/game-assets/tower-ppt.png', tip: '黑话领域减速40%' },
{ type: 'senior', name: 'P6资深开发', cost: 120, desc: '远程 30伤 5格射程', color: '#3B82F6', img: '/game-assets/tower-senior.png', tip: '代码屎山:附带持续伤害' },
{ type: 'pm', name: '产品经理', cost: 160, desc: '远程 20伤 需求变更', color: '#06B6D4', img: '/game-assets/tower-pm.png', tip: '需求变更每4次把怪打回去' },
] as const
type TowerType = 'outsource' | 'intern' | 'hrbp' | 'ops' | 'ppt' | 'senior' | 'pm'
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)
const selectedTowerRef = useRef<TowerType | null>(null)
const handleSelectTower = useCallback((type: TowerType) => {
const next = selectedTowerRef.current === type ? null : type
selectedTowerRef.current = next
setSelectedTower(next)
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
// 从 localStorage 读取难度,通过 window 变量传给场景
if (typeof window !== 'undefined') {
const storedDifficulty = localStorage.getItem('game-difficulty')
if (storedDifficulty === 'easy' || storedDifficulty === 'normal' || storedDifficulty === 'hard') {
;(window as any).__gameDifficulty = storedDifficulty
} else {
;(window as any).__gameDifficulty = 'normal'
}
}
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
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
delete (window as any).__gameDifficulty
}
}
}, [])
return (
<div
className="w-full h-screen flex flex-col overflow-hidden"
style={{ backgroundColor: '#0A1628' }}
>
{/* Phaser canvas 区域 */}
<div
id="game-canvas-container"
className="flex-1 min-h-0 w-full"
style={{ backgroundColor: '#0A1628' }}
/>
{/* 底部塔选择面板 */}
<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>
)
}