Files
test1/.docs/tasks/task-1cd-towers-enemies.md

428 lines
15 KiB
Markdown
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.
# Task 1C+1D: 防御塔系统 + 怪物波次系统
## 任务概要
在已完成的 Phaser.js 游戏框架基础上实现完整的防御塔系统4种塔+部署逻辑+精力管理和怪物波次系统4种怪物+路径寻路+战斗逻辑+KPI扣减
**前置条件**Task 1A+1B 已完成,`game/` 目录已存在,`game/GameScene.ts``game/constants.ts``game/GameManager.ts` 等已实现。
## 技术架构
```
game/
├── constants.ts ← 已存在
├── config.ts ← 已存在
├── GameScene.ts ← 已存在,需要集成新系统
├── GameManager.ts ← 已存在
├── towers/
│ ├── TowerBase.ts ← 新建
│ ├── InternTower.ts ← 新建00后实习生
│ ├── SeniorDevTower.ts ← 新建P6资深开发
│ ├── PPTMasterTower.ts ← 新建PPT大师
│ ├── HRBPTower.ts ← 新建HRBP辅助塔
│ └── TowerManager.ts ← 新建
├── enemies/
│ ├── EnemyBase.ts ← 新建
│ ├── FreshGraduate.ts ← 新建(校招应届生)
│ ├── OldEmployee.ts ← 新建35岁老员工
│ ├── TroubleMaker.ts ← 新建(职业碰瓷王)
│ ├── BossVP.ts ← 新建空降VP
│ ├── EnemyPool.ts ← 新建(对象池)
│ └── WaveManager.ts ← 新建(波次管理器)
└── ui/
├── TowerPanel.ts ← 新建(底部塔选择面板)
└── HUD.ts ← 新建(升级 GameScene 中的 HUD 逻辑)
```
## 实现规范
### 防御塔系统
**`game/towers/TowerBase.ts`**
```typescript
export abstract class TowerBase {
protected scene: Phaser.Scene
public gridX: number
public gridY: number
protected sprite: Phaser.GameObjects.Graphics // 用 Graphics 绘制(无美术资源)
protected staminaBar: Phaser.GameObjects.Graphics
// 数值属性
public readonly cost: number
public readonly attackRange: number // 格子单位
public readonly attackDamage: number
public readonly attackSpeed: number // 每秒攻击次数
public readonly maxStamina: number = 100
public stamina: number = 100
protected attackCooldown: number = 0
protected staminaRegen: number = 5 // 每秒恢复量(不攻击时)
protected isActive: boolean = true // false = 摸鱼状态
constructor(scene, gridX, gridY) { ... }
// 每帧更新
update(delta: number, enemies: EnemyBase[]): void {
this.attackCooldown -= delta
if (this.stamina <= 0) {
this.isActive = false
}
if (!this.isActive) {
// 摸鱼:恢复精力
this.stamina = Math.min(this.maxStamina, this.stamina + this.staminaRegen * delta / 1000)
if (this.stamina > 0) this.isActive = true
this.updateStaminaBar()
return
}
// 找目标并攻击
const target = this.findTarget(enemies)
if (target && this.attackCooldown <= 0) {
this.attack(target)
this.stamina -= 5 // 攻击消耗精力
this.attackCooldown = 1000 / this.attackSpeed
this.updateStaminaBar()
} else if (!target) {
// 没有目标时恢复精力
this.stamina = Math.min(this.maxStamina, this.stamina + this.staminaRegen * delta / 1000)
this.updateStaminaBar()
}
}
abstract attack(target: EnemyBase): void
abstract drawSprite(): void // 用 Graphics 绘制塔的外观
protected findTarget(enemies: EnemyBase[]): EnemyBase | null {
// 找射程内血量最多的怪(或最近怪)
...
}
protected updateStaminaBar(): void {
// 更新精力条颜色和宽度
...
}
// 玩家点击塔时调用花费10HC购买咖啡
buyCoffee(): boolean {
const manager = GameManager.getInstance()
if (manager.spendHC(COFFEE_COST)) {
this.stamina = this.maxStamina
this.isActive = true
this.updateStaminaBar()
return true
}
return false
}
destroy(): void { ... }
}
```
**4 种塔的具体实现:**
`InternTower`00后实习生50HC
- 视觉绿色小人形状Graphics 画)
- 攻击近战射程1格单体15伤害1.5/s
- 特殊技能"整顿职场"每次攻击5%概率秒杀(血量<500的普通怪物
- 被动每秒1%概率直接离场(调用 destroy退还 25HC
- 攻击方式:直接扣血(近战,不需要弹幕)
`SeniorDevTower`P6资深开发120HC
- 视觉:深蓝色方形,顶部有 `</>` 符号
- 攻击远程直线30伤害1.0/s射程5格
- 特殊技能"代码屎山":子弹命中后附加 DOT10伤害/秒持续3秒
- 攻击方式发射子弹Graphics 绘制的绿色代码块),直线飞行
`PPTMasterTower`PPT大师100HC
- 视觉:橙色圆形,有 PPT 图标
- 攻击范围AOE5伤害1.5/s射程3格圆形区域
- 特殊技能"黑话领域"攻击命中的怪物移速降低40%持续2秒
- 攻击方式爆炸效果Graphics 绘制的扩散圆圈),范围内所有怪物受伤
`HRBPTower`HRBP80HC
- 视觉:粉色,心形图案
- 攻击:无直接伤害(辅助塔)
- 特殊技能"打鸡血"每0.5秒给周围1格内的塔攻速+20%叠加BUFF每次BUFF消耗自身5精力
- 辅助方式周期性扫描周围格子找到塔就给BUFF
**`game/towers/TowerManager.ts`**
- 维护所有已部署塔的数组
- 处理建塔请求:检查格子合法性(非路径+未占用扣除HC
- 每帧调用所有塔的 update(delta, enemies)
- 处理塔的点击事件(显示购买咖啡的提示)
**`game/ui/TowerPanel.ts`**(底部塔选择面板,在 Phaser Scene 内用 DOM 或 Phaser.GameObjects 实现):
- 优先使用 HTML DOM 覆盖层(`document.createElement`),绝对定位在 Canvas 底部
- 4个塔卡片显示像素风格图标 + 名称 + 成本
- 当前选中的塔有高亮边框
- HC 不足时显示灰色不可点击状态
- 点击塔卡片后,鼠标进入"建塔模式",下一次点击地图格子时放置塔
---
### 怪物波次系统
**`game/enemies/EnemyBase.ts`**
```typescript
export abstract class EnemyBase {
protected scene: Phaser.Scene
protected sprite: Phaser.GameObjects.Graphics
protected healthBar: Phaser.GameObjects.Graphics
protected quoteText: Phaser.GameObjects.Text // 头顶语录
public maxHp: number
public hp: number
public speed: number // 像素/秒
public readonly kpiDamage: number // 到达终点扣除的 KPI
public readonly hcReward: number // 死亡奖励的 HC
// 路径相关
protected pathPoints: { x: number, y: number }[] // 像素坐标路径
protected currentPathIndex: number = 0
protected distanceTraveled: number = 0
// 状态
public isDead: boolean = false
public isActive: boolean = true
public dotEffects: { damage: number, duration: number, timer: number }[] = [] // DOT效果
public slowEffect: number = 0 // 减速百分比0~1
public slowTimer: number = 0
public shieldCount: number = 0 // 护盾层数(老员工专用)
constructor(scene, pathPoints) { ... }
update(delta: number): void {
if (this.isDead || !this.isActive) return
// 处理DOT效果
this.processDOT(delta)
// 处理减速
if (this.slowTimer > 0) {
this.slowTimer -= delta
if (this.slowTimer <= 0) this.slowEffect = 0
}
// 沿路径移动
this.moveAlongPath(delta)
// 更新血条位置
this.updateHealthBar()
this.updateQuoteText()
}
protected moveAlongPath(delta: number): void {
if (this.currentPathIndex >= this.pathPoints.length - 1) {
// 到达终点
this.reachEnd()
return
}
const target = this.pathPoints[this.currentPathIndex + 1]
const currentSpeed = this.speed * (1 - this.slowEffect)
const distance = currentSpeed * delta / 1000
// 向目标移动
... target distance ...
// 到达当前路径点后切换到下一个
...
}
takeDamage(damage: number): void {
if (this.shieldCount > 0) {
this.shieldCount--
return // 护盾吸收
}
this.hp -= damage
this.updateHealthBar()
if (this.hp <= 0) {
this.die()
}
}
addDOT(damage: number, duration: number): void {
this.dotEffects.push({ damage, duration, timer: duration })
}
addSlow(percent: number, duration: number): void {
this.slowEffect = Math.max(this.slowEffect, percent)
this.slowTimer = Math.max(this.slowTimer, duration)
}
protected die(): void {
this.isDead = true
const manager = GameManager.getInstance()
manager.addHC(this.hcReward)
this.onDeath() // 子类可重写碰瓷王扣HC
this.destroy()
}
protected reachEnd(): void {
const manager = GameManager.getInstance()
manager.reduceKPI(this.kpiDamage)
this.isDead = true
this.destroy()
}
protected onDeath(): void {} // 子类重写
abstract drawSprite(): void
abstract getQuote(): string // 返回该怪物的语录
destroy(): void {
this.sprite?.destroy()
this.healthBar?.destroy()
this.quoteText?.destroy()
}
}
```
**4 种怪物:**
`FreshGraduate`(校招应届生):
- HP: 30, 速度: 120px/s, KPI扣减: 2%, HC奖励: 10
- 护盾: 0
- 视觉小绿点Graphics 小圆)
- 语录随机:["求转正!", "我愿意加班!", "卷!卷!卷!"]
`OldEmployee`35岁老员工
- HP: 150, 速度: 50px/s, KPI扣减: 8%, HC奖励: 30
- 护盾: 3层shieldCount=3前3次攻击无效
- 视觉:大蓝方块
- 语录:["我为公司立过功!", "我有10年经验", "年龄不是问题!"]
`TroubleMaker`(职业碰瓷王):
- HP: 80, 速度: 80px/s, KPI扣减: 5%, HC奖励: 20
- 特殊:死亡时重写 `onDeath()` 扣玩家20 HC劳动仲裁
- 视觉:红色叹号形状
- 语录:["录音笔已开启", "这是违法的!", "我要仲裁!"]
`BossVP`空降VP
- HP: 800, 速度: 40px/s, KPI扣减: 30%, HC奖励: 150
- 技能"组织架构调整"每20秒触发一次随机摧毁场上一个防御塔
- 视觉:大金色六边形,周围有特效
- 语录:["我来教大家怎么做事", "你们缺乏战略眼光", "这不是执行力的问题"]
- BOSS出现时全屏红色闪光效果Phaser Camera flash
**`game/enemies/EnemyPool.ts`**(对象池):
简单实现:维护一个 inactive 数组acquire 时取出复用release 时放回。
**`game/enemies/WaveManager.ts`**(波次管理器):
```typescript
// 6波配置
const WAVE_CONFIG = [
{ enemies: [{ type: 'FreshGraduate', count: 10, interval: 800 }] }, // 第1波
{ enemies: [{ type: 'FreshGraduate', count: 8, interval: 800 }, { type: 'OldEmployee', count: 3, interval: 2000 }] },
{ enemies: [{ type: 'OldEmployee', count: 5, interval: 2000 }, { type: 'TroubleMaker', count: 3, interval: 1500 }] }, // 周报前
{ enemies: [{ type: 'FreshGraduate', count: 12, interval: 600 }, { type: 'TroubleMaker', count: 2, interval: 1500 }] },
{ enemies: [{ type: 'OldEmployee', count: 6, interval: 1500 }, { type: 'TroubleMaker', count: 3, interval: 1500 }] },
{ enemies: [{ type: 'BossVP', count: 1, interval: 0 }, { type: 'OldEmployee', count: 4, interval: 2000 }] }, // Boss波
]
export class WaveManager {
private currentWave: number = 0
private isSpawning: boolean = false
private waveComplete: boolean = false
// 周报触发第3波结束后
private onWave3Complete: () => void
// 胜利第6波全部消灭
private onAllWavesComplete: () => void
startNextWave(): void { ... }
private spawnEnemy(type: string): EnemyBase { ... }
update(delta: number): void { ... } // 每帧更新所有活跃怪物
getAllActiveEnemies(): EnemyBase[] { ... }
}
```
**路径像素坐标计算**
PATH_WAYPOINTS 是格子坐标,需要转换为 Phaser Canvas 像素坐标:
```typescript
// 格子坐标转像素坐标(格子中心)
function gridToPixel(gridX, gridY): { x, y } {
return {
x: gridX * TILE_SIZE + TILE_SIZE / 2,
y: gridY * TILE_SIZE + TILE_SIZE / 2 + HUD_HEIGHT // 加上顶部HUD高度
}
}
```
完整路径像素坐标通过对 PATH_WAYPOINTS 相邻点之间插值生成:
```typescript
// 将 PATH_WAYPOINTS 扩展为完整的转折路径(每个转折点都在路径中)
function buildFullPath(): { x, y }[] {
const points = []
for (let i = 0; i < PATH_WAYPOINTS.length - 1; i++) {
const from = gridToPixel(PATH_WAYPOINTS[i].x, PATH_WAYPOINTS[i].y)
const to = gridToPixel(PATH_WAYPOINTS[i+1].x, PATH_WAYPOINTS[i+1].y)
points.push(from)
points.push(to)
}
// 去重相邻相同点
return points.filter((p, i, arr) =>
i === 0 || p.x !== arr[i-1].x || p.y !== arr[i-1].y
)
}
```
---
### HUD 与战斗整合
更新 `game/GameScene.ts`,集成两个新系统:
1. 在场景 `create()` 中初始化 TowerManager 和 WaveManager
2. 在场景 `update(time, delta)` 中:
- 调用 `towerManager.update(delta, waveManager.getAllActiveEnemies())`
- 调用 `waveManager.update(delta)`
- 更新 HUDKPI条、HC显示
3. 处理格子点击事件:若处于"建塔模式",调用 `towerManager.placeTower(gridX, gridY, selectedTowerType)`
4. 添加"开始下一波"按钮(按钮文字"召唤下一波"
5. 波次开始时显示波次提示文字("第X波来袭"
---
### 视觉规范Graphics 绘制指南,无美术资源)
所有游戏对象用 Phaser.GameObjects.Graphics 绘制:
| 对象 | 形状 | 颜色 |
|------|------|------|
| 00后实习生 | 小圆(r=12) + 十字形身体 | `#22C55E`(绿) |
| P6资深开发 | 方块(24x24) + `</>` 文字 | `#3B82F6`(蓝) |
| PPT大师 | 圆(r=16) + 圆心白点 | `#F59E0B`(橙) |
| HRBP | 菱形(16x16) | `#EC4899`(粉) |
| 校招应届生 | 小圆(r=8) | `#86EFAC`(浅绿) |
| 35岁老员工 | 方块(20x20) + 护盾外框 | `#93C5FD`(浅蓝) |
| 职业碰瓷王 | 三角形 | `#FCA5A5`(浅红) |
| 空降VP | 六边形(r=30) + 金色外框 | `#FBBF24`(金色) |
塔的精力条:宽 40px高 4px黄色 `#F59E0B`,底部深灰 `#374151`
怪物血条:宽 30px高 4px绿→红渐变按血量百分比
---
## 验收标准
1. 可以在地图格子上部署 4 种塔,扣除正确 HC
2. 点击已部署的塔显示精力信息,可以花 10HC 购买咖啡
3. 点击"召唤下一波"按钮,怪物从起点出现并沿 S 型路径移动到终点
4. 塔会自动攻击射程内的怪物
5. 怪物被消灭后奖励 HC到达终点后扣除 KPI
6. KPI 条和 HC 显示实时更新
7. 35岁老员工前 3 次攻击不掉血(护盾)
8. 职业碰瓷王死亡时扣除玩家 20 HC
9. 完整 6 波可以正常触发
## 相关上下文
- 设计系统:`design-system/大厂保卫战/MASTER.md`
- PRD`.docs/prd-tower-defense-game.md`
- 计划文档:`.docs/plans/2026-03-21-tower-defense-game.md`
- Task 1A+1B 的产出(已在 `game/` 目录中)
- 开发规范:`@rules/dev-best-practices.md`**必须先阅读**
完成开发后,按 `@rules/atomic-commit.md` 规范,将变更按逻辑模块分组提交(禁止 `git add .`),确保所有文件均已 commit 后再结束任务。