简介:一套基于 C++ 的“飞翔的小鸟”期末课程设计资源,包含完整源代码与文档说明。面向计算机相关专业在校学生、老师及自学者,可用于期末作业、课程设计、毕业设计或项目初期演示,也适合 K12 阶段编程初学者快速体验 C++ 小游戏开发;重点是让学习者快速获得一个可运行的 C++ 小游戏项目,并理解游戏循环、碰撞检测、键盘交互与简单图形绘制等基础模块。压缩包共 43 个文件、2.25MB,核心内容为 bird.cpp 主程序,配以 Visual Studio 工程文件(sln/vcxproj/filters)、bmp 图像素材、编译过程生成的 obj/pdb/tlog 等中间文件、可直接运行的 exe,以及 README.md 说明文档;分类较清晰,便于按需查看和二次编译。目前已有 104 人学习浏览,代码均经测试运行成功,作者称答辩评审平均分达到 96 分,可直接用于作业演示;解压后建议先阅读 README.md,再配合源码和素材理解实现细节。若基础较好,也可在现有工程上修改逻辑或替换 bmp 图像,扩展出更丰富的玩法;另外,工程内 snake 命名源于最初想做贪吃蛇,实际功能为飞翔的小鸟,阅读时注意区分即可。
1. C++ 期末作业“飞翔的小鸟”:一份能看懂、能改参数、能直接交差的参考源码
课程设计周才开始找 C++ 期末作业参考源码的人,大概率会撞上同一道题目:做一个“飞翔的小鸟”小游戏。这篇笔记拆的,正是一份典型的 C++ 期末作业成果——控制台版飞翔的小鸟,自带完整源代码和文档说明。它没有惊艳的架构,却把 C++ 入门阶段最该会的输入处理、循环结构、碰撞检测和随机数全部串了起来。适合 C++ 刚入门、想用最小成本交掉期末作业的人,也适合想搞懂控制台游戏为什么能跑起来的初学者。看完你能判断它值不值得下,也能照着把代码重写一遍。
2. 拆源码骨架:全局变量、主循环、碰撞函数是这份代码的命门
拿到任何控制台游戏源码,第一个动作不是急着编译,而是先打开 .cpp 文件搜 int main,从 main 往回读。这份飞翔的小鸟源码和 Win32 图形游戏不一样,它没有图形库依赖,地图就是 40×20 的字符画,鸟的上下、管道的左右移动,全靠在控制台重新输出字符串完成。整份代码能不能改得动,取决于三块:头部全局变量、主循环、碰撞检测函数。资源自带的文档说明里写的“模块划分”,通常也围绕这三块展开。
2.1 先认全局变量:改两个数字就能调整难度
源码开头一般会看到这样的定义:
const int WIDTH = 40; // 地图宽度,单位:字符列 const int HEIGHT = 20; // 地图高度,单位:字符行 int birdX = 8; // 鸟的固定列坐标 int birdY = HEIGHT / 2; // 鸟当前所在行 int pipeX = WIDTH; // 管道当前列,从最右向左移动 int pipeGap = HEIGHT / 2; // 管道缺口中心行 int score = 0; // 已穿过的管道数 int sleepTime = 50; // 帧间隔,单位 ms这些全局变量决定了整个程序的“手感”。birdX 表示鸟固定在第八列,游戏中鸟不左右移动,所有运动都靠管道向左移动和 birdY 上下变化来模拟,这就是“飞翔”的视觉原理。pipeGap 是管道缺口的中心位置,鸟必须从 pipeGap 附近穿过去,pipeGap 上下的空隙由碰撞检测里的容错值决定。sleepTime 是每一帧之间的暂停时间,减到 30 以下游戏速度明显变快,这是作业里最常见的难度调节旋钮。
初学的人最容易犯的错,是把这些变量全部塞进 main 里或随手改名。我的建议是:先不动逻辑,只改 const WIDTH、HEIGHT 和 sleepTime,把地图变大、帧间隔调短,跑一遍看看效果。如果你的文档说明里写着“通过调整 sleepTime 参数实现难度控制”,那就是你自己亲手改出来的功能,比抄别人的代码可信得多。
顺便说一个很容易漏掉的点:管道重新生成时用 rand() % (HEIGHT - 6) + 3,把缺口中心控制在 3 到 HEIGHT-3 之间。如果源码 main 里忘了 srand(time(0)),每次运行生成的管道位置序列完全一样。这个问题不用动逻辑,只要加一行初始化。
2.2 主循环三个动作:输入、更新、绘制
控制台版飞行小游戏的核心循环基本是同一套骨架:
while (!gameOver) { // 1. 输入:非阻塞读一个按键,空格让鸟上跳 if (_kbhit()) { int key = _getch(); if (key == ' ') { birdY -= 2; // 上跳两步,改这个数决定跳跃力度 } } // 2. 更新:鸟自然下落,管道左移,碰撞检测 birdY++; // 没有按键时,鸟每帧下落一行 if (isCollide()) { gameOver = true; break; } pipeX--; // 管道向左移动 if (pipeX <= 1) { // 管道移出屏幕,重新生成 pipeX = WIDTH; pipeGap = rand() % (HEIGHT - 6) + 3; score++; } // 3. 绘制:光标回到左上角,重新输出整幅画面 gotoxy(0, 0); draw(); Sleep(sleepTime); // 帧间隔,调小就是加速 }这里最需要理解的是循环顺序:先处理输入,再改状态,最后绘制。如果先把 birdY 自增再读按键,按键就滞后一帧,玩起来觉得鸟“反应慢半拍”。birdY -= 2 是一次跳跃上升的行数,改成 3 会让跳跃更高;birdY++ 是重力下落速度,改成 birdY += 2 就是“重力加倍”。这两个参数配合 pipeGap 的容错值,构成了完整的难度体系。
绘制之前先 gotoxy(0, 0),把光标移回左上角,而不是每次 system("cls") 清屏,这是控制台游戏避免闪烁的关键。如果这份源码里用了 cls 写法,你改成光标定位后,闪烁问题会立刻好转。
2.3 碰撞检测:别把判定写成“到处都是墙”
bool isCollide() { // 撞上边界:上边界 0,下边界 HEIGHT - 1 if (birdY <= 0 || birdY >= HEIGHT - 1) { return true; } // 管道上下沿:pipeGap 为中心,上下各留 2 行安全区 if (birdX + 1 >= pipeX && birdX <= pipeX + 1) { if (birdY <= pipeGap - 2 || birdY >= pipeGap + 2) { return true; } } return false; }先看边界条件。birdY <= 0 表示飞出屏幕顶部,birdY >= HEIGHT - 1 表示撞到地面。注意这里用 HEIGHT - 1 而不是 HEIGHT,因为最后一行通常画成地面,这决定了地图看起来是否自然。管道判定则是先判断鸟和管道是否处于同一列范围,再判断鸟有没有从缺口穿过去。管道占两列宽度,所以用 birdX + 1 >= pipeX && birdX <= pipeX + 1 描述横向碰撞范围。
pipeGap - 2 和 pipeGap + 2 是缺口上下边界。把 2 改成 3,缺口变大,游戏变简单;改成 1,几乎只能从管道正中间穿过,难度陡增。我见过不少同学翻车,是因为只改了碰撞范围,没改管道随机生成范围,结果管道生成位置和碰撞判定不一致,鸟经常“隔空撞死”。改这里时,必须和 rand() % (HEIGHT - 6) 的范围一起调。
绘制函数也有值得读的地方。最常见的地图绘制是两层循环,按行按列画字符,逻辑上给每个坐标点分类:地面画 =,管道画 #,鸟画 @,其余留空。文档说明里如果配一张“坐标字符对照表”,评阅老师一眼就能看懂代码结构。
3. 在 VS2022 里把源码跑起来:新建空项目到粘贴运行的完整流程
这份资源最常见的用法就是配合 Visual Studio 交期末作业,因为学校的上机环境基本都有 VS。把源码从压缩包落到能跑的画面,中间有几个步骤经常让人卡住:项目类型选错、文件覆盖错位置、控制台乱码。以下流程以 VS2022 社区版为例,其他版本大同小异。
3.1 新建“空项目”,把 main.cpp 整个覆盖
压缩包里一般是一个 main.cpp 和一份说明文档,main.cpp 是唯一源文件,文档负责介绍项目和使用方式。VS 新建项目时有两个选项容易误导人:控制台应用和空项目。控制台应用模板自带一个带 main 的示例文件,而这份飞翔的小鸟源码本身就包含完整 main,直接粘贴替换即可,所以选空项目更干净。
操作顺序是:新建项目 → 语言选 C++ → 模板选“空项目”→ 项目名改成 bird 或 flappy_bird → 在解决方案资源管理器里右键“源文件” → 添加 → 新建项 → 选 C++ 文件,命名 main.cpp → 把资源包里的代码整个粘贴进去 → Ctrl+Shift+B 编译 → Ctrl+F5 运行。
提示:运行请用 Ctrl+F5,而不是 F5。F5 是调试运行,程序一结束窗口立刻关闭;Ctrl+F5 会在程序结束后弹出“请按任意键继续”,演示时不会尴尬。
这里有个容易被忽略的细节:VS 里粘贴代码后如果出现大量红色波浪线,先看编辑器右下角的文件编码有没有被识别成 UTF-8 with BOM。这份源码通常带中文注释,如果打开时编码识别错,中文字符串会变成乱码并报 C4819 警告或 C2065 错误,处理方式按下一节来。
3.2 中文乱码:源码是 UTF-8,控制台默认是 GBK
新安装的 Windows 11 上,控制台窗口默认代码页是 GBK(936),而不少网上下载的源码保存成 UTF-8。运行后“飞翔的小鸟”标题变成乱码,或者编辑器里中文正常、编译后界面乱码,都属于这一类。处理办法有三种,选一种就行。
#include <windows.h> int main() { SetConsoleOutputCP(CP_UTF8); // 控制台输出代码页切到 UTF-8 // ... 游戏主体代码 }这段代码的作用是在程序启动时,直接把控制台输出代码页切到 UTF-8,比 system("chcp 65001") 更干净,不依赖命令行环境配合。注意如果源码里已经用了 _setmode 切换宽字符模式,SetConsoleOutputCP 可能和它冲突,两选一即可。
不想改代码的话,也可以把源文件另存为 UTF-8 with BOM 或 GBK。在 VS 里操作:文件 → 另存为 → 保存按钮右侧小箭头 → 编码保存 → 选“简体中文(GB2312)”。这种做法的好处是代码一个字符都不用动,缺点是以后换到 VSCode 或其他编辑器,文件编码状态又不同,交接时容易乱。
3.3 备选:VSCode + g++,适合不想装 VS 的人
如果你平时用 VSCode,想先在练习本上跑通,可以用 MinGW-w64 的 g++ 编译。这里有一个必踩的坑:直接 g++ main.cpp -o bird.exe 编译,运行时中文乱码,因为 g++ 默认把中文字符串按 UTF-8 编码进可执行文件,而 Windows 控制台按 GBK 解释。解决办法是编译时加上编码参数:
g++ main.cpp -o bird.exe -fexec-charset=GBK参数说明:-fexec-charset=GBK 告诉编译器,把可执行文件里的中文字符串按 GBK 编码生成,这样 Windows 控制台显示中文就不会乱码。如果你的源码本身就是 GBK 编码,这个参数可以不加。
VSCode 里配置编译任务时,常见做法是把命令写进 tasks.json:
{ "version": "2.0.0", "tasks": [ { "label": "build", "type": "cppbuild", "command": "g++", "args": [ "-fexec-charset=GBK", "-g", "main.cpp", "-o", "bird.exe" ], "group": "build" } ] }tasks.json 里的 args 顺序就是最终执行的命令行参数顺序,label 是任务名,group 为 build 表示它是编译任务。VSCode 本身不管理 C++ 编译,它只是把这段命令交给 g++ 执行,所以编译器路径必须能在终端里直接敲 g++ 能找到。
补充一个排查思路:如果编译报“无法打开 main.cpp”或 No such file,八成是 tasks.json 里的相对路径没对齐工作区路径,把 main.cpp 改成绝对路径或 ${workspaceFolder}/main.cpp 就好。如果报 undefined reference,先查是不是把 windows.h 或 conio.h 里的函数名写错了,这两个头文件在 g++ 环境下同样可用。
4. 避坑:编译通过但游戏不动的六类常见问题
这些坑集中在“代码能编译,但运行行为不对”的地带,最容易让人怀疑人生,也是老手常翻车的地方。每条按现象、原因、解决三步写,可以直接复制到自己的工程里对照排查。
4.1 窗口一闪而过,根本看不到游戏
现象:编译没有任何错误,按 F5 运行,黑窗口闪一下就没了。
原因:控制台程序从 main 返回后进程结束,窗口被关闭;F5 是调试模式,调试会话结束窗口也随之关闭。
解决:改成 Ctrl+F5 运行。如果老师要求双击 exe 演示,在 main 末尾加一行 system("pause");,让程序结束后等待按键再退出。
4.2 画面闪得眼睛发花
现象:游戏能跑,但整个画面疯狂闪烁。
原因:draw 函数每次先 system("cls") 清屏,再逐行输出,清屏和重绘之间的空档让屏幕反复刷新。
解决:把 system("cls") 换成 gotoxy(0, 0) 光标回原点后整幅重绘。光标定位不改变已输出内容,避免清屏产生的空白帧,闪屏立刻消失。
4.3 按空格没反应,或者跳跃延迟
现象:游戏运行,鸟一直下落,按空格没反应,偶尔延迟一拍才动。
原因:输入循环放在更新之后,按键判断晚了一帧;或者缓冲里积压了多个按键,一次循环处理多次空格导致跳跃幅度异常。
解决:坚持“输入 → 更新 → 绘制”的顺序,按键读取用单次判断。一帧只处理一个按键:
if (_kbhit()) { int key = _getch(); if (key == ' ') { birdY -= 2; } }不要用 while (_kbhit()) 把所有按键都读出来,那样长按空格时会因为键盘重复触发连续跳跃,手感完全不对。如果依旧迟钝,检查循环里有没有多余的阻塞延时。
4.4 分数疯狂上涨,穿一根管道加好几十分
现象:明明只穿了一根管道,分数突然加了好几分甚至几十分。
原因:score++ 写在碰撞检测的外侧,只要管道在鸟左侧就每帧执行一次,没有“只加一次”的控制。
解决:把 score++ 挪到管道重新生成的分支里,和 rand() 重新生成管道放在一起。管道离开屏幕重新生成,才说明鸟完整穿过了这一根,这时加一次分正好。
4.5 换到机房电脑编译,_kbhit 变成未定义标识符
现象:在自己电脑上编译正常,到学校机房打开同一份工程,_kbhit、_getch 报未定义标识符。
原因:机房 VS 版本较旧,或者工程创建时勾选了“预编译头”导致 include 顺序被截断,也可能拷贝项目时丢了部分头文件路径。
解决:显式包含 #include <conio.h> 和 #include <windows.h>。如果报 C4996 说 _kbhit 不安全,不要改成 kbhit,直接忽略安全警告或加 #pragma warning(disable:4996),这是控制台作业里的常规操作。
4.6 每次运行管道位置都一模一样
现象:重启程序后,管道出现的顺序、缺口的位置完全相同,玩过一遍就没有新鲜感。
原因:rand() 没有播种,默认种子固定,随机序列每次运行都是同一个。
解决:在 main 开头加 srand((unsigned)time(NULL));,并包含 #include <time.h> 或 #include 。这一步加完,管道生成序列才会真正随机。
5. 交差前再动两步:难度递增、高分存档,把文档说明写出“自己动手”的痕迹
期末作业想拿高分,光能跑是不够的,老师看的是你有没有在别人代码之外加了“改动痕迹”。这里给两个改动成本低、说得出原理的进阶技巧,以及一套配合源码的文档说明写法。
5.1 难度递增:两行代码让速度随分数变快
int currentSleep = sleepTime - score * 2; if (currentSleep < 20) currentSleep = 20; Sleep(currentSleep);这段代码放在主循环 Sleep 的位置,每穿过一个管道,score 加 1,帧间隔就减少 2ms,直到下限 20ms。if 判断保证游戏不会因为分数无限增加而快到无法渲染。原理就是前面说的 sleepTime 是帧间隔,帧间隔越小,整个画面更新越快,管道视觉移动速度就越快。这在文档说明里写“引入动态帧间隔参数,实现难度阶梯”是站得住脚的。
5.2 高分存档:六行代码让刷新后留住最高分
// 启动时读取上次最高分 int best = 0; ifstream in("best.txt"); if (in >> best) { /* 读到就更新 best */ } in.close(); // 游戏结束时写回 ofstream out("best.txt"); out << score; out.close();ifstream 负责读取文件,ofstream 负责写入文件,best.txt 保存在程序工作目录。VS 控制台程序的工作目录默认在工程目录下,VSCode 里则取决于 tasks.json 的 cwd 配置。注意if (in >> best)这个判断必须有:如果文件不存在或格式不对,in >> best 失败后 best 保持 0,避免游戏崩溃或显示出垃圾值。使用前记得在头部加 #include 。
5.3 文档说明三板斧:函数表、参数测试、运行截图
资源自带的文档说明一般包括项目介绍、环境配置、操作说明、代码结构。如果要改成自己的,先把函数表做出来,再补两张参数测试截图。
| 函数/模块 | 职责 | 关键参数 |
|---|---|---|
| main | 游戏主流程与状态控制 | sleepTime 控制帧率 |
| isCollide | 碰撞检测 | pipeGap ± 2 缺口容错值 |
| draw | 地图与角色绘制 | WIDTH、HEIGHT 地图尺寸 |
| rand 管道生成 | 随机生成缺口位置 | HEIGHT - 6 的取模范围 |
参数测试写法是重点,比如:“将 sleepTime 从 50 改为 30 后,管道移动速度提升约 40%,游戏明显提速;将 pipeGap 容错值从 2 改为 1 后,缺口宽度减半,难度显著增加”。这类描述直接表明你调过参数、观察过效果,比“本项目功能强大”有用得多。文档里出现的函数名要和源码保持一致,一张函数表加两段测试记录就够,别堆大段模板文字。
我自己的习惯是,拿到任何一份游戏源码,都强制先走一遍“搜 main → 读全局变量 → 改 sleepTime 跑通 → 再看碰撞函数”这四步,顺序不对后面就全是玄学。这份飞翔的小鸟结构足够简单,正好适合走一遍这样的流程。希望帮到你。
本文还有配套的精品资源,点击获取