《坦克动荡》是一个实时对战小游戏:迷宫地图里两台坦克互相攻击,子弹撞墙会反弹, 而反弹回来的子弹照样能打爆自己 —— 这就是"动荡"两个字的由来。 本章是全书的收尾项目:前面十五章学的是零件,这一章要把它们装成一台能跑起来的机器。
第十五章解决的是「数据怎么活过这一次运行」(文件),本章解决的是「这些东西怎么动起来、怎么被看见」。
本章是一章合并结算课,把前面几乎所有的账都结一遍:
- 10~11 章(类与对象、继承与多态)→ 游戏对象体系(坦克、子弹、地图都是一种"对象");
- 12~13 章(模板与 STL)→ 用 vector 装一堆对象、用算法做碰撞检测;
- 14 章(RTTI 与异常)→ 出错时不让游戏直接崩掉;
- 15 章(文件操作)→ 读地图、存成绩、加载素材。
一句话:本章不教新语法,只教「怎么把学过的用在一起」。
一、这是一个什么游戏
《坦克动荡》(Tank Trouble)的规则,五句话能说完:
- 一张迷宫地图 —— 由"墙"和"空地"组成的格子阵;
- 两台坦克 —— 一个玩家控制一台(也可以人机对战、双人对战);
- 坦克能移动、能转向 —— 而且不是只走上下左右四方向,是任意角度自由开;
- 炮弹撞墙会反弹 —— 弹一次、两次、很多次都行;
- 谁被打中谁出局 —— 而打中你的,很可能是你自己反弹回来的那一发。
第 5 条是这个游戏和"坦克大战"最大的区别:
| 传统坦克大战 | 坦克动荡 | |
|---|---|---|
| 移动方向 | 上 / 下 / 左 / 右 四方向 | 任意角度 |
| 子弹 | 打墙就消失 | 打墙会反弹 |
| 最刺激的地方 | 抢老家、守基地 | 算好角度 —— 因为乱开枪会打死自己 |
"算角度"就是这个游戏的核心乐趣 —— 地图越复杂、能反弹的路线越多,玩起来越像打台球。
二、为什么拿它当收尾项目
它是整本书里知识覆盖面最广的一个例子:
| 你会用到的 | 来自哪几章 | 在这个游戏里干什么 |
|---|---|---|
| 类与对象 | 10 章 | 把坦克、子弹、地图都封装成"类" |
| 继承与多态 | 11 章 | 坦克、子弹都是"可移动对象"的子类 |
| 虚函数 | 11.4 | 让"移动""绘制"各自表现不同 |
vector |
13.1 | 装一堆子弹(子弹会越打越多) |
| 排序 / 查找 | 13.3 | 自动寻路时挑"走得最近的那一步" |
| 文件操作 | 15 章 | 存地图文件、读图片素材 |
| 异常处理 | 14.2 | 图片加载失败时不崩、给个提示 |
一句话:它不是"又一个例子",而是"一次结业考"。
三、开工前要知道的三件事(尤其第一件)
第 1 件:这次要换工具了
本书前面十五章一直用 Dev-C++(写控制台程序)。本章必须换到 Visual Studio —— 因为:
- 我们要做的不是黑框框,而是带窗口、带图片、带鼠标的游戏;
- 这需要 Windows API(管窗口和消息)和 GDI+(管绘图和图片);
- 这两样东西只有 Windows 上的 MSVC 工具链(Visual Studio)能顺畅地用。
不是 Dev-C++ 不好,而是它面向控制台,写窗口程序不合适。工具要跟着任务换。
本节后面第五部分会手把手把 Visual Studio 装上,并跑出第一个窗口。
第 2 件:本章要用的三样东西
| 东西 | 一句话说明 | 负责什么 |
|---|---|---|
| Windows API | 操作系统提供的一套 C 函数 | 建窗口、收鼠标键盘、开定时器 |
| GDI+ | Windows 的绘图层 | 画线、画图、贴图片 |
| C++ 标准库 | 你已经学的那些 | 组织对象、装容器、读写文件 |
前两样是新面孔,第三样是你的老朋友。 本章的新知识,九成都在前两样上。
第 3 件:思路比代码重要
本章代码量比前面任何一章都大,但真正要带走的是三个思路:
- 游戏循环 —— 程序不再是"从上到下跑一遍就结束",而是每秒重复几十次;
- 对象分工 —— 谁负责画、谁负责动、谁负责算碰撞;
- 状态管理 —— 菜单、游戏中、结算,同一个窗口在不同状态下做不同的事。
这三件事想清楚了,代码是水到渠成;想不清楚,代码抄下来也改不动。
四、本章的八节怎么走
| 节 | 干什么 | 一句话 |
|---|---|---|
| 16.1 | 开发背景 | 做什么、为什么、用什么(就是本节) |
| 16.2 | 系统功能设计 | 先把功能列清楚、把流程画出来,再动手 |
| 16.3 | 创建项目 | 建工程、备素材、把空窗口跑起来 |
| 16.4 | 创建游戏框架 | 窗口 + 游戏循环 + 鼠标响应 |
| 16.5 | 绘图库 GDI+ 的使用 | 学会"往屏幕上画东西" |
| 16.6 | 游戏功能的详细设计 | 坦克、子弹、地图、碰撞、寻路 |
| 16.7 | 实现完整的游戏 | 把所有零件装进一个 CGame 类 |
| 16.8 | 本章总结 | 收账 + 全书收官 |
注意顺序:先设计(16.2)再动手(16.3 起) —— 这是本章想教的第一条工程习惯。
五、动手 Demo:把"黑框框"换成"窗口"
第 1 步:装 Visual Studio
- 到微软官网下载 Visual Studio Community(社区版,免费);
- 安装时在"工作负载"里勾上「使用 C++ 的桌面开发」 —— 这一项必须勾,否则后面建不了 C++ 项目;
- 装完打开,确认能看到"创建新项目"。
第 2 步:跑一个最小的窗口程序
新建 → 选 Windows 桌面应用程序(语言 C++ / 平台 Windows / 类型 桌面)→ 项目名填 TankTurmoil。
把生成代码里的消息处理函数 WndProc 改一处,让它往窗口上画一行字:
// 消息处理函数:系统把"窗口需要重画"的消息发到这里
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_PAINT: { // 系统说:这块地方要重画了
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps); // 开始画
// 先用 GDI 的"画字"函数试水(16.5 会换成 GDI+)
TextOut(hdc, 20, 20, L"坦克动荡 · 窗口已就绪", 11);
EndPaint(hWnd, &ps); // 画完了
return 0;
}
case WM_DESTROY: // 窗口被关掉
PostQuitMessage(0); // 让消息循环退出 → 程序结束
return 0;
}
return DefWindowProc(hWnd, msg, wParam, lParam); // 其余消息交给系统默认处理
}
看到什么算成功:
- 编译运行后弹出一个白色窗口;
- 窗口左上角有「坦克动荡 · 窗口已就绪」几个字;
- 点右上角关掉窗口,程序正常退出(不报错、任务管理器里没有残留进程)。
读结果:
WM_PAINT是"窗口需要重画"的消息 —— Windows 不是"你想画就画",而是"系统叫你来画"(16.5 会细讲这套机制);WM_DESTROY里那句PostQuitMessage(0)是"关闭窗口 = 结束程序"的开关 —— 去掉它,窗口关了但进程还在;TextOut是 GDI 的老函数,本章后面会改用 GDI+,但先跑通"能画东西"这一步最重要。
六、30 秒实验
- 把
TextOut那一行注释掉再跑 → 窗口还在,但一片空白。说明"窗口存在"和"窗口上有内容"是两件事。 - 把
WM_DESTROY分支整段注释掉再跑 → 关掉窗口后,任务管理器里还能看到进程。这就是"消息循环没退出"。 - 把窗口样式里的
WS_OVERLAPPEDWINDOW换成只带WS_BORDER→ 窗口不能改大小、没有最大化按钮。体会"窗口样式"就是一堆选项按位或起来(和 15.1.3 的位标志一个套路)。
七、常见误区
- "我 Dev-C++ 用得挺熟,本章也能用" —— 不行。Dev-C++ 面向控制台,本章要窗口和 GDI+,请用 Visual Studio。工具跟着任务换,不是工具不好。
- "窗口程序就是
main里一路cout" —— 不是。窗口程序的核心是消息循环:程序不"从头跑到尾",而是等系统发消息、你处理消息。 - "照着抄完代码就算学会了" —— 不算。本节第三部分那三个思路(游戏循环、对象分工、状态管理)才是要带走的;代码只是它们的载体。
- "应该先把坦克、子弹都写出来再调试" —— 反过来。本章顺序是先设计 → 再建框架 → 再画图 → 最后堆功能。一次只让一个新东西动起来。
- "游戏和普通程序是两个世界" —— 不是。底子还是你学过的:类、继承、容器、文件 —— 只不过外面套了一层"窗口"的壳。
自测三问(点开对答案)
1. **《坦克动荡》和传统坦克大战最大的区别是什么?** —— **子弹打墙会反弹,反弹的子弹能打到自己**;而且**坦克可以任意角度移动**,不是四方向。**"算角度"成了核心玩法。** 2. **为什么这一章必须从 Dev-C++ 换成 Visual Studio?** —— **因为本章要写窗口程序**(需要 Windows API 和 GDI+),**而 Dev-C++ 面向控制台,不适合**。**工具要跟着任务换。** 3. **本章真正要学的三个"思路"是什么?** —— **① 游戏循环**(每秒重复几十次);**② 对象分工**(谁画、谁动、谁算碰撞);**③ 状态管理**(菜单 / 游戏中 / 结算,同一窗口做不同事)。- ☐ 下一节(16.2 系统功能设计)