🔷 第十六章 坦克动荡游戏
📄 一句话概括

本章把前面十五年的零散知识装成了一个能跑的窗口游戏: 一张知识地图 + 十个必记结论 + 三张判断表 + 两关自测。 更重要的是 —— 这一节也是全书的收官:《零基础学C++》16 章,到此读完。

📝 本节和前面几节的关系

这是全书最后一节。它不像别的小结只回首一章,它要回首整整十六本账: - 14.3 / 15.7 的小结只收一章;本节要收"从 C++ 概述到游戏完成"的全过程; - 本节的"三张判断表",是把本章最容易混的三个选择(窗口还是控制台、 GDI 还是 GDI+、数组还是 vector)固化成速查; - "两关自测" 是给这一章做的结业考(比前几章的小结更硬 —— 因为这一章本身就是综合题)。 一句话:前面是"学",这一节是"结账"。

一、本章知识地图

                        第十六章 坦克动荡游戏
                                 │
   ┌─────────────┬───────────────┼───────────────┬─────────────┐
   │             │               │               │             │
【背景与设计】  【工程与框架】  【绘图】      【游戏功能】    【整合】
   │             │               │               │             │
16.1 开发背景  16.3 创建项目   16.5 GDI+      16.6 详细设计  16.7 实现
16.2 系统设计  16.4 游戏框架         │               │             │
   │             │            ├ 引入/初始化  ├ 可移动对象   ├ 公有接口
├ 功能结构     ├ 主窗口        ├ 画帧数       ├ 坦克/子弹    ├ 私有帮手
├ 业务流程     ├ 游戏循环      ├ 图片资源     ├ 地图/轨迹    ├ update
└ 功能→类      ├ 鼠标响应      └ 菜单背景     ├ 碰撞/反弹    ├ render
               └ 接进窗口                     ├ 自动寻路     └ 接进主窗口
                                              ├ 菜单
                                              └ 单人/双人

全章 8 节,一条主线: 先想清楚(16.1~16.2)→ 把架子搭起来(16.3~16.5)→ 往里填内容(16.6)→ 拧成一台机器(16.7)。

二、必须记住的十个结论

  1. 游戏和普通程序的分水岭是"游戏主循环" —— while (运行中) { 输入 → 更新 → 绘制 },每秒几十次,永不结束。
  2. 游戏循环必须用 PeekMessage,不能用 GetMessage —— 后者没消息会睡着,游戏逻辑就不走了。
  3. "请求重画"≠"真的重画" —— 用 InvalidateRect 请求,系统发 WM_PAINT 给你画;合法的 hdc 只在那里拿得到。
  4. GDI+ 用前 GdiplusStartup、用后 GdiplusShutdown —— 成对出现,位置分别在程序的开头和结尾。
  5. 屏幕坐标 y 轴向下 —— 0° 朝右,90° 朝下;角度制与弧度制只在 cos/sin 前转换。
  6. 移动要"分轴" —— 横向被挡就只走纵向,这样撞墙能贴墙滑行而不是卡死。
  7. 子弹反弹两个公式 —— 撞竖墙 a' = 180° − a;撞横墙 a' = −a —— 两个都要写。
  8. 碰撞检测比平方,不开方 —— d² ≤ r² 就够了,每帧算几十次,省下来很实在。
  9. "固定用数组,可变用容器" —— 坦克 2 台用 CTank[2];子弹数量会变 → std::vector。
  10. 状态机让"同一个窗口做不同的事" —— 一个 state + switch,菜单 / 游戏中 / 结算三张画面共用一个 render。

三、三张判断表

表一:这个程序用控制台还是窗口?

你的程序要…… 用 为什么
算数、读文件、练语法 控制台(Dev-C++ 也行) 简单、专注逻辑
显示图形、收鼠标、做游戏/软件界面 窗口(Visual Studio) 需要 Windows API + GDI+

表二:用 GDI 还是 GDI+?

你的需求 用 说明
画几条直线、不讲究好看 GDI 老函数(TextOut/Rectangle)
要抗锯齿、渐变、半透明、读 PNG GDI+ 本章的选择

表三:这个数据用数组还是 vector?

数据的特点 用
数量固定(两台坦克、一张地图的行) 数组 / std::string
数量会变(子弹越打越多、越清越少) std::vector

四、两关自测

第一关:给游戏加一个"计时器"

要求:在主画面上显示本局已经进行了多少秒,并做到:

  1. 只在 ST_PLAYING 状态下计时(菜单里不计、结算后停);
  2. 显示在右上角(左上角已经被帧数占了);
  3. 用 GetTickCount() 算时间差,不要用 frame 除以 60 硬凑(帧率不是恒定的)。

参考思路(不唯一):

// CGame 里加两个成员
DWORD startTick;      // 本局开始的时刻
int   seconds;        // 已经过去的秒数

// startGame 里
startTick = GetTickCount();
seconds   = 0;

// update 里(在 state 检查之后)
seconds = (GetTickCount() - startTick) / 1000;   // 毫秒 → 秒

// drawWorld 里
wchar_t buf[64];
swprintf_s(buf, L"时间: %d 秒", seconds);
Font f(&ff, 16, FontStyleRegular, UnitPixel);
g.DrawString(buf, -1, &f, PointF(640, 10), &yellow);   // 右上角

验收:菜单里数字不动;开局后每秒 +1;结算后停住;重开一局归零。

第二关:十五个坑,逐个判断

# 说法 对不对 为什么
1 主循环用 GetMessage 就够了 ❌ 没消息会阻塞,游戏逻辑不跑
2 在循环里直接 render(hdc) ❌ 拿不到合法 hdc,要走 WM_PAINT
3 InvalidateRect 之后立刻会画 ❌ 只是"请求",系统决定时机
4 忘记 GdiplusShutdown 无所谓 ❌ 资源不回收
5 角度可以用弧度和角度混着写 ❌ 本章统一角度制
6 坦克撞墙时整步取消移动 ❌ 会卡死;要分轴移动
7 子弹反弹统一写 angle = -angle ❌ 只对横墙;竖墙是 180 − angle
8 碰撞检测必须开方 ❌ 比平方就够
9 Graphics 旋转后要 Restore ✅ 否则后画的全歪
10 子弹用固定数组更方便 ❌ 数量会变,用 vector
11 菜单必须用按钮控件 ❌ 矩形 + 命中测试即可
12 单人/双人要写两套移动逻辑 ❌ 一个 isComputer 统一
13 update 里先判胜负再处理子弹 ❌ 顺序反了,会漏判
14 图片加载失败会报错 ❌ 安静失败,要查 GetLastStatus()
15 每帧要"擦掉"上一帧的子弹 ❌ 整幅重画,不需要局部擦除

得分:答对 15 个 → 这一章真的过了;答对 10~14 个 → 回去翻对应小节;10 个以下 → 16.4 ~ 16.7 重读一遍。

五、全书收官:这十六本账

回顾一下,从"什么是 C++"到"写完一个游戏",你都学了什么:

阶段 章 一句话
入门 第 1~2 章 认识 C++、认识环境、会读会写最基本的程序
语法基础 第 3~6 章 运算符、分支、循环、函数 —— 程序的三块积木
数据 第 7~8 章 数组、指针、引用、结构体 —— 数据怎么摆
面向对象 第 9~11 章 类、继承、多态 —— 代码怎么组织
泛型与标准库 第 12~13 章 模板、STL —— 不再重复造轮子
进阶主题 第 14~15 章 RTTI、异常、文件 —— 出错不崩、数据能存
综合实战 第 16 章 把以上全部用一遍,做出一个完整的游戏

这本教材到此,全部完结。 🎉

三个"带得走的"东西(比语法更值钱):

  1. "分而治之"的习惯 —— 大问题拆成小问题,小问题拆成函数和类;
  2. "先设计后动手"的习惯 —— 16.2 那一节不是走过场;
  3. "立刻检查"的习惯 —— 打开文件要检查、加载图片要检查、分配内存要检查。

接下来可以往哪儿走(几个方向,任选):

方向 学什么 为什么
图形界面 Qt 跨平台、语法优雅,比 Win32 舒服得多
游戏开发 Unreal / Godot 本章的游戏循环和对象体系是基础
算法功底 数据结构与算法 本章的 BFS 只是一块敲门砖
现代 C++ C++11/14/17/20 特性 auto、智能指针、lambda、std::filesystem
继续做项目 把本章的游戏扩展 加音效、加地图编辑器、加联机 —— 一个项目可以吃很久

六、常见误区(本章汇总)

  1. "总结就是再看一遍目录" —— 不是。要把"结论"和"判断表"记下来,下次遇到同类问题直接查表。
  2. "游戏跑起来就行了,不用管结构" —— 不行。加一个功能就乱了 —— 16.2 的设计和 16.7 的分工,都是为了让"加功能"变容易。
  3. "项目做完就扔了" —— 太可惜。把它当模板:加个计分板、加个道具、换张地图 —— 每加一样,都是真本事在长。
  4. "书读完了就学会了" —— 只算"见过"。真正的学会,是自己从零再写一遍 —— 哪怕写得慢、写得丑。
自测三问(点开对答案) 1. **本章八节的主线是什么?** —— **先想清楚(16.1~16.2 背景与设计)→ 搭架子(16.3~16.5 项目 / 框架 / 绘图)→ 填内容(16.6 功能)→ 拧成机器(16.7 整合)。** 2. **这个游戏里,三个"最值钱"的设计决定是什么?** —— **① 用 `CMovable` 抽象出坦克和子弹的共同部分**(**继承 + 多态省一半代码**);**② 用 `isComputer` 统一单人/双人**(**一个标志代替两套逻辑**);**③ 用 `state` 状态机统一三种画面**(**同一个窗口做不同的事**)。 3. **如果让你重写一遍,最想先改哪里?** —— **开放题**。**常见答案:把"全局 `g_map`"改成"CGame 传参"(耦合更低)、把坦克/子弹的碰撞半径做成可配置、把地图格式加个头信息。** **能说出"哪里不够好",说明你真的读懂了这一章。**

相关笔记