简介:基于C++的飞机大战小游戏完整源码包,以Visual Studio工程形式呈现,适合C++初学者、课程设计或游戏开发爱好者理解小型游戏的设计思路与实现细节。源码包共71个文件,包含24张PNG图片(背景、敌机、英雄机、子弹等素材)、16个C++源文件,另含Visual Studio解决方案、工程配置文件、编译日志及两个可直接运行的程序,压缩包约64.23MB。代码按功能拆分为滚动背景、Hero移动、子弹发射、双发、碰撞检测、敌机爆炸、游戏结束判断、计分与多关卡难度升级等多个版本模块,层次分明地展现从单一功能到完整玩法的演进过程。已有887人学习浏览。读者既可运行EXE直接体验游戏效果,也能对照源文件研究界面设计、输入响应、音频播放与碰撞逻辑等实现方法,整体目录结构清晰,适合个人练习或团队二次开发。
1. C++飞机大战小游戏源码:版本号就是最好的开发笔记
C++飞机大战这套小游戏源码,拿到手先别急着点F7。我最推荐它的理由不是画面有多炫,而是里面的版本号本身就是一条教学链路:v1.0滚动背景、v2.0 Hero移动、v3.0单个子弹发射、v4.0多个子弹发送、v6.0双发……一路排到V13.0多关卡、V14.0难度升级、V15.0背景音乐,v16应该就是整合后的当前版本。这不是一份只有最终代码的项目,而是同一个游戏从空窗口长到完整可玩状态的中间快照集合,非常适合想学C++小游戏开发、又不想从零抠窗口API的读者。技术栈不杂:Visual Studio工程、EasyX图形库、Windows GDI贴图,覆盖滚动背景、键盘操控、子弹系统、碰撞检测、爆炸帧动画、计分、多关卡和BGM。C++入了门、觉得只写控制台程序不过瘾的人,用这份源码当跳板正合适。
2. 打开工程前的功课:先看清资源结构与VS环境要求
看一份源码不先看目录结构,后面一定会被"源.cpp到底管什么"这种问题绕晕。飞机大战这套包的摆法其实很有条理:解决方案文件、工程文件、按版本命名的C++历史文件、res素材目录、x64/Debug下编译好的exe。搞清楚这几块,至少能少走一小时弯路。
2.1 源码包里的两条主线:工程文件与版本快照
先把眼睛放到最该看的几个文件上:
| 文件或目录 | 在项目里扮演的角色 |
|---|---|
| 飞机大战.sln | Visual Studio解决方案入口,双击直接打开 |
| 飞机大战.vcxproj | 工程配置,记录编译平台、字符集、依赖项 |
| 源.cpp | 当前版本整合后的程序入口,v16的落点 |
| v1.0~V16共16个cpp | 按版本号保留的历史源码快照,是本包最大亮点 |
| res/ | 素材目录,enemy、hero、bg、blast、zd、over前缀的PNG全在这 |
| x64/Debug/飞机大战.exe | 已经编译好的Debug可执行程序,不想搭环境可以直接跑 |
这套命名很有规律:enemy是敌机,hero是主角,bg是背景,blast是爆炸帧,zd是子弹("子弹"拼音),over是结束画面。素材名和代码变量名对得上,读代码时不至于抓瞎。res里出现了enemy0、enemy1、zd11、zd12、zd20、bg01~bg03、blast和blast2,说明敌机和子弹有多套贴图,爆炸也至少做了两帧,这是后面做帧动画的基础。
版本快照这层设计是我最想夸的。普通源码包给你一个最终版,最多配个说明;这份把v1.0到v15.0每个功能里程碑都留了底。我拿到手第一反应就是:这相当于作者的git提交记录被平铺在文件夹里,不用装git也能看到"滚动背景→移动→子弹→碰撞→爆炸→计分→关卡"的完整演进顺序。学的时候建议从最旧的v1.0开始看,每个版本编译一次,而不是一上来就啃源.cpp。
我见过不少新手拿到这份源码直接改源.cpp,改完发现想退回v9.0看爆炸实现,只能撤销编辑器操作。更稳妥的习惯是:源.cpp只当集成后的参照,按版本学习时把对应版本cpp单独拷出来建临时工程玩,玩完删掉,不污染主工程。血泪经验,值得记。
2.2 环境与字符集:EasyX依赖、x64 Debug、多字节
源码是Visual Studio工程,环境要求不外乎三件事:VS桌面开发组件、EasyX图形库、工程字符集。从源码里大量出现loadimage、putimage、initgraph来判断,这套项目用的是EasyX,不是SDL也不是Qt。选型理由也很直白:
| 图形方案 | 上手成本 | 中文教程密度 | 与本项目吻合度 |
|---|---|---|---|
| EasyX | 低,装完直接#include <graphics.h> | 高 | 完全吻合 |
| SDL2 | 中,要理解窗口、渲染器、纹理三层 | 一般 | 需要大量改写 |
| Qt | 较高,信号槽体系有学习曲线 | 高 | 杀鸡用牛刀 |
对只想快速跑通一个2D小游戏的人来说,EasyX把窗口创建和画像素都封装好了,专心写游戏逻辑就好。装EasyX时注意下载对应自己VS版本的安装包,装完在VS里能正常包含graphics.h和easyx.h就算成功。
字符集是另一个容易被忽略的配置项。打开飞机大战.vcxproj,找CharacterSet这一项:
<!-- 飞机大战.vcxproj 中 Debug|x64 配置片段 --> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <CharacterSet>MultiByte</CharacterSet> <UseDebugLibraries>true</UseDebugLibraries> </PropertyGroup>这段配置的含义是:Debug x64模式下使用多字节字符集(MultiByte)。EasyX的outtextxy在绘制文字时,宽字符和多字节字符的调用方式不一样,如果被改成Unicode,文字部分会直接编译报错。这也是很多EasyX项目从别的机器复制过来后第一波红灯的元凶。
如果你习惯用VSCode而不是VS,这套带.sln的工程我不建议手工写task.json去拉编译,EasyX的include和lib路径在VSCode里要额外配置,收益不高。直接在VS里打开.sln按F7是最省事的路径。想用命令行编译也可以:
MSBuild.exe 飞机大战.sln /p:Configuration=Debug /p:Platform=x64 /m这里/p:Configuration指定Debug而非Release,/p:Platform指定x64而不是Win32,/m是并行编译加快速度。Debug版保留调试信息,初学时跑Debug更合适,碰到问题能下断点看变量;Release留给做完之后打包用。环境上最容易翻车的是EasyX装完VS没识别到,如果graphics.h一直报找不到,用EasyX安装程序重新装一次并重启VS,别在包含目录里手动乱指路径。
提示:拿到陌生EasyX源码包,先看.vcxproj里的CharacterSet和PlatformToolset,再决定要不要改配置。改字符集前想清楚,改动会牵动所有文字接口。
3. 核心机制拆解:滚动背景、Hero移动与子弹系统
3.1 v1.0滚动背景与v2.0 Hero移动:游戏循环的地基
先看v1.0,这个版本只有一个功能:背景向下滚动。代码是整个项目的地基,后面所有版本的循环结构都从这里长出来。核心逻辑是把一张背景图贴两次,配合取模运算实现无限循环:
// v1.0-滚动背景.cpp 的核心循环 #include <graphics.h> #include <conio.h> int main() { initgraph(480, 700); // 创建480x700的游戏窗口 IMAGE bg; loadimage(&bg, "res/bg01.png"); // 加载背景图 int bgY = 0; // 第一张背景的Y坐标 int speed = 2; // 每帧向下移动2像素 while (true) { putimage(0, bgY, &bg); // 贴第一张背景 putimage(0, bgY - bg.getheight(), &bg); // 第二张补在上方 bgY += speed; if (bgY >= bg.getheight()) // 滚完一圈就回位 { bgY -= bg.getheight(); } Sleep(10); // 约100帧每秒,控制循环节奏 } return 0; }这段代码有三个设计点值得记。第一,窗口高700,背景图高度正好对应滚动区间,两张图一上一下拼接就能做到无缝循环。第二,bgY累加到超过背景高度后减掉一个背景高度,用"减法回绕"而不是if清零,循环里少一点开销是一点。第三,Sleep(10)是帧节奏的唯一控制手段,数值越小游戏越快,后面V14调难度时改的就是这些速度参数,而不是重写逻辑。
v2.0加的是Hero移动,和背景配合起来游戏才有"角色在动"的感觉。移动用的是GetAsyncKeyState轮询键盘:
// v2.0-Hero移动.cpp 的按键处理 int heroX = 240, heroY = 600; // 英雄初始位置 int step = 5; // 每帧移动5像素 // 主循环内每帧执行 if (GetAsyncKeyState(VK_LEFT) & 0x8000) { heroX -= step; } if (GetAsyncKeyState(VK_RIGHT) & 0x8000) { heroX += step; } // 边界限制,防止飞机飞出屏幕 if (heroX < 0) heroX = 0; if (heroX > 480 - 60) heroX = 480 - 60; // 60按飞机贴图宽度来GetAsyncKeyState返回值的最高位表示按键是否被按下,所以用0x8000做位与判断,这是Windows按键检测的常规姿势。step=5配合Sleep(10)约为每秒500像素,手感偏快,改成2~3更细腻,这个参数直接决定游戏"飘不飘"。边界限制里480是窗口宽度,60是飞机贴图宽度,减掉它才不让飞机半个身子卡在屏幕外。这里坐标还是裸变量,v7.0之后会封装成结构体,那是后话。
3.2 v3.0到v6.0子弹演进:从单发到双发与音效
v3.0到v6.0是一个典型的"先跑通再优化"过程。v3.0只有一颗子弹,按一下打一发,子弹飞出屏幕前再按毫无反应,手感约等于没有。v4.0最大的变化是引入子弹数组,把子弹从"单个变量"升级成"一批槽位",牺牲一点内存换手感:
// v4.0-多个子弹发送.cpp 的子弹管理 #define MAX_BULLETS 20 struct Bullet { int x, y; bool alive; // false表示槽位空闲 } bullets[MAX_BULLETS]; // 发射:从数组里找一个空闲槽位,填入子弹数据 void fire(int x, int y) { for (int i = 0; i < MAX_BULLETS; i++) { if (!bullets[i].alive) { bullets[i].x = x; bullets[i].y = y; bullets[i].alive = true; break; } } } // 主循环里对每个子弹做更新:上移、出界回收 for (int i = 0; i < MAX_BULLETS; i++) { if (bullets[i].alive) { bullets[i].y -= 8; // 子弹每帧上移8像素 if (bullets[i].y < 0) { bullets[i].alive = false; // 出界,槽位让出来 } } }这里用原生数组而不是STL的vector,不是作者偷懒。游戏主循环每帧都要遍历子弹和敌机,vector的动态扩容和析构在这种高频短对象场景里会影响帧率稳定性。MAX_BULLETS=20意味着同屏最多20发子弹,发满后fire循环找不到空闲槽位,自然表现为"按了没反应",这是对象池的边界,不是bug。y -= 8配合Sleep(10),子弹每秒上升800像素,从屏幕底部飞到中部大约几百毫秒,这套数值链可以整体按比例调。
v5.0在发射时加了音效,v6.0实现双发。音效的常见做法是PlaySound:
// v5.0-多发子弹连续播放声音.cpp 的音效触发 #include <mmsystem.h> #pragma comment(lib, "winmm.lib") // 链接Windows多媒体库 void fireWithSound(int x, int y) { PlaySound("res/zd.wav", NULL, SND_FILENAME | SND_ASYNC); fire(x, y); // 复用v4.0的发射逻辑 }SND_FILENAME表示第一个参数是文件路径,SND_ASYNC表示异步播放、不阻塞主循环。如果连续高频发射导致音效叠成一团,可以先PlaySound(NULL, NULL, 0)停止上一个再播新的。双发的实现更有意思,v6.0没有改数据结构,只是把fire调用从一次变两次,子弹起点x分别偏左和偏右:
// v6.0-实现双发.cpp 的核心 fire(heroX + 10, heroY); // 左膛 fire(heroX + 40, heroY); // 右膛英雄贴图宽度约60像素时,两颗子弹从x+10和x+40飞出,视觉上正好是机翼两侧。双发用两行代码就完成了,因为它吃透了v4.0的数组槽位设计。这就是版本演进的价值:每一步都有迹可循,v6.0的简单建立在v4.0的打底上。
4. 碰撞、爆炸与计分:v7.0到v12.0把玩法闭环
4.1 v7.0封装与v8.0碰撞检测:结构体和矩形相交判定
v7.0之前,英雄坐标是裸变量,敌机也是零散变量。当敌机变成一批时,裸变量就撑不住了。v7.0做的事是封装Plane结构体,把飞机共同的属性收拢在一起:
// v7.0-重新封装飞机-创建敌机.cpp 的飞机结构 #define MAX_ENEMIES 8 struct Plane { int x, y; int width, height; IMAGE img; bool alive; } enemy[MAX_ENEMIES]; // 敌机生成:从上方随机位置进入,复用槽位 void spawnEnemy() { for (int i = 0; i < MAX_ENEMIES; i++) { if (!enemy[i].alive) { enemy[i].x = rand() % (480 - enemy[i].width); enemy[i].y = -enemy[i].height; // 从屏幕外上方出现 enemy[i].alive = true; break; } } }rand() % (480 - width)保证敌机横坐标不会超出右边界,这个取模上限的写法比"生成后再判断重新生成"干净得多。敌机从y为负值开始往下落,玩家看到的是敌机"滑入屏幕",而不是凭空冒出来。记得程序启动时调一次srand(time(NULL)),否则每次运行随机序列都一样,敌机永远出现在同一批位置。
透明贴图是图形部分最隐蔽的坑。PNG素材直接putimage,飞机会带一圈矩形底色。v7.0里的常见做法是素材出成统一底色,再用两阶段光栅操作抠掉底色:
// 透明贴图:白底素材用SRCAND+SCRINVERT两段式输出 putimage(p.x, p.y, &p.img, SRCAND); putimage(p.x, p.y, &p.img, SCRINVERT);SRCAND先把白色区域与屏幕做与运算,SCRINVERT再做反色叠加,两张图配合后白色被透明化,飞机本体保留。素材如果是黑底,逻辑要反过来。这套做法在EasyX游戏里沿用多年,比逐像素处理省事。新版EasyX对带alpha通道的PNG已经可以loadimage后直接putimage,素材质量好的话用不上抠色这一套。
v8.0碰撞检测是整个玩法从"打空气"变成"能打中"的关键。这里用的是矩形相交判定,不是像素级碰撞:
// v8.0-碰撞检测.cpp bool isHit(const Plane& a, const Plane& b) { // 两个矩形不重叠的充要条件:某一条轴的内侧距离为负 if (a.x + a.width < b.x || b.x + b.width < a.x) return false; if (a.y + a.height < b.y || b.y + b.height < a.y) return false; return true; }逻辑是把两架飞机都当作轴对齐矩形,x方向和y方向只要有一方不重叠,矩形必然不相交;只要两个方向都有重叠区间,就判定命中。实战中我不会直接用原始宽高做判定,飞机贴图往往不是实心矩形,机翼两侧和尾部会白送碰撞。常见做法是把判定矩形往里缩一圈:
// 实际调用时缩小碰撞盒,手感更宽容 bool hitBulletEnemy = isHit( { bullet.x + 2, bullet.y + 2, 8, 8 }, { enemy[i].x + 4, enemy[i].y + 4, enemy[i].width - 8, enemy[i].height - 8 } );子弹碰撞盒缩成8x8,敌机四周各让出4像素,玩家打中机翼边缘不会误判,也不会出现"明明擦到却没死"的憋屈感。这个参数属于手感调校,不同人偏好不同,但方向是统一的:碰撞盒永远比视图片小一点。
4.2 v9.0到v12.0:爆炸帧动画、游戏结束与计分
v9.0实现敌机爆炸,素材里blast.png和blast2.png就是为这个功能准备的。爆炸本质是短时间内连续播放几帧贴图:
// v9.0-实现敌机爆炸.cpp 的爆炸帧展示 void showBlast(int x, int y) { IMAGE blastImg[2]; loadimage(&blastImg[0], "res/blast.png"); loadimage(&blastImg[1], "res/blast2.png"); for (int i = 0; i < 2; i++) { putimage(x, y, &blastImg[i]); Sleep(50); // 每帧停留50ms,两帧共100ms } }两帧动画只求"炸了一下"的视觉反馈,够用。帧数多的话可以抽成通用函数:传入帧数组和帧间隔,循环贴图。注意showBlast是阻塞式的,内部Sleep(50)会拖慢主循环,爆炸期间其他物体也跟着停顿。帧数少时感知不到,帧数多了尽量改成非阻塞的爆炸状态机。v10.0的Hero和敌机碰撞和v8.0用的是同一套isHit,只是入参换成hero和enemy[i]——这就是复用结构体的甜头,一个函数服务所有碰撞对。
v11.0游戏结束判断和v12.0计分是玩法的收尾。结束画面的标准姿势:
// v11.0-游戏结束判断.cpp if (!hero.alive) { IMAGE over; loadimage(&over, "res/over.png"); putimage(0, 0, &over); _getch(); // 等待玩家按键,再退出或者重置游戏 break; }计分则是每次消灭敌机时累加:
// v12.0-更新分数.cpp score += 10; TCHAR text[32]; swprintf_s(text, _T("SCORE: %d"), score); outtextxy(10, 10, text);score加多少是设计问题,10分一架是常见起步值。敌机可以按类型区分价值和速度,普通机10分、精英机30分,这正好对应res里enemy0和enemy1两套贴图的差异。outtextxy的文字输出在宽字符下容易出问题,多字节字符集下用TCHAR配合_T宏是通用写法,这又回到第2章强调的MultiByte配置——字符集不对,这里就是第一个爆点。
到这里,一个能玩的游戏闭环已经形成:背景滚动、角色移动、子弹发射、碰撞消灭、爆炸反馈、死亡结束、分数累计。欠的只剩纵向扩展,也就是第5章的多关卡和BGM。
5. 多关卡与背景音乐:V13.0到V15.0把游戏推向可发布状态
5.1 V13.0多关卡实现:过关判定与战场重置
单关游戏做完后,最大瓶颈是"玩家5分钟就腻"。V13.0引入关卡数的思路不复杂:用一个整数记录当前关卡,每消灭N架敌机进入下一关。源码里V13.0-多关卡实现.cpp解决的就是这一层:
// V13.0-多关卡实现.cpp 的过关逻辑 int stage = 1; // 当前关卡,从第1关开始 int killCount = 0; // 本关已消灭的敌机数 const int PASS_COUNT = 10; // 每关需消灭10架敌机 // 每次成功消灭一架敌机后调用 killCount++; if (killCount >= PASS_COUNT) { stage++; // 关卡+1 killCount = 0; // 击杀数清零,下一关重新计算 resetBattlefield(); // 关键:重置战场 hero.x = 240; // 英雄回到初始位置 hero.y = 600; }过关判定本身不复杂,真正的坑在resetBattlefield。我在这里翻过车:只重置了敌机数组的alive状态,忘了清理场上残余子弹和爆炸帧,下一关开场满屏子弹乱飞,就像上一场战斗的"残骸"粘到了新关卡。正确的重置顺序是先清爆炸特效,再清子弹数组全部槽位,最后重置敌机坐标,顺序反了玩家就会看到子弹还在飞、敌机还在掉。
另一个值得提的是关卡重置后的"开场保护"。如果resetBattlefield后不重置敌机生成计时器,新关卡第一秒就可能连续刷出好几架敌机,玩家还没回过神就撞机。我一般会在关卡切换时设置一个60帧(约0.6秒)的生成暂停窗口,给玩家一个呼吸空间。对应的重置函数大概长这样:
// 常见的战场重置顺序,注意先后关系 void resetBattlefield() { for (int i = 0; i < MAX_ENEMIES; i++) { enemy[i].alive = false; // 敌机全部撤退 } for (int i = 0; i < MAX_BULLETS; i++) { bullets[i].alive = false; // 玩家子弹清空 enemyBullets[i].alive = false; // 敌机子弹也清空 } spawnTimer = 60; // 重置敌机出生计时器,给玩家缓冲 }这个函数在源码里没有单独成文件,但V13之后的版本几乎都隐含着这套逻辑。学习时自己补上这段,比只抄过关判断理解更深。
5.2 V14.0难度升级与V15.0背景音乐:参数曲线与MCI切歌
V14.0做多关卡难度升级,本质是让难度参数随关卡号平滑变化,而不是每关硬编一组数据。源码版本名直说了"难度升级",具体参数曲线常用线性公式:
// V14.0-多关卡难度升级.cpp 的难度参数 // 敌机下落速度:第1关为1,每关递增0.5 int enemySpeed = 1 + (stage - 1) * 0.5; // 敌机出生间隔:第1关60帧一只,每关少5帧 int spawnInterval = 60 - (stage - 1) * 5; // 同屏敌机上限:第1关3架,每关加1 int maxEnemies = 3 + (stage - 1);三个公式对应三条难度曲线:速度快了躲着难,生成密了压力大,同屏多了操作繁琐。如果全部线性增长,第5关敌人速度到3.0,配合60帧每秒的循环已经很难闪避;所以更稳妥的玩法是给参数设上限,比如enemySpeed最大到2.5、spawnInterval最低到35帧,避免第10关变成不可能通关的死局。参数表大概是这种感觉:
| 关卡 | 敌机速度 | 生成间隔(帧) | 同屏上限 |
|---|---|---|---|
| 第1关 | 1.0 | 60 | 3 |
| 第2关 | 1.5 | 55 | 4 |
| 第3关 | 2.0 | 50 | 5 |
| 第4关及以后 | 2.5封顶 | 35封底 | 8封顶 |
V15.0的背景音乐用的是MCI命令字符串接口。相比PlaySound只播一个固定wav,MCI能管理独立句柄、支持循环播放,切关卡换BGM就靠它:
// V15.0-设置各关卡的背景音乐.cpp #include <mmsystem.h> #pragma comment(lib, "winmm.lib") // 切换关卡背景音乐 void playStageMusic(int stage) { mciSendString("close bgmusic", NULL, 0, NULL); // 关掉旧音乐 TCHAR cmd[128]; swprintf_s(cmd, _T("open res/bg%d.mp3 alias bgmusic"), stage); mciSendString(cmd, NULL, 0, NULL); // 打开第stage关的mp3 mciSendString("play bgmusic repeat", NULL, 0, NULL); // 循环播放 }MCI命令的特征是"open xxx alias 名字",之后所有操作都通过alias指代这个媒体对象。切歌前必须先close同一个alias,否则连续open会积累多个打开实例,内存和句柄双泄漏,跑十几关后音乐开始卡顿甚至无声,这是MCI方案最经典的坑。repeat后缀表示循环播放,不加的话一首播完就停。bg%d.mp3这类资源命名要求素材名和关卡号严格对应,否则第3关open失败后音频全无,代码会调但没准备素材一样白搭。
注意:MCI对mp3格式有兼容性要求,部分采样率或码率的mp3会打不开。遇到open命令返回错误时,先把所有BGM统一转成44100Hz、128kbps的常规参数,这是成本最低的解法。
V13到V15做完,这套源码已经从"能玩几分钟"变成"想打穿全部关卡"的成品状态。玩法闭环、难度曲线、听觉反馈三块凑齐,剩下的是工程化的事:怎么把它安全地从源码编译成exe、碰到环境问题怎么定位。最后一章把这些琐碎但致命的坑收干净。
6. 编译避坑与验证清单:装好环境再跑通一轮
6.1 三个高频编译坑:现象、原因、解决
先说坑一:编译时报"C2065:'graphics.h' 未声明"或"无法打开包含文件graphics.h"。原因基本都是EasyX没安装,或者装的时候选的VS版本和当前工程用的VS工具集不一致。解决方法是下载匹配当前VS版本的EasyX安装包重新安装,装完在项目属性→VC++目录→包含目录里确认EasyX头文件路径已存在。最常见的场景是从别人那里拷来源码,人家装了EasyX你没有。
坑二:链接时报"LNK2019:无法解析的外部符号 _PlaySound@12"或mciSendString相关报错。现象是编译通过、链接失败。原因是PlaySound和mciSendString属于winmm.lib,工程没链接这个库。解决方法是加#pragma comment(lib, "winmm.lib"),或者在项目属性→链接器→附加依赖项里手动补winmm.lib。
坑三:运行后飞机周围一圈黑底或白底色块,贴图不透明。现象是素材图是PNG,但直接用putimage整块贴出矩形底色。原因是对不带alpha通道的PNG,EasyX按不透明位图处理。解决方法是先确认素材是否真透明;如果真透明还出黑框,用loadimage重新加载;如果素材本身是白底图,用第4章的SRCAND+SCRINVERT两段式抠色贴图。另外提醒一句:Debug模式下降帧很常见,IDE调试器占用会让Sleep(10)失真,子弹"飞不动"先用Release模式验证是不是性能问题。
6.2 上手验证清单:十五分钟跑通一轮
拿到源码后按这个流程走一遍,能省掉大部分自我怀疑:
- 双击飞机大战.sln打开工程,确认右上角解决方案配置是Debug、平台是x64
- 按F7编译,看到0错误0警告再运行
- 运行后先确认背景在持续向下滚动
- 按左右方向键,英雄贴图跟随移动且不越界
- 按空格发射,子弹连续向上飞,音效正常
- 让子弹击中敌机,观察爆炸帧是否出现
- 故意迎头撞敌机,确认over.png结束画面弹出
- 打够过关数量,确认关卡号增加、BGM切换、敌机变快
如果想要快速验证碰撞检测有没有粘到正确对象,可以在v12.0的score += 10这行下断点:
killCount++; score += 10; // 此处下断点:打中一架敌机应命中一次 if (killCount >= PASS_COUNT) { ... }断点如果没触发,说明碰撞路径根本没走到,回头查isHit的调用位置和矩形参数,先确认敌机alive状态,再看矩形是否缩得过多导致永远不重叠。Debug模式下按F11跟进去看两个矩形的实际数值,比盯着贴图猜快。我自己第一次上手这套源码时,上来直接编译源.cpp,撞上graphics.h找不到,装完EasyX又撞上Unicode字符集报错,折腾一整晚才发现问题全在环境配置不在代码。从那以后我拿到任何EasyX游戏源码,第一件事永远是确认字符集和EasyX版本,编译前强制走一遍环境检查,再顺手过一遍上面的验证清单,最后才动手调碰撞参数。这套流程帮我避开了不少冤枉路,希望也帮到你。
本文还有配套的精品资源,点击获取