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

设计图有了,本节把"空房子"盖起来: ① 确认开发环境(16.3.1:Visual Studio + 「使用 C++ 的桌面开发」工作负载 + Unicode 字符集); ② 准备素材(16.3.2:图片、地图文件放哪儿、怎么找得到); ③ 创建工程(16.3.3:建项目 + 按 16.2 的类骨架拆分文件)。 本节结束的标志:一个能弹窗、能编译、能读到文件的"空壳工程"。

📝 本节和前面几节的关系

16.2 定义了"有哪些类",本节把这些类真的变成磁盘上的文件。 - 和 15.1.2「类库的使用」的关系:那节讲文件路径的参照点是"程序(exe)所在目录", 本节把这条规律用在了"素材怎么找得到"上(第二部分的坑全在这里); - 和 10.6.1「使用命名空间」的关系:工程一大,头文件重复包含是常事, 本节会补上"头文件保护"这个必备动作。 一句话:上一节画图纸,本节立框架。

一、开发环境要求(16.3.1)

1. 装什么

项目 要求 为什么
IDE Visual Studio Community(或更高版本) 提供 MSVC 编译器 + Windows SDK
工作负载 必须勾「使用 C++ 的桌面开发」 不勾就没有 Windows API 的头文件和库
平台工具集 默认(v143 一类)即可 不用改
运行平台 Win32 / x64 都能跑 本章代码不挑位数

Dev-C++ 用惯了怎么办? —— 本章请装 Visual Studio。 原因 16.1 已说过:本章要 Windows API + GDI+,Dev-C++ 面向控制台,不合适。 装 VS 不是"换语言",是"换工具" —— 代码仍然是 C++,一个字没变。

2. 一个必知配置:字符集

Visual Studio 默认用 Unicode 字符集,这带来两条直接影响:

// Unicode 下,字符串前要加 L(宽字符)
TextOut(hdc, 20, 20, L"坦克动荡", 4);     // 对:L"..."
// TextOut(hdc, 20, 20, "坦克动荡", 4);   // 错:编译报"无法转换"

// 如果教材用的是"多字节字符集"(老版本默认),则不用加 L

怎么确认自己在哪一档:项目属性 → 配置属性 → 高级 → 字符集。 本章统一按 Unicode 写(也就是所有中文字面量前面加 L)。

这不是小细节:字符集选错,大半的错误信息都长一个样 ——「无法从 const char* 转换为 LPCWSTR」。 遇到这个报错,第一件事就是查字符集。

二、游戏所用资源(16.3.2)

1. 需要哪些资源

资源 用途 本章示例怎么处理
坦克图片 画出坦克 用 GDI+ 画三角形/矩形代替图片(见下)
子弹图片 画出子弹 画小圆点代替
地图块 地板、墙 画填充矩形代替
菜单背景 主菜单底图 画渐变背景代替
地图文件 记录墙在哪 真的用文本文件(15 章的账)

为什么本章示例不依赖外部图片? —— 因为图片有版权、也容易丢,而你一旦学会"画",换成"贴图"只差一个函数。 本章先用图形把逻辑跑通;想贴图时,16.5.4 会给出替换方法。

2. 资源放哪儿:一条最容易踩的坑

程序找文件,是按"相对路径"找的,而相对路径的参照点是【可执行文件(exe)所在目录】,不是源代码所在目录。

你写在代码里的路径 实际去哪儿找
"map.txt" exe 旁边(x64/Debug 或 x64/Release 目录下)
"res\\map.txt" exe 旁边的 res 子目录
"D:\\game\\map.txt" 绝对路径(换台电脑就失效)

所以素材要放对地方,最省事的两种做法:

  1. 把 res 文件夹放到 exe 输出目录下(改代码时不用动);
  2. 在项目属性 → 调试 → 工作目录里,把工作目录设成你的素材目录(调试时省心)。

注意第 1 条里那个 \\ —— Windows 路径里的反斜杠要写两个("res\\map.txt"), 因为单个 \ 在 C++ 字符串里是"转义符"(15.1.2 讲过)。

三、创建新项目(16.3.3)

第 1 步:建工程

  1. 打开 Visual Studio → 创建新项目;
  2. 搜索框输入 Windows 桌面 → 选 「Windows 桌面应用程序」(C++ / Windows / 桌面);
  3. 项目名填 TankTurmoil,位置选一个纯英文、无空格的目录;
  4. 点创建 —— VS 会生成一个已经能弹窗的模板。

先别改代码,直接按 F5 跑一遍 —— 能弹出窗口,说明环境没问题。

第 2 步:认识工程结构

TankTurmoil/
├── TankTurmoil.sln          ← 解决方案(VS 用)
├── TankTurmoil.vcxproj      ← 项目文件(配置、包含哪些源文件)
├── TankTurmoil.cpp          ← 主程序(含 WinMain 和 WndProc)
└── (你新建的文件都放这里)

本章约定:一个类一对文件(.h 放声明、.cpp 放实现)—— 这是 C++ 工程的通行做法。

第 3 步:按 16.2 的设计图拆文件

照着上一节的类骨架,建这些文件(先只写声明,实现留空):

CMovable.h     ← 可移动对象(父类)
CMovable.cpp
CTank.h / CTank.cpp         ← 坦克
CBullet.h / CBullet.cpp     ← 子弹
CMap.h / CMap.cpp           ← 地图
CGame.h / CGame.cpp         ← 总调度

头文件必须加"头文件保护",否则重复包含会报"重复定义":

// CMovable.h
#ifndef CMOVABLE_H          // ← 这三行叫"头文件保护"
#define CMOVABLE_H          //    第一次包含时定义宏,之后再包含就跳过

#include <string>
#include <vector>

class CMovable {
public:
    double x, y, angle;
    bool   alive;
    CMovable() : x(0), y(0), angle(0), alive(true) {}
    virtual ~CMovable() {}
    virtual void move() = 0;
    virtual void draw() = 0;
};

#endif                      // ← 保护结束

C++11 起也可以写 #pragma once(一行搞定,VS 支持),效果一样、更省事。

四、让"空壳工程"跑起来

目标:只验证"类能编译、文件能读到",不急着做游戏。

第 1 步:把主程序改成"读一个地图文件",先建一个 res 目录,放一个 map.txt:

##########
#........#
#..####..#
#..#..#..#
#..####..#
#........#
##########

(# 是墙,. 是空地 —— 一行地图就是一个字符串,正好对上 16.2 的设计。)

第 2 步:在主程序里读它并打印到窗口标题上:

// 把地图读进来,读到了就把行数显示在窗口标题栏
void TryLoadMap(HWND hWnd) {
    std::ifstream in("res\\map.txt");          // ★ 相对路径:相对 exe
    if (!in) {                                 // ★ 15.1.2 的纪律:立刻检查
        SetWindowTextW(hWnd, L"地图读取失败:检查 res\\map.txt 是否在 exe 旁边");
        return;
    }
    int rows = 0;
    std::string line;
    while (std::getline(in, line)) ++rows;     // 15.3.3 的"读操作进条件"
    wchar_t buf[64];
    swprintf_s(buf, L"地图读取成功,共 %d 行", rows);
    SetWindowTextW(hWnd, buf);
}

看到什么算成功:

读结果:

五、30 秒实验

  1. 把 res\map.txt 里的 # 改成 * 再跑 → 一样能读到(因为我们只数行数,不解析内容)。说明"读进来"和"看懂"是两件事。
  2. 在项目属性里把字符集改成"多字节",把 L"..." 去掉 → 能编译过。体会字符集换了,代码也得跟着换。
  3. 删掉 CMovable.h 的两行保护宏(#ifndef / #endif),在 main.cpp 里 #include 它两次 → 报"重复定义"。这就是头文件保护存在的理由。

六、常见误区

  1. "素材放源代码目录就行" —— 不行。程序找的是 exe 目录(本节第二部分)。素材放错,代码写得再对也读不到。
  2. "报错 无法从 const char* 转换为 LPCWSTR 是代码写错了" —— 先查字符集(本节第一部分)。Unicode 下字符串要加 L。
  3. "新建的项目不用先跑就改代码" —— 先跑一遍。能弹窗说明环境没问题,否则后面出错时分不清是环境还是代码。
  4. "头文件保护可写可不写" —— 必须写(#ifndef 三行或 #pragma once)。不然只要有两个文件相互包含,编译就报重复定义。
  5. "路径里的 \ 写一个就行" —— 写两个("res\\map.txt"),因为 \ 在字符串里是转义符(15.1.2 的账)。
  6. "先把所有类都实现完再运行" —— 不要。本节只验证"能编译、能读文件";实现留到后面几节,一次加一点。
自测三问(点开对答案) 1. **程序按相对路径找文件时,参照点是哪里?素材该怎么放?** —— **参照点是【可执行文件(exe)所在目录】**,不是源码目录。**做法:把 `res` 文件夹放到 exe 输出目录下(或在项目属性→调试→工作目录里指定素材目录)。** 2. **Windows 路径里为什么 `"res\map.txt"` 是错的?** —— **因为 `\` 在 C++ 字符串里是转义符**(`\n`、`\t` 就是这么来的)。**要写 `"res\\map.txt"`(写两个反斜杠)。** 3. **头文件保护(`#ifndef` / `#define` / `#endif`)是干什么的?** —— **防止同一个头文件在一次编译里被包含两次**(导致"重复定义")。**等价写法是 `#pragma once`。**

相关笔记