纸牌上的坦克大战
基于 C++17 + SDL2 的 2D 坦克对战游戏,支持三种难度、实时战斗与胜负判定。
功能特性
- 3 档难度:简单(HP 5 / ATK 1)、中等(HP 10 / ATK 2)、困难(HP 15 / ATK 3)
- 实时坦克对战:WASD 移动 + 空格开火,敌人 AI 随机移动与射击
- 战场交互:撞子弹扣血、踩残骸回血 + 提升攻击力、墙壁阻挡
- 胜负判定:消灭全部 10 辆敌方坦克获胜,HP 归零失败
- 背景音乐:循环 BGM 播放(静音自动降级)
- 跨平台:Linux / Windows / macOS,CMake 一键构建
开发环境
| 依赖 |
版本要求 |
| C++ |
17+ |
| CMake |
3.10+ |
| SDL2 |
2.x |
| SDL2_image |
2.x |
| SDL2_mixer |
2.x |
| SDL2_ttf |
2.x |
| 中文字体 |
wqy-microhei 或 noto-cjk(用于界面文本渲染) |
Linux
macOS
Windows
操作指南
| 按键 |
功能 |
| W / ↑ |
向上移动 |
| S / ↓ |
向下移动 |
| A / ← |
向左移动 |
| D / → |
向右移动 |
| 空格 |
发射子弹 |
| ESC |
返回主菜单 |
鼠标左键点击按钮进行菜单导航。
玩法说明
- 主菜单选择开始游戏(使用当前难度设置)或难度设置
- 操控红色坦克在 20×20 地图中移动、射击敌方蓝色坦克
- 踩到敌方坦克残骸(
is_taken)可 +1 HP(上限 5)并 +1 攻击力
- 撞到敌方子弹扣 1 HP
- 击败全部 10 辆敌方坦克即胜利;玩家 HP 归零则失败
- 子弹射到墙壁消失;敌方子弹射到玩家扣 HP 并消失
项目结构
images/ 与 music/ 在 CMake 构建时自动拷贝到 build/ 目录,游戏通过 SDL_GetBasePath() 自动定位可执行文件所在目录加载资源。
核心模块
Game — 状态机主循环
| 职责 |
说明 |
| 场景管理 |
enum class Scene { MENU, LEVEL, GAME, WIN, LOSE, QUIT } 驱动主循环,彻底消除旧版递归函数跳转导致的栈溢出隐患 |
| 事件分发 |
handleEvents() 统一处理 SDL 事件(退出 / ESC / 鼠标点击),根据当前场景路由到不同按钮命中检测 |
| 更新循环 |
updateGame() 每帧执行:玩家输入 → 玩家移动 / 射击 → 敌人 AI 移动 / 射击 → 子弹移动与碰撞 → 地图同步 |
| 渲染调度 |
renderScene() 根据场景调用对应渲染函数 |
场景跳转流程:
Map — 地图与碰撞
| 方法 |
职责 |
reset() |
初始化 20×20 网格:四周为 WALL,内部为 BLANK |
get(x, y) |
返回指定坐标的 CELL_Type 枚举值 |
set(x, y, type) |
写入单元,每帧同步坦克位置 |
peek(x, y, dir) |
查看某方向前方一格的内容,供坦克和子弹碰撞检测 |
Tank — 坦克实体
| 属性 |
说明 |
x, y |
网格坐标 |
hp |
生命值(玩家默认 5,敌人由难度决定) |
attack |
攻击力(决定子弹伤害) |
dir |
方向(UP/DOWN/LEFT/RIGHT) |
isTaken |
仅敌方:残骸是否已被玩家拾取 |
方向向量 dx(dir) / dy(dir) 统一处理四方向位移,消除旧版中 4 份复制粘贴的 switch 逻辑。
Bullet — 子弹实体
| 方法 |
职责 |
fire(x, y, dir, dmg) |
初始化子弹(位置、方向、伤害值) |
peekAt(map) |
检测前方一格的地图单元类型 |
step() |
向前移动一格 |
isLive |
是否存活(碰墙/命中后置 false) |
玩家子弹命中 BLUE_TANK 时遍历敌人数组扣血;敌方子弹命中 RED_TANK 时直接扣玩家 HP。子弹碰 WALL 立即消失。
AssetManager — 资源管理
- 纹理缓存:
std::unordered_map<string, SDL_Texture*> 按路径缓存,避免重复加载
- 字体加载:
TTF_OpenFont() 带多路径回退(wqy-microhei → wqy-zenhei → noto-cjk)
- BGM 管理:
Mix_LoadMUS() + Mix_PlayMusic(-1) 循环播放,失败静默降级
- 生命周期:析构时自动释放全部纹理、字体、音频资源
Renderer — 渲染封装
| 方法 |
职责 |
clear() |
填充黑色背景 |
present() |
提交帧缓冲区到屏幕 |
drawTexture() |
渲染纹理到指定矩形区域 |
drawImage() |
加载图片并渲染(通过 AssetManager 缓存) |
drawText() |
TTF 文字渲染(UTF-8 编码,SDL_RenderCopy 绘制) |
drawInt() |
整数值渲染(std::to_string 后调用 drawText) |
UI — 界面常量
| 常量 |
用途 |
BTN_START |
开始游戏按钮区域 {50, 250, 150, 50} |
BTN_LEVEL |
难度设置按钮区域 {50, 330, 150, 50} |
BTN_QUIT |
退出游戏按钮区域 {50, 410, 150, 50} |
BTN_EASY/MEDIUM/HARD |
难度选择按钮区域 |
BTN_BACK |
返回按钮区域 {30, 550, 150, 50} |
hit(rect, mx, my) |
鼠标命中检测辅助函数 |
计时机制
游戏使用 SDL_GetTicks() 替代跨平台不一致的 clock(),各逻辑模块以毫秒为间隔独立刷新:
| 模块 |
间隔 |
说明 |
| 玩家移动 |
100 ms |
按住方向键连续移动 |
| 玩家射击 |
500 ms |
按住空格连续发射 |
| 敌人移动 |
1000 ms |
随机方向移动 |
| 敌人射击 |
3000 ms |
每个存活敌人发射一发 |
| 玩家子弹 |
100 ms |
逐格前进 |
| 敌方子弹 |
500 ms |
逐格前进 |
版本日志
| 版本 |
变更 |
| v19 |
全面重构:1468 行单文件拆分为 8 个模块,状态机替代递归,全局变量清零,方向向量消除 4x 复制粘贴,修复 EnemiesMove 循环变量遮蔽 bug |
| v18 |
从 Windows/EasyX 迁移到 SDL2,支持跨平台;修复启动黑屏(SDL_GetBasePath 自动定位资源目录) |
| v17 |
修复返回菜单后 InitMap() 未调用导致地图脏数据 |
| v16 |
添加全局 BGM 背景音乐循环播放 |