设计图有了,本节把"空房子"盖起来: ① 确认开发环境(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" |
绝对路径(换台电脑就失效) |
所以素材要放对地方,最省事的两种做法:
- 把
res文件夹放到 exe 输出目录下(改代码时不用动); - 在项目属性 → 调试 → 工作目录里,把工作目录设成你的素材目录(调试时省心)。
注意第 1 条里那个
\\—— Windows 路径里的反斜杠要写两个("res\\map.txt"), 因为单个\在 C++ 字符串里是"转义符"(15.1.2 讲过)。
三、创建新项目(16.3.3)
第 1 步:建工程
- 打开 Visual Studio → 创建新项目;
- 搜索框输入 Windows 桌面 → 选 「Windows 桌面应用程序」(C++ / Windows / 桌面);
- 项目名填
TankTurmoil,位置选一个纯英文、无空格的目录; - 点创建 —— 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);
}
看到什么算成功:
- 窗口标题栏显示「地图读取成功,共 7 行」;
- 把
res\map.txt改名或挪走,再跑 → 标题栏显示「地图读取失败:…」,程序不崩。
读结果:
- 两行
#include <fstream>、<string>和那句if (!in),就是 15.1.2 的"五步走" —— 本章的游戏代码,本质上还是这些老朋友; - "改名就报失败、但程序不崩" —— 这正是我们想要的效果(读取失败要给提示,而不是白屏);
SetWindowTextW是 Unicode 版(所以才用L"..."),这是本节字符集知识的一次实战。
五、30 秒实验
- 把
res\map.txt里的#改成*再跑 → 一样能读到(因为我们只数行数,不解析内容)。说明"读进来"和"看懂"是两件事。 - 在项目属性里把字符集改成"多字节",把
L"..."去掉 → 能编译过。体会字符集换了,代码也得跟着换。 - 删掉
CMovable.h的两行保护宏(#ifndef/#endif),在main.cpp里#include它两次 → 报"重复定义"。这就是头文件保护存在的理由。
六、常见误区
- "素材放源代码目录就行" —— 不行。程序找的是 exe 目录(本节第二部分)。素材放错,代码写得再对也读不到。
- "报错
无法从 const char* 转换为 LPCWSTR是代码写错了" —— 先查字符集(本节第一部分)。Unicode 下字符串要加L。 - "新建的项目不用先跑就改代码" —— 先跑一遍。能弹窗说明环境没问题,否则后面出错时分不清是环境还是代码。
- "头文件保护可写可不写" —— 必须写(
#ifndef三行或#pragma once)。不然只要有两个文件相互包含,编译就报重复定义。 - "路径里的
\写一个就行" —— 写两个("res\\map.txt"),因为\在字符串里是转义符(15.1.2 的账)。 - "先把所有类都实现完再运行" —— 不要。本节只验证"能编译、能读文件";实现留到后面几节,一次加一点。
自测三问(点开对答案)
1. **程序按相对路径找文件时,参照点是哪里?素材该怎么放?** —— **参照点是【可执行文件(exe)所在目录】**,不是源码目录。**做法:把 `res` 文件夹放到 exe 输出目录下(或在项目属性→调试→工作目录里指定素材目录)。** 2. **Windows 路径里为什么 `"res\map.txt"` 是错的?** —— **因为 `\` 在 C++ 字符串里是转义符**(`\n`、`\t` 就是这么来的)。**要写 `"res\\map.txt"`(写两个反斜杠)。** 3. **头文件保护(`#ifndef` / `#define` / `#endif`)是干什么的?** —— **防止同一个头文件在一次编译里被包含两次**(导致"重复定义")。**等价写法是 `#pragma once`。**- ☐ 下一节(16.4 创建游戏框架)