本文档描述基于 Vue 3 + Phaser 3 重写的塔防游戏核心流程,包含防作弊验证机制。
- 使用现代前端技术栈(Vue 3 + Phaser 3 + TypeScript)重构原游戏
- 实现现代后端技术栈(Django + DRF + PostgreSQL)实现结果验证和积分排行榜
- 保持原游戏的核心玩法和数值体系
- 添加与后端交互的排行榜功能
- 游戏类型: 动态寻路塔防(无限模式)
- 地图规格: 16x16 格子
- 格子大小: 32px
- 帧率: 60 FPS
- 入口: 左上角 (0, 0)
- 出口: 右下角 (15, 15)
- 全局速度系数: 0.1(GLOBAL_SPEED,用于将配置速度转换为每帧实际像素移动量)
- 帧号从 0 开始,每帧递增 1,暂停时不增长
- 所有时间相关记录使用帧号而非系统时间戳
怪物使用动态寻路算法(BFS)从入口移动到出口。
核心特性:
- 独立路径: 每个怪物独立计算路径,不共享。同一位置出发的怪物可能走不同路径
- 动态重算: 怪物每帧有 10% 概率自动重新寻路,遇到障碍时强制重算
- 路径随机性: 寻路回溯时若有多条等距路径,随机选择一条
- 建筑影响: 建筑放置后该格子变为不可通行,影响后续怪物的路径计算
可通行性规则:
- 预设障碍物(
obstacles配置): 不可通行 - 已放置建筑的格子: 不可通行
- 其他格子: 可通行
怪物重新寻路机制:
怪物在以下情况下会重新计算路径:
- 路径列表为空时
- 每步有 10% 概率自动重新寻路(模拟随机移动)
- 下一步格子变为不可通行时(如被新建筑占据)
当建筑放置后阻断了怪物的当前路线,怪物会自动重新寻路,可能选择更远的绕行路线。这是游戏策略的核心部分:玩家可以通过建筑引导怪物走更长的路线,但不能完全阻断所有通路。
建筑放置约束:
建筑放置时需要通过双层检查机制:
- 不能放置在入口或出口格子
- 不能完全阻断入口到出口的所有路径(路径检查)
- 不能使已存在的怪物无法到达出口(怪物检查)
可以阻断怪物的当前最短路线,此时怪物会重新寻路走更远的路线。但如果新建筑会导致某个怪物完全无路可走,则禁止建造。
所有速度公式中的 (24/60) 是帧率补偿系数,用于保持与原 24 FPS 实现相同的实际移动速度。
怪物移动速度 = speed × GLOBAL_SPEED × (24 / 60)
子弹飞行速度 = bullet_speed × 20 × GLOBAL_SPEED × (24 / 60)
建筑攻击间隔(帧) = floor(max(2 / (speed × GLOBAL_SPEED), 1)) × (60 / 24)
子弹速度额外乘以 20 倍系数,确保对高速怪物也能有效追击。攻击间隔与 speed 成反比。
- life: 生命值
- speed: 移动速度(1-40)
- max_speed: 最大速度
- shield: 护盾值(减伤)
- damage: 到达终点造成的伤害(1-10)
- money: 击杀奖励金币
- 0-普通怪
- life: 50, speed: 3, max_speed: 10, shield: 0, damage: 1, money: 5
- 1-稀强怪
- life: 50, speed: 6, max_speed: 20, shield: 1, damage: 2, money: 8
- 2-速度怪
- life: 50, speed: 12, max_speed: 30, shield: 1, damage: 3, money: 10
- 3-血量怪
- life: 500, speed: 5, max_speed: 10, shield: 1, damage: 3, money: 50
- 4-护盾怪
- life: 50, speed: 5, max_speed: 10, shield: 20, damage: 3, money: 30
- 5-伤害怪
- life: 50, speed: 7, max_speed: 14, shield: 2, damage: 10, money: 25
- 6-速度血量怪
- life: 100, speed: 15, max_speed: 30, shield: 3, damage: 3, money: 35
- 7-极速怪
- life: 30, speed: 30, max_speed: 40, shield: 1, damage: 4, money: 20
- 8-护盾血量怪
- life: 300, speed: 3, max_speed: 10, shield: 15, damage: 5, money: 60
- 波次 1-10 使用预定义配置
- 波次 11+ 自动生成:
- 怪物总数 = min(wave^1.1, 100)
- 随机算法:组大小随机 1-3,怪物类型随机 0-8
- 波次间隔:180 帧(3 秒)
- 上波未造成伤害时
- wave < 5: difficulty × 1.05
- difficulty > 30: difficulty × 1.1
- 其他: difficulty × 1.2
- 上波造成伤害时
>= 50点: difficulty × 0.6>= 30点: difficulty × 0.7>= 20点: difficulty × 0.8>= 10点: difficulty × 0.9< 10点且 wave >= 10: difficulty × 1.05
- difficulty 最小值为 1
- damage: 攻击力
- range: 初始射程(格子数),可升级
- max_range: 射程升级上限
- speed: 攻击速度
- bullet_speed: 子弹速度
- life: 塔的生命值
- shield: 塔的护盾值
- cost: 建造费用
- wall (路障)
- damage: 0, range: 0, speed: 0, cost: 5
- 特性: 阻挡路径不攻击
- cannon (炮台)
- damage: 12, range: 4-8, speed: 2, cost: 300
- 特性: 平衡型
- LMG (轻机枪)
- damage: 5, range: 5-10, speed: 3, cost: 100
- 特性: 低攻击大范围经济型
- HMG (重机枪)
- damage: 30, range: 3-5, speed: 3, cost: 800
- 特性: 高攻击小范围
- laser_gun (激光枪)
- damage: 25, range: 6-10, speed: 20, cost: 2000
- 特性: 高速攻击高造价
- 默认: 每级属性 x 1.2
- cannon(炮台): 前 11 次升级 x 1.2,第 12 次升级起 x 1.3(即 Level 2-12 用 1.2,Level 13+ 用 1.3)
- HMG(重机枪): 每级 x 1.3
精度处理:计算过程中保持浮点精度,仅在最终结果时截断(floor)。
- 初始金币: 500
- 初始生命: 100
- 击杀奖励: 根据怪物配置(monster.money 或按公式计算)
波次奖励:
- 每 5 波: +5 生命(不超过 100)
- 每 10 波: +10 生命(不超过 100)
- 生命值降为 0: 失败
- 无限模式: 无胜利条件
每次攻击命中时累加:得分 = floor(sqrt(实际伤害))
- Vue 3.5: UI 框架
- Phaser 3.90: 游戏引擎
- Vite 7.2: 构建工具
- TypeScript 5: 开发语言
- Pinia 3.0: 状态管理
- Axios 1.13: HTTP 客户端
- Python 3.13: 开发语言
- uv: 0.9.15 包管理器
- Django 5.2: Web 框架
- Django REST Framework 3.16: API 框架
- PostgreSQL 15: 数据库
- pytest + pytest-django: 测试框架
- Docker + Gunicorn: 部署
- 服务端权威:怪物属性、建筑属性、波次配置由服务端定义
- 客户端零配置:客户端不内置任何游戏配置和默认状态,所有数值均从服务端获取
- 客户端执行:游戏逻辑在客户端执行,服务端验证结果
- 批量提交:每波结束时批量提交,而非实时上传
- 渐进验证:每波验证一次,而非游戏结束时一次性验证
- 服务端生成:波次配置由服务端随机生成并下发,怪物属性包含随机因子
采用多层验证架构(Level 1 基础验证 + Level 2 伤害验证 + Level 4 统计分析),详见 安全设计文档。
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 页面加载 │───▶│ 放置建筑 │───▶│ 波次进行 │───▶│ 游戏结束 │
│ 创建会话 │ │ 开始动画 │ │ 提交验证 │ │ 提交排名 │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │
▼ ▼ ▼ ▼
POST /sessions 无请求 POST /sessions/wave POST /sessions/end
阶段说明:
- 页面加载: 创建会话,获取游戏配置和第一波怪物数据
- 放置建筑: 本地处理,扣除金钱后开始游戏动画
- 波次进行: 每波结束时提交操作记录,服务端验证后返回下一波配置
- 游戏结束: 提交昵称和最后一波数据,记录排行榜
200:请求成功400:请求参数错误或验证失败404:会话不存在或已过期
所有错误响应使用统一的 JSON 格式:
{
"code": "BUSINESS_ERROR_CODE",
"message": "Human-readable error description"
}code:业务状态码,用于程序判断错误类型message:人类可读的错误描述
MISSING_FIELDS:缺少必填字段SESSION_NOT_FOUND:会话不存在或已过期WAVE_NOT_CONTINUOUS:波次号不连续VALIDATION_FAILED:服务端验证失败(详细信息仅记录在服务端日志)INVALID_NICKNAME:昵称验证失败EARLY_END_REQUIRES_WAVE:提前结束需至少完成一波ZERO_SCORE:零分不能上榜
POST /api/game/sessions:
- 无错误响应(总是成功创建会话)
POST /api/game/sessions/wave:
MISSING_FIELDS(400):缺少必填字段SESSION_NOT_FOUND(404):会话不存在WAVE_NOT_CONTINUOUS(400):波次号不连续VALIDATION_FAILED(400):服务端验证失败
POST /api/game/sessions/end:
MISSING_FIELDS(400):缺少必填字段INVALID_NICKNAME(400):昵称为空、超长或含非法字符SESSION_NOT_FOUND(404):会话不存在VALIDATION_FAILED(400):服务端验证失败EARLY_END_REQUIRES_WAVE(400):提前结束需至少完成一波ZERO_SCORE(400):零分不能上榜
GET /api/game/leaderboard:
- 无错误响应(无效 limit 参数自动回退为默认值 10)
当收到 SESSION_NOT_FOUND 错误时:
- 显示提示告知用户会话已失效
- 自动重启游戏创建新会话
以下内容已拆分到专门的规范文档: