基于C语言设计的像素小鸟小游戏
2026/6/16 9:16:54 网站建设 项目流程

♻️ 资源

大小: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();

使用到的头文件、lib

<graphics.h> <stdio.h> <stdlib.h> <conio.h> <time.h> <windows.h> "mmsystem.h"

WINMM.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 寸的手机屏幕大小。这一步的实现是:initgraph(350,600);

之后,需明确游戏的两个对象,鸟,管道;

由于小鸟的属性本质是取其需打印位置的左上角坐标(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_down2,stone_up3,stone_up4,stone_down3,stone_down4;

以便于后面的 loadimage();putimage();这一系列操作。

随后,便是各个功能模块的技术实现了,我简述一下我的技术实现:

背景图片的显示。这个比较简单,定左上角为(0,0)点,只要使用函数 putimage(0,0,&文件地址);就能实现该模块。

加入小鸟图片。首先我们观察游戏会发现,该游戏中小鸟只会上下移动,并不会左右移动,造成小鸟向前飞的错觉是管道相对于小鸟的相对位移造成的,因此,我们只需要 putimage 到(150,300)即屏幕中央偏左的位置即可。另外,这里有一个小技术实现,就是三光栅操作,使得位图能透明显示。其原理是 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_y+=3,缓慢下降。

障碍物向左移动。这个操作是在自定义函数 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_x1>10 && stone_x1<20)||(stone_x1>174&&stone_x1<184)) if((bird_y>(stone_y1+576)&&bird_y<(stone_y1+600))||((bird_y>(stone_y1+726))&&bird_y<(stone_y1+750)))

同时,掉出下界也需要判断,下界分为与下管道重叠,与窗口下边缘重叠,所以有了以下的代码实现:

else if(stone_x1>=20 && stone_x1<=174) if(!(bird_y>(stone_y1+600)&&bird_y<(stone_y1+726))) if(bird_y>576)

判断结果为 ture 后,即进入 endorretry();或者 main while(1)主循环

计分模块。当 stone_x==150 时,scole++,即柱子过屏幕一半的位置时,默认小鸟通过了柱子。当碰撞后,初始化,即执行 endorretry();,scole=0,计分归零。

暂停模块。比较简单,键盘检测鼠标右键、Esc 键,即在 bird();函数中检测键盘输入是否等于 27(ASCII 码对应 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_x=stone_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_x=150,bird_y=300,i=0,k=0;//鸟的左上角坐标 int scole=0,t=0; 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(t==0) { begin(); t++; } if(t) { print(); } getch(); //制造停顿 //初始化 bird_y=300; bird_x=150; i=0; k=0; scole=0; 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%5==0) i++; k++; scoleprint(); FlushBatchDraw(); // 绘制 } void printstone()//柱子移动规律/哭 { Sleep(30); if(stone_x1>210)//此时画面存在两根柱子 { //三光栅操作 putimage(stone_x1, stone_y1, &stone_up2,NOTSRCERASE); //掩码图 putimage(stone_x1, stone_y1, &stone_up1,SRCINVERT); //精灵图 putimage(stone_x1, stone_y1+750, &stone_down2,NOTSRCERASE); putimage(stone_x1, stone_y1+750, &stone_down1,SRCINVERT); putimage(stone_x2, stone_y2, &stone_up4,NOTSRCERASE); putimage(stone_x2, stone_y2, &stone_up3,SRCINVERT); putimage(stone_x2, stone_y2+750, &stone_down4,NOTSRCERASE); putimage(stone_x2, stone_y2+750, &stone_down3,SRCINVERT); stone_x1--; stone_x2--; } else if(stone_x1==210)//左柱子消失,将stone_x2,stone_y2值归位 { stone_x2=stone_x1; stone_y2=stone_y1; putimage(stone_x1, stone_y1, &stone_up2,NOTSRCERASE); putimage(stone_x1, stone_y1, &stone_up1,SRCINVERT); putimage(stone_x1, stone_y1+750, &stone_down2,NOTSRCERASE); putimage(stone_x1, stone_y1+750, &stone_down1,SRCINVERT); stone_x1--; stone_x2--; } else if(stone_x1<210&&stone_x1>0)//画面只存在一根柱子的情况 { putimage(stone_x1, stone_y1, &stone_up2,NOTSRCERASE); putimage(stone_x1, stone_y1, &stone_up1,SRCINVERT); putimage(stone_x1, stone_y1+750, &stone_down2,NOTSRCERASE); putimage(stone_x1, stone_y1+750, &stone_down1,SRCINVERT); stone_x1--; stone_x2--; } if(stone_x1==0)//柱子左端end,生成新柱子 { stone_y1=rand()%310-555; stone_x1=350; putimage(stone_x1, stone_y1, &stone_up2,NOTSRCERASE); putimage(stone_x1, stone_y1, &stone_up1,SRCINVERT); putimage(stone_x1, stone_y1+750, &stone_down2,NOTSRCERASE); putimage(stone_x1, stone_y1+750, &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_y1=rand()%310-555; stone_x1=350; stone_x2=stone_y2=-9999; //避免一开始显示两管道 } void bird() { char space; if(kbhit()) //检测按键 { space=getch(); if(space==' ') //检测空格 { bird_y-=80; } else if(space==27) //ESC { getch(); } } else { bird_y+=3; } while(MouseHit()) { // 获取一条鼠标消息 = GetMouseMsg(); switch(m.uMsg) { case WM_LBUTTONDOWN: bird_y-=80; break; case WM_RBUTTONDOWN: getch(); break; } } } void judgement() { if((stone_x1>10 && stone_x1<20)||(stone_x1>174&&stone_x1<184)) { if((bird_y>(stone_y1+576)&&bird_y<(stone_y1+600))||((bird_y>(stone_y1+726))&&bird_y<(stone_y1+750))) { endorretry();//结束游戏 } } else if(stone_x1>=20 && stone_x1<=174) { if(!(bird_y>(stone_y1+600)&&bird_y<(stone_y1+726))) { endorretry(); //结束游戏 } } if(bird_y>576) { endorretry();//结束游戏 } if(stone_x1==150) { scole++; } } void endorretry() { if(bird_y<550 && stone_x1>160) { while(bird_y<550) { putimage(0, 0, &background);//背景图像 putimage(stone_x1, stone_y1, &stone_up2,NOTSRCERASE); putimage(stone_x1, stone_y1, &stone_up1,SRCINVERT); putimage(stone_x1, stone_y1+750, &stone_down2,NOTSRCERASE); putimage(stone_x1, stone_y1+750, &stone_down1,SRCINVERT); putimage(stone_x2, stone_y2, &stone_up4,NOTSRCERASE); putimage(stone_x2, stone_y2, &stone_up3,SRCINVERT); putimage(stone_x2, stone_y2+750, &stone_down4,NOTSRCERASE); putimage(stone_x2, stone_y2+750, &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_y1=rand()%310-555; stone_x1=350; stone_x2=stone_y2=-9999; bird_y=300; scole=0; main(); } void scoleprint() { IMAGE *fen1[6],*fen2[6]; int he,weishu=1,i=0,sdsf=scole; if(sdsf==0) { putimage(250,50,&scole1[0],NOTSRCERASE); putimage(250,50,&scole2[0],SRCINVERT); } while(sdsf>0) { he=sdsf%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++; } }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询