227 lines
7.1 KiB
Markdown
227 lines
7.1 KiB
Markdown
# Task 1A+1B: 游戏基础框架搭建
|
||
|
||
## 任务概要
|
||
在现有 Next.js 项目中,安装 Phaser.js,改造主页(封面),创建游戏页面,实现 Phaser 游戏框架挂载 + S型地图渲染。
|
||
|
||
## 执行步骤
|
||
|
||
### Step 1: 安装 Phaser.js
|
||
```bash
|
||
pnpm add phaser
|
||
```
|
||
|
||
### Step 2: 改造 `app/page.tsx`(主页/游戏封面)
|
||
完全重写主页,展示游戏封面。注意:**必须覆盖模板原有 UI,不保留任何模板样式**。
|
||
|
||
设计要求(参考 `design-system/大厂保卫战/MASTER.md`):
|
||
- 背景色:`#0F0F23`(深夜宇宙蓝)
|
||
- 标题字体:Press Start 2P(像素字体),颜色 `#A78BFA`(霓虹紫)
|
||
- 副标题:VT323 字体
|
||
- 主色 CTA 按钮(进入游戏):`#F43F5E` 红色
|
||
- 效果:CRT 扫描线叠加层(`::before` 伪元素,repeating-linear-gradient 细黑线)
|
||
- 霓虹发光效果:标题 `text-shadow` 双层发光
|
||
- 装饰性文字:背景中随机排列大厂黑话(低透明度)
|
||
|
||
主页内容结构:
|
||
```
|
||
[全屏黑色背景]
|
||
[CRT扫描线叠加层]
|
||
[装饰黑话背景文字]
|
||
[中央卡片]
|
||
标题:大厂保卫战
|
||
副标题:最后的打工人
|
||
英文副标:THE LAST GRINDER
|
||
描述:保住KPI,战胜空降VP
|
||
[开始游戏] 按钮 → 跳转 /game
|
||
[底部版权:像素风格]
|
||
```
|
||
|
||
### Step 3: 创建 `app/game/page.tsx`(游戏页面)
|
||
```typescript
|
||
'use client'
|
||
import { useEffect, useRef } from 'react'
|
||
|
||
export default function GamePage() {
|
||
const gameRef = useRef<any>(null)
|
||
|
||
useEffect(() => {
|
||
let game: any = null
|
||
|
||
const initGame = async () => {
|
||
const Phaser = (await import('phaser')).default
|
||
const { createGameConfig } = await import('@/game/config')
|
||
|
||
if (gameRef.current && !game) {
|
||
game = new Phaser.Game(createGameConfig('game-container'))
|
||
}
|
||
}
|
||
|
||
initGame()
|
||
|
||
return () => {
|
||
game?.destroy(true)
|
||
}
|
||
}, [])
|
||
|
||
return (
|
||
<div className="w-full h-screen bg-[#0F0F23] overflow-hidden">
|
||
<div id="game-container" className="w-full h-full" />
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
### Step 4: 创建游戏核心文件
|
||
|
||
**`game/constants.ts`**(游戏常量):
|
||
```typescript
|
||
export const MAP_COLS = 16
|
||
export const MAP_ROWS = 12
|
||
export const TILE_SIZE = 80 // 每格80px,总计 1280x960,Phaser会缩放
|
||
export const GAME_WIDTH = 1280
|
||
export const GAME_HEIGHT = 720
|
||
|
||
// S型路径格子坐标
|
||
export const PATH_WAYPOINTS = [
|
||
{ x: 0, y: 2 },
|
||
{ x: 11, y: 2 },
|
||
{ x: 11, y: 9 },
|
||
{ x: 15, y: 9 },
|
||
]
|
||
|
||
// 游戏初始数值
|
||
export const INITIAL_HC = 200
|
||
export const INITIAL_KPI = 100
|
||
export const STAMINA_MAX = 100
|
||
export const STAMINA_REGEN = 5 // 每秒恢复量
|
||
export const COFFEE_COST = 10 // 瑞幸咖啡 HC 成本
|
||
|
||
// 颜色常量
|
||
export const COLOR_PATH = 0x3d2b1f
|
||
export const COLOR_BUILDABLE = 0x1e3a5f
|
||
export const COLOR_HOVER = 0x2d5a8e
|
||
export const COLOR_BORDER = 0x0a1628
|
||
```
|
||
|
||
**`game/config.ts`**(Phaser 配置):
|
||
```typescript
|
||
import Phaser from 'phaser'
|
||
import { GAME_WIDTH, GAME_HEIGHT } from './constants'
|
||
|
||
export function createGameConfig(containerId: string): Phaser.Types.Core.GameConfig {
|
||
return {
|
||
type: Phaser.AUTO,
|
||
width: GAME_WIDTH,
|
||
height: GAME_HEIGHT,
|
||
parent: containerId,
|
||
backgroundColor: '#0a1628',
|
||
scale: {
|
||
mode: Phaser.Scale.FIT,
|
||
autoCenter: Phaser.Scale.CENTER_BOTH,
|
||
},
|
||
scene: [], // 在 GameScene 中动态加载
|
||
physics: {
|
||
default: 'arcade',
|
||
arcade: { debug: false }
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**`game/GameScene.ts`**(主游戏场景):
|
||
|
||
核心地图渲染逻辑:
|
||
- 创建 16x12 格子网格
|
||
- 根据 PATH_WAYPOINTS 计算路径格子集合(路径上的每个格子都是路径格)
|
||
- 路径格子:深褐色(`0x3d2b1f`)
|
||
- 可建塔格子:深蓝色(`0x1e3a5f`)
|
||
- 格子间有1px间距
|
||
- 鼠标悬停非路径格子时高亮
|
||
- 点击非路径格子:触发建塔逻辑(Phase 1先只做UI提示)
|
||
- 添加地图装饰性文字("面试间"、"财务室")
|
||
- 场景中显示:顶部 HUD 区域(KPI进度条 + HC数值)
|
||
|
||
地图路径计算方式(不能只用 4 个坐标点,需要填充中间格子):
|
||
```typescript
|
||
// 将折线坐标展开为完整路径格子集合
|
||
function buildPathTiles(waypoints): Set<string> {
|
||
const tiles = new Set<string>()
|
||
for (let i = 0; i < waypoints.length - 1; i++) {
|
||
const from = waypoints[i]
|
||
const to = waypoints[i + 1]
|
||
// 水平或垂直连线
|
||
if (from.x === to.x) {
|
||
for (let y = Math.min(from.y, to.y); y <= Math.max(from.y, to.y); y++) {
|
||
tiles.add(`${from.x},${y}`)
|
||
}
|
||
} else {
|
||
for (let x = Math.min(from.x, to.x); x <= Math.max(from.x, to.x); x++) {
|
||
tiles.add(`${x},${from.y}`)
|
||
}
|
||
}
|
||
}
|
||
return tiles
|
||
}
|
||
```
|
||
|
||
HUD 设计(Phaser Text/Graphics 实现,而非 HTML 层):
|
||
- 顶部黑色半透明条(全宽,高度60px)
|
||
- 左:游戏标题 "大厂保卫战" (Press Start 2P,小字号)
|
||
- 中:KPI 进度条(宽300px,绿→黄→红渐变)
|
||
- 右:HC 数值 "HC: 200"
|
||
|
||
**`game/GameManager.ts`**(游戏状态管理器):
|
||
```typescript
|
||
export class GameManager {
|
||
private static instance: GameManager
|
||
public hc: number = INITIAL_HC
|
||
public kpi: number = INITIAL_KPI
|
||
public currentWave: number = 0
|
||
public gameState: 'idle' | 'playing' | 'paused' | 'victory' | 'defeat' = 'idle'
|
||
|
||
static getInstance(): GameManager { ... }
|
||
|
||
spendHC(amount: number): boolean { ... } // 扣除HC,不足返回false
|
||
addHC(amount: number): void { ... }
|
||
reduceKPI(amount: number): void { ... } // 减少KPI,归零触发失败
|
||
|
||
// 事件系统(用于 Scene 与 UI 通信)
|
||
onHCChange: ((hc: number) => void)[] = []
|
||
onKPIChange: ((kpi: number) => void)[] = []
|
||
onGameOver: (() => void)[] = []
|
||
onVictory: (() => void)[] = []
|
||
}
|
||
```
|
||
|
||
### Step 5: 更新 `app/layout.tsx`
|
||
在 layout 中引入 Google Fonts(Press Start 2P + VT323):
|
||
```tsx
|
||
// 在 <head> 中添加
|
||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
||
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=VT323:wght@400&display=swap" rel="stylesheet" />
|
||
```
|
||
|
||
### Step 6: 更新 `app/globals.css`
|
||
添加游戏全局样式:
|
||
- CSS 变量(颜色 token)
|
||
- CRT 扫描线效果(供主页使用)
|
||
- 霓虹发光 keyframes 动画
|
||
- 像素字体 class
|
||
|
||
## 验收标准
|
||
1. `pnpm dev` 启动成功,无编译错误
|
||
2. 访问 `/`:显示游戏封面,有进入游戏按钮,霓虹效果正常
|
||
3. 点击进入游戏跳转至 `/game`
|
||
4. 访问 `/game`:渲染 16x12 格子地图,S型路径可见(深褐色路径,深蓝色可建区域)
|
||
5. 鼠标悬停可建塔格子时高亮
|
||
6. 顶部 HUD 显示 KPI 进度条和 HC 数值
|
||
|
||
## 相关上下文
|
||
- 设计系统:`design-system/大厂保卫战/MASTER.md`
|
||
- PRD:`.docs/prd-tower-defense-game.md`
|
||
- 计划文档:`.docs/plans/2026-03-21-tower-defense-game.md`
|
||
- 开发规范:`@rules/dev-best-practices.md`(**必须先阅读**)
|
||
|
||
完成开发后,按 `@rules/atomic-commit.md` 规范,将变更按逻辑模块分组提交(禁止 `git add .`),确保所有文件均已 commit 后再结束任务。
|