♻️ 资源大小11.9MB➡️资源下载https://download.csdn.net/download/s1t16/87450316基于C语言设计的像素小鸟小游戏一、设计目的掌握定义函数并熟练使用各函数实现各函数模块的调用学会将静态库导入工程熟练使用库函数掌握使用 graphics.hIMAG 的数据类型学会调用 API学会编写合适的算法来解决问题二、设计内容为了更好地掌握各数据类型、数据结构的使用以及 C 语言程序设计的思想以 C 语言平日课堂知识为主复刻了前几年在移动端很火的一款游戏 Flappy bird。成品 Flappy bird 截图如下三、概要设计3.1 功能模块图答如下图所示3.2 各个模块详细的功能描述。Custom Function list 自定义函数void print(); void begin();//载入music、背景、First stone void printstone(); void bird();//控制鸟的下降和上升 void judgement(); void scoleprint(); void endorretry();使用到的头文件、libgraphics.h stdio.h stdlib.h conio.h time.h windows.h mmsystem.hWINMM.lib (VC6.0 自带的静态库四、详细设计答首先需要安装 EasyX 图形库将.exe 呈现出更好的图形界面然后利用 graphics.h 绘制图像。正式动工前的准备熟悉 graphics 头文件中的各种库函数比如initgraph(,);界面初始化circle画圆RGB颜色设置IMAGE 定义 IMAGE 对象closegraphics关闭绘图界面等等。还需要寻找一些库函数识别键盘的敲击比如 while(!kbhit())等等。熟悉相关库函数后开始本次 flappy bird 程序编写。我的思路是这样背景图片的显示加入小鸟图片小鸟自由下落按键后上升加入静态的障碍物障碍物向左移动判断小鸟和障碍物的碰撞障碍物移动出左边界后在右边重新出现加入记分模块加入开始界面、结束界面加入背景音乐分阶段编写实现这十个模块。首先由于我需要设置图形界面所以调用了 VC6.0 里头对窗口句柄属性操作的函数实现对窗口句柄属性的修改这里由于游戏简单仅修改了窗口名字下面贴代码// 获取窗口句柄HWND hwnd GetHWnd();// 设置窗口标题文字SetWindowText(hwnd, 大峰子的 Flappy bird!);而后便是游戏窗口的设置这里为了尽最大限度地复刻移动端给玩家带来熟悉的感觉我初始化成了 350X600大概是 6 寸的手机屏幕大小。这一步的实现是initgraph350600之后需明确游戏的两个对象鸟管道由于小鸟的属性本质是取其需打印位置的左上角坐标X’,Y’)然后通过库函数 loadimage();putimage();将准备好的图片素材输出到该位置上这样小鸟就呈现在了屏幕上所以我们需要定义两个全局变量 bird_x、bird_y 去实现存储小鸟坐标的功能。之前提到本游戏主要是基于 C 语言 graphics.h 中各类绘图库函数实现的所以要定义 IMAGE 对象代码说话IMAGE background,bird1[4],bird2[4],scole1[10],scole2[10],stone_up1,stone_up2, stone_down1,stone_down2stone_up3,stone_up4,stone_down3,stone_down4;以便于后面的 loadimageputimage这一系列操作。随后便是各个功能模块的技术实现了我简述一下我的技术实现背景图片的显示。这个比较简单定左上角为(0,0)点只要使用函数 putimage00文件地址就能实现该模块。加入小鸟图片。首先我们观察游戏会发现该游戏中小鸟只会上下移动并不会左右移动造成小鸟向前飞的错觉是管道相对于小鸟的相对位移造成的因此我们只需要 putimage 到150300即屏幕中央偏左的位置即可。另外这里有一个小技术实现就是三光栅操作使得位图能透明显示。其原理是 XOR即异步运算掩码图 XOR 精灵图能透明显示到背景图上的位图。举个例子黑 XOR 黑原颜色。我的素材or通过 XOR 运算将两张图像先后重叠打印就能够实现图像的显示而不造成图像闪烁现象。加入静态的障碍物管道。这个原理与 2 是一致的。不再过度阐述。开始时播放背景音乐。这里需要调用媒体播放 API,双线程进行不然就会造成“播放.wav 时控制台黑屏”的现象。这里用到的是头文件 windows.h 下的库函数 Playsound下面贴代码PlaySound(000.wav, NULL, SND_FILENAME | SND_ASYNC|SND_LOOP);通过这段代码实现多线程、循环播放。此外实现该功能需要添加 WINMM.lib 到 project 当中。加入封面、结束界面。封面的加入较为简单制作一个 350X600 的图片在开始时 putimage并利用 getch实现停顿直至键盘输入。结束界面的“GAME OVER”图像显示方法如 2.中描述的一样不再过多阐述。小鸟的自动下落键盘输入空格 or 鼠标左键时上升。首先自动下落可以通过循环解决每一次循环都进行一次按键检测 if(kbhit())若按下空格键 or 鼠标左键bird_y 减小小鸟图像打印时将上移造成一种小鸟向上飞跃的感觉若没有按按键则 bird_y3缓慢下降。障碍物向左移动。这个操作是在自定义函数 printstone中实现的。关键点在于游戏一开始时柱子的初始化、柱子的随机长度、及柱子移动到屏幕左边框后右侧柱子重新出现...这三种情况的技术实现。柱子的随机长度怎么设置呢需要用 rand函数其位于 stdlib.h 中用 rand%310-550将得到的数值赋给上方的柱子坐标 stone_y保持 x 不变y-750就得到了下柱子的位置坐标再 putimage即可。为了让每一次游戏时管道间的间距位置都不一致我使用了 srand随机种子由于随机种子函数值决定了 rand得到的数字序列所以可以利用时间的多样性实现随机种子的多样性下面贴代码srand( (unsigned)time( NULL ) );这样每次 rand;得到的数字都是不定的达到伪随机的效果。如何实现柱子的左移呢在 printstone中添加 stone_x-- 这一行这样每次 while 循环就能实现柱子的左移。如何才能在一开始游戏时只出现一个柱子呢需把 stone_x2 赋一个极小值让 putimage();到 350*600 的屏幕之外即可。同理每次循环检测柱子坐标变量 stone_x 的值是否等于 0等于 0 的话将 stone_x 重新赋值为 350 即可。碰撞判断模块。我自定义了一个 judgement();进行判断,如果鸟和管道的素材重叠就判断游戏失败了执行 endorretry();。由于我的 bird 图像素材是 34X24所以区间就是 stone_y-24同时要考虑管道的素材是 140*600首先考虑碰撞上界上界即是与上管道重叠因此有以下代码实现if((stone_x110 stone_x120)||(stone_x1174stone_x1184)) if((bird_y(stone_y1576)bird_y(stone_y1600))||((bird_y(stone_y1726))bird_y(stone_y1750)))同时掉出下界也需要判断下界分为与下管道重叠与窗口下边缘重叠所以有了以下的代码实现else if(stone_x120 stone_x1174) if(!(bird_y(stone_y1600)bird_y(stone_y1726))) if(bird_y576)判断结果为 ture 后即进入 endorretry();或者 main while1主循环计分模块。当 stone_x150 时scole即柱子过屏幕一半的位置时默认小鸟通过了柱子。当碰撞后初始化即执行 endorretry();scole0计分归零。暂停模块。比较简单键盘检测鼠标右键、Esc 键即在 bird函数中检测键盘输入是否等于 27ASCII 码对应 Esc 键或者是否等于’ ’对应空格然后再使用 getch();函数停顿即可。游戏失败后的初始化模块。当 judgement();中判断成功并进入 endorretry();将打印 Game Over 图像并初始化。技术实现上需要消除图像闪烁问题这里需要用到 BeginBatchDraw();和 EndBatchDraw();批量绘图函数来消除 Sleep();函数造成的闪烁问题。4.1 功能函数的调用关系图4.2 各功能函数的数据流程图五、测试数据及运行结果5.1 正常测试数据和运行结果要求提供 3 组正常测试数据和运行结果5.2 异常测试数据及运行结果要求提供 2 组异常测试数据和运行结果六、调试情况设计技巧及体会6.1 改进方案答对自己的设计进行评价指出合理和不足之处提出改进方案像素小鸟翅膀的扇动①②③改进办法当 bird_y 上升/下降时利用 IMAGE 数组控制输出不同类型的图案。像素小鸟位移感严重跳跃不顺畅改进办法按键检测到空格键 or 鼠标左键时将 bird_y-80 调整至 bird_y-40减小相对位移的长度。柱子移动偏慢改进办法多设置一个难度模块通过改变柱子移动速度达到难度的改变例如定义全局变量 n将 stone_x--修改为 stone_xstone_x-n然后通过 scanf“%d”,n);去控制每次循环时柱子向左移动的位移即可。缺乏最高分模块。记录玩家最高分改进办法后续加入该模块。6.2 体会答通过这次 C 语言课程设计让我很好地掌握了 C 语言数据类型的使用学会了用结构化、模块化的思想去编写程序增强程序的健壮性可读性同时增强了对于添加并引用 lib 的熟练度让程序功能更好地达到目的。同时增强了个人对计算机、程序编写的自信心有利于未来的职业发展衷心地感谢任课老师的指导与栽培。七、参考文献《C 语言程序设计实践教程》 ——王曙燕 主编《C Primer Plus》 —— Stephen Prata 著八、附录源代码电子版# include graphics.h # include stdio.h # include stdlib.h # include conio.h # include time.h # include windows.h # include mmsystem.h # pragma comment(lib, winmm.lib) void print(); void begin();//载入music、背景、First stone void printstone(); void bird();//控制鸟的下降和上升 void judgement(); void scoleprint(); void endorretry(); int bird_x150,bird_y300,i0,k0;//鸟的左上角坐标 int scole0,t0; int stone_x1,stone_y1;//上截柱子左下坐标 int stone_x2,stone_y2;//上截柱子左下坐标 IMAGE background,bird1[4],bird2[4],scole1[10],scole2[10],stone_up1,stone_up2,stone_down1,stone_down2 ,stone_up3,stone_up4,stone_down3,stone_down4;//图片储存变量 MOUSEMSG m; // 定义鼠标消息 int main() { if(t0) { begin(); t; } if(t) { print(); } getch(); //制造停顿 //初始化 bird_y300; bird_x150; i0; k0; scole0; while(1) { bird(); print(); judgement(); } closegraph(); system(pause); return 0; } void print() { putimage(0, 0, background);//背景图像 printstone();//画柱子 putimage(bird_x, bird_y,bird1[i%3],NOTSRCERASE); putimage(bird_x, bird_y,bird2[i%3],SRCINVERT); if(k%50) i; k; scoleprint(); FlushBatchDraw(); // 绘制 } void printstone()//柱子移动规律/哭 { Sleep(30); if(stone_x1210)//此时画面存在两根柱子 { //三光栅操作 putimage(stone_x1, stone_y1, stone_up2,NOTSRCERASE); //掩码图 putimage(stone_x1, stone_y1, stone_up1,SRCINVERT); //精灵图 putimage(stone_x1, stone_y1750, stone_down2,NOTSRCERASE); putimage(stone_x1, stone_y1750, stone_down1,SRCINVERT); putimage(stone_x2, stone_y2, stone_up4,NOTSRCERASE); putimage(stone_x2, stone_y2, stone_up3,SRCINVERT); putimage(stone_x2, stone_y2750, stone_down4,NOTSRCERASE); putimage(stone_x2, stone_y2750, stone_down3,SRCINVERT); stone_x1--; stone_x2--; } else if(stone_x1210)//左柱子消失将stone_x2,stone_y2值归位 { stone_x2stone_x1; stone_y2stone_y1; putimage(stone_x1, stone_y1, stone_up2,NOTSRCERASE); putimage(stone_x1, stone_y1, stone_up1,SRCINVERT); putimage(stone_x1, stone_y1750, stone_down2,NOTSRCERASE); putimage(stone_x1, stone_y1750, stone_down1,SRCINVERT); stone_x1--; stone_x2--; } else if(stone_x1210stone_x10)//画面只存在一根柱子的情况 { putimage(stone_x1, stone_y1, stone_up2,NOTSRCERASE); putimage(stone_x1, stone_y1, stone_up1,SRCINVERT); putimage(stone_x1, stone_y1750, stone_down2,NOTSRCERASE); putimage(stone_x1, stone_y1750, stone_down1,SRCINVERT); stone_x1--; stone_x2--; } if(stone_x10)//柱子左端end生成新柱子 { stone_y1rand()%310-555; stone_x1350; putimage(stone_x1, stone_y1, stone_up2,NOTSRCERASE); putimage(stone_x1, stone_y1, stone_up1,SRCINVERT); putimage(stone_x1, stone_y1750, stone_down2,NOTSRCERASE); putimage(stone_x1, stone_y1750, stone_down1,SRCINVERT); stone_x1--; stone_x2--; } } void begin() { // PlaySound(000.wav, NULL, SND_FILENAME | SND_ASYNC|SND_LOOP); initgraph(350,600); // 获取窗口句柄 HWND hwnd GetHWnd(); // 设置窗口标题文字 SetWindowText(hwnd, 大峰子的Flappy bird!); IMAGE beforegame; loadimage(beforegame,D:\\flappybird\\source\\beforegame.jpg); putimage(0,0,beforegame); Sleep(1000); getch(); BeginBatchDraw();// 开启批量绘图模式 loadimage(background, D:\\flappybird\\source\\bkgroundfb.jpg);//载入背景图片 //鸟 loadimage(bird2[0], D:\\flappybird\\source\\bird1-2.gif); loadimage(bird1[0], D:\\flappybird\\source\\bird1-1.gif); loadimage(bird2[1], D:\\flappybird\\source\\bird2-2.gif); loadimage(bird1[1], D:\\flappybird\\source\\bird2-1.gif); loadimage(bird2[2], D:\\flappybird\\source\\bird3-2.gif); loadimage(bird1[2], D:\\flappybird\\source\\bird3-1.gif); loadimage(bird2[3], D:\\flappybird\\source\\bird4-2.gif); loadimage(bird1[3], D:\\flappybird\\source\\bird4-1.gif); //柱子 loadimage(stone_up1,D:\\flappybird\\source\\stone_up1.gif); loadimage(stone_up2,D:\\flappybird\\source\\stone_up2.gif); loadimage(stone_down1,D:\\flappybird\\source\\stone_down1.gif); loadimage(stone_down2,D:\\flappybird\\source\\stone_down2.gif); loadimage(stone_up3,D:\\flappybird\\source\\stone_up1.gif); loadimage(stone_up4,D:\\flappybird\\source\\stone_up2.gif); loadimage(stone_down3,D:\\flappybird\\source\\stone_down1.gif); loadimage(stone_down4,D:\\flappybird\\source\\stone_down2.gif); //数字 loadimage(scole1[0],D:\\flappybird\\source\\0_1.jpg); loadimage(scole2[0],D:\\flappybird\\source\\0_2.jpg); loadimage(scole1[1],D:\\flappybird\\source\\1_1.jpg); loadimage(scole2[1],D:\\flappybird\\source\\1_2.jpg); loadimage(scole1[2],D:\\flappybird\\source\\2_1.jpg); loadimage(scole2[2],D:\\flappybird\\source\\2_2.jpg); loadimage(scole1[3],D:\\flappybird\\source\\3_1.jpg); loadimage(scole2[3],D:\\flappybird\\source\\3_2.jpg); loadimage(scole1[4],D:\\flappybird\\source\\4_1.jpg); loadimage(scole2[4],D:\\flappybird\\source\\4_2.jpg); loadimage(scole1[5],D:\\flappybird\\source\\5_1.jpg); loadimage(scole2[5],D:\\flappybird\\source\\5_2.jpg); loadimage(scole1[6],D:\\flappybird\\source\\6_1.jpg); loadimage(scole2[6],D:\\flappybird\\source\\6_2.jpg); loadimage(scole1[7],D:\\flappybird\\source\\7_1.jpg); loadimage(scole2[7],D:\\flappybird\\source\\7_2.jpg); loadimage(scole1[8],D:\\flappybird\\source\\8_1.jpg); loadimage(scole2[8],D:\\flappybird\\source\\8_2.jpg); loadimage(scole1[9],D:\\flappybird\\source\\9_1.jpg); loadimage(scole2[9],D:\\flappybird\\source\\9_2.jpg); srand(time(0)); //初始化种子 print(); //第一根柱子初始化 stone_y1rand()%310-555; stone_x1350; stone_x2stone_y2-9999; //避免一开始显示两管道 } void bird() { char space; if(kbhit()) //检测按键 { spacegetch(); if(space ) //检测空格 { bird_y-80; } else if(space27) //ESC { getch(); } } else { bird_y3; } while(MouseHit()) { // 获取一条鼠标消息 GetMouseMsg(); switch(m.uMsg) { case WM_LBUTTONDOWN: bird_y-80; break; case WM_RBUTTONDOWN: getch(); break; } } } void judgement() { if((stone_x110 stone_x120)||(stone_x1174stone_x1184)) { if((bird_y(stone_y1576)bird_y(stone_y1600))||((bird_y(stone_y1726))bird_y(stone_y1750))) { endorretry();//结束游戏 } } else if(stone_x120 stone_x1174) { if(!(bird_y(stone_y1600)bird_y(stone_y1726))) { endorretry(); //结束游戏 } } if(bird_y576) { endorretry();//结束游戏 } if(stone_x1150) { scole; } } void endorretry() { if(bird_y550 stone_x1160) { while(bird_y550) { putimage(0, 0, background);//背景图像 putimage(stone_x1, stone_y1, stone_up2,NOTSRCERASE); putimage(stone_x1, stone_y1, stone_up1,SRCINVERT); putimage(stone_x1, stone_y1750, stone_down2,NOTSRCERASE); putimage(stone_x1, stone_y1750, stone_down1,SRCINVERT); putimage(stone_x2, stone_y2, stone_up4,NOTSRCERASE); putimage(stone_x2, stone_y2, stone_up3,SRCINVERT); putimage(stone_x2, stone_y2750, stone_down4,NOTSRCERASE); putimage(stone_x2, stone_y2750, stone_down3,SRCINVERT); putimage(bird_x, bird_y,bird1[3],NOTSRCERASE); putimage(bird_x, bird_y,bird2[3],SRCINVERT); FlushBatchDraw(); bird_y; } } IMAGE gameover1,gameover2,atlast; loadimage(gameover1, D:\\flappybird\\source\\gameover1.gif); loadimage(gameover2, D:\\flappybird\\source\\gameover2.gif); putimage(80, 200, gameover1,NOTSRCERASE); putimage(80, 200, gameover2,SRCINVERT); FlushBatchDraw(); Sleep(1000); loadimage(atlast, D:\\flappybird\\source\\atlast.jpg); putimage(0,0,atlast); scoleprint(); FlushBatchDraw(); getch(); //第一根柱子 stone_y1rand()%310-555; stone_x1350; stone_x2stone_y2-9999; bird_y300; scole0; main(); } void scoleprint() { IMAGE *fen1[6],*fen2[6]; int he,weishu1,i0,sdsfscole; if(sdsf0) { putimage(250,50,scole1[0],NOTSRCERASE); putimage(250,50,scole2[0],SRCINVERT); } while(sdsf0) { hesdsf%10; fen1[i]scole1[he]; fen2[i]scole2[he]; putimage(300-50*weishu,50,fen1[i],NOTSRCERASE); putimage(300-50*weishu,50,fen2[i],SRCINVERT); sdsf/10; i; weishu; } }