前言
flappy bird 是一款在 2014 年爆火的游戏,相信大家都曾听说过或者玩过,现在我们将会采用 C 语言来实现这个游戏的基本玩法。我们不用再像之前编写 C 程序一样面对黑乎乎的命令行窗口,而是从制作一款小游戏中接触可视化编程。
程序效果
准备工作
VS2019 下载及配置
我选择的开发工具是 VS2019(点击前往下载教程),去掉了安全检查以及将字符集换成了多字符集(点击前往教程)。
图形库 EasyX 下载
可视化编程需要图形库,需要自己下载(点击前往下载安装教程),这里我们选择easyx,这个图形库对新手比较友好。
程序编写
相信很多未曾接触过 EasyX 图形库的同学可能担心不能立即上手这个图形库,但没有关系,本文是面向只有 C 语言基础的同学,用到的 API 都会有说明,仍然不太理解可以去查阅 EasyX 的官方文档(点击跳转官方文档)
快速上手
之前我们都是从命令行窗口读取命令,并输出结果到命令行中的,但想要显示图形,那就得有一个图形窗口了,这时我们就可以使用 EasyX 的 initgraph
函数去创建一个图形窗口了,既然有创建窗口对应的函数,那么自然也有关闭窗口函数 closegraph
,initgraph
与 closegraph
函数原型如下
/*
width 绘图窗口的宽度。
height 绘图窗口的高度。
flag 绘图窗口的样式,默认为 NULL。(flag 为 NULL 时会关闭原来自带的命令行窗口,取而代之的是一个图形化窗口)
*/
HWND initgraph(int width, int height, int flag = NULL); // 初始化图形环境
void closegraph(); // 这个函数用于关闭绘图窗口。
这时我们便可以尝试一下调用这个函数了,这里我写了一个简单的 demo,我们一起来看看运行结果
注意我们这里需要创建一个 cpp 文件,因为 EasyX 实际是一个 C++ 库,但无需担心,大多数情况下,在 cpp 文件写 C 代码和在 c 文件中可以实现相同的效果的
#include <graphics.h>
#include <conio.h>
// 定义窗口大小
const int win_width = 288;
const int win_height = 624;
int main()
{
initgraph(win_width, win_height); // 创建一个 win_width * win_height 绘图窗口,不显示命令行窗口
_getch(); // 等待用户输入
closegraph(); // 关闭窗口
return 0;
}
其中 _getch
函数的作用是从控制台读取一个字符(无需键入回车),但不显示在屏幕上(不带回显)
但这个时候因为我们没有在这个窗口上显示图片,因此界面还是黑乎乎的一片,那我们来学习一下怎么在窗口中显示图片吧。
我们直接上代码
#include <graphics.h>
#include <conio.h>
#include <stdio.h>
// 定义窗口大小
const int win_width = 288;
const int win_height = 624;
int main()
{
initgraph(win_width, win_height); // 创建一个 win_width * win_height 绘图窗口,不显示命令行窗口
IMAGE background[2]; // 创建两个 IMAGE 对象,用于存储图片的一些信息
for (int i = 0; i < 2; i++)
{
char path[100] = { 0 };
sprintf(path, "./image/background_%d.jpg", i);
loadimage(&background[i], (LPCTSTR)path); // 加载图片信息,即将文件路径下的图片的信息保存到 IMAGE 对象中
}
putimage(0, 0, &background[0]); // 在窗口的 (0, 0) 位置上显示图片,即图片的左上角坐标为 (0, 0)
putimage(0, 0 + background[0].getheight(), &background[1]);
_getch(); // 等待用户输入
closegraph(); // 关闭窗口
return 0;
}
注意点
- IMAGE 是个对象,这里不用深究什么是对象,简单理解为一种绑定了一些函数的结构体即可
"./image/background1.jpg"
使用的是相对路径,注意的是,直接在 VS2019 中运行,它相对的是工程路径
,因此将文末提供的image
文件夹放在工作路径下即可background[0].getheight()
这个为获取第一张图片的高度,因为背景是由 2 张背景图片构成的,因此采用这种方式显式图片
柱子与鸟
接着便是贴小鸟和柱子的图片了,但是与贴背景图不同的是这里的小鸟和柱子并不是一个规则的图形,所以我们采用透明贴图技术将小鸟和柱子贴上去。具体方法可以参考下面这篇文章。
https://blog.csdn.net/qq969422014/article/details/44536375
因为柱子的高度是随机而且成对出现的,我们需要一个随机数去模拟上面柱子及下面柱子的高度,我们使用结构体的方法去写柱子。
//下面的x,y都指其所在窗口的位置
//定义小鸟的结构体
struct huoyin
{
int x;
int y;
int speed;//小鸟弹跳的距离
};
struct huoyin hero = { 100 , 240 , 70 };
//三对柱子的结构体
struct walls
{
int x, y;
int height;//上面柱子的高
};
struct walls pillar[3];
void pillars(struct walls pillar[],int i)
{
pillar[i].height = rand() % 100 + 120;
pillar[i].x = 288;
pillar[i].y = 0;
}
//贴柱子
void drawWall(struct walls nowpillar)
{
//上面柱子
putimage(nowpillar.x, 0, 52, nowpillar.height, wall, 0, 320 - nowpillar.height, SRCAND);
putimage(nowpillar.x, 0, 52, nowpillar.height, wall + 1, 0, 320 - nowpillar.height, SRCPAINT);
//下面柱子
putimage(nowpillar.x, 512 - (320 - nowpillar.height), 52, 320 - nowpillar.height, wall + 2, 0, 0, SRCAND);
putimage(nowpillar.x, 512 - (320 - nowpillar.height), 52, 320 - nowpillar.height, wall + 3, 0, 0, SRCPAINT);
}
//以时间为种子构建随机函数
#include <time.h>//需要的头文件
srand(time(NULL));
//移动柱子
for (int i = 0; i < 3; i++)
{
pillar[i].x -= 10;
}
//柱子的初始化
for (int i = 0; i < 3; i++)
{
pillars(pillar, i);
//每对柱子等距离
pillar[i].x = 288 + i * 150;
}
//不断创造新的柱子
for (int i = 0; i < 3; i++)
{
drawWall(pillar[i]);
if (pillar[i].x <= -158)
{
pillars(pillar, i);
}
}
介绍一下在贴柱子使用的 putimage(x,y,width,height,&IMAGE,xx,yy)函数,这个函数是从图片(&IMAGE)的 x,y 坐标截取 width 宽与 height 高贴在窗口的 xx,yy 坐标上。
多线程播放音乐
为了不让播放音乐影响画面,我们采用多线程播放音乐。
#include <mmsystem.h>
#pragma comment(lib,"winmm.lib")
//播放音乐
DWORD WINAPI playMusic(LPVOID lpvoid)
{
mciSendString("open 卡通弹跳.wav alias music", 0, 0, 0);
mciSendString("play music wait", 0, 0, 0);
mciSendString("close music", 0, 0, 0);
return 0;
}
小鸟的弹跳
接着就要实现使用空格让小鸟弹跳了。
//按键响应
void keyDown()
{
char keys = _getch();
switch(keys)
{
case ' ':
hero.y -= hero.speed;
CreateThread(NULL, NULL, playMusic, NULL, NULL, NULL);
break;
default:
break;
}
}
判断游戏是否结束
//判断游戏是否结束
int overs()
{
if (hero.y < 0 || hero.y > 440 )
return 1;
for (int i = 0; i < 3; i++)
{
if (hero.x > pillar[i].x && hero.x < pillar[i].x + 52 - 12)
{
if(hero.y < pillar[i].height || hero.y > pillar[i].height + 112 + 38)
return 1;
}
else return 0;
}
}
判断游戏结束方面还存在一些问题,如有高见可以留言指出来,感谢!
游戏结束动画
//游戏结束动画
void overMovies()
{
int x = 42, y = 700;
while (y >= 200)
{
putimage(0, 0, &bg[0]);
putimage(0, 512, &bg[1]);
putimage(x, y, &end[0], SRCAND);
putimage(x, y, &end[1], SRCPAINT);
y -= 30;
Sleep(100);//防止出现掩码图与彩图交替出现
}
}
完整代码
#include <stdio.h>
#include <conio.h>
#include <graphics.h>
#include <time.h>
#include <mmsystem.h>
#pragma comment(lib,"winmm.lib")
//背景图\小鸟图\游戏结束图\柱子图
IMAGE bg[2],bird[2][3],end[2],wall[4];
//定义小鸟的结构体
struct huoyin
{
int x;
int y;
int speed;
};
struct huoyin hero = { 100 , 240 , 70 };
//三对柱子的结构体
struct walls
{
int x, y;
int height;
};
struct walls pillar[3];
void pillars(struct walls pillar[],int i)
{
pillar[i].height = rand() % 100 + 120;
pillar[i].x = 288;
pillar[i].y = 0;
}
//批量加载资源(给文件夹里的图片命名)
void jiazai()
{
loadimage(&bg[0], "4.jpg");
loadimage(&bg[1], "5.jpg");
loadimage(&bird[0][0], "bird0_0.png");
loadimage(&bird[1][0], "bird1_0.png");
loadimage(&bird[0][1], "bird0_1.png");
loadimage(&bird[1][1], "bird1_1.png");
loadimage(&bird[0][2], "bird0_2.png");
loadimage(&bird[1][2], "bird1_2.png");
loadimage(&end[0], "11.jpg");
loadimage(&end[1], "8.jpg");
loadimage(&wall[0], "9.jpg");
loadimage(&wall[1], "6.jpg");
loadimage(&wall[2], "10.jpg");
loadimage(&wall[3], "7.jpg");
}
//透明贴图(掩码图加原图)
/*
void drawHero()
{
putimage(hero.x, hero.y, &bird[0][1], SRCAND);
putimage(hero.x, hero.y, &bird[1][1], SRCPAINT);
}*/
//贴柱子*****
void drawWall(struct walls nowpillar)
{
//上面柱子
putimage(nowpillar.x, 0, 52, nowpillar.height, wall, 0, 320 - nowpillar.height, SRCAND);
putimage(nowpillar.x, 0, 52, nowpillar.height, wall + 1, 0, 320 - nowpillar.height, SRCPAINT);
//下面柱子
putimage(nowpillar.x, 512 - (320 - nowpillar.height), 52, 320 - nowpillar.height, wall + 2, 0, 0, SRCAND);
putimage(nowpillar.x, 512 - (320 - nowpillar.height), 52, 320 - nowpillar.height, wall + 3, 0, 0, SRCPAINT);
}
//播放音乐
DWORD WINAPI playMusic(LPVOID lpvoid)
{
mciSendString("open 卡通弹跳.wav alias music", 0, 0, 0);
mciSendString("play music wait", 0, 0, 0);
mciSendString("close music", 0, 0, 0);
return 0;
}
//按键响应
void keyDown()
{
char keys = _getch();
switch(keys)
{
case ' ':
hero.y -= hero.speed;
CreateThread(NULL, NULL, playMusic, NULL, NULL, NULL);
break;
default:
break;
}
}
//判断游戏是否结束
int overs()
{
if (hero.y < 0 || hero.y > 440 )
return 1;
for (int i = 0; i < 3; i++)
{
if (hero.x > pillar[i].x && hero.x < pillar[i].x + 52 - 12)
{
if(hero.y < pillar[i].height || hero.y > pillar[i].height + 112 + 38)
return 1;
}
else return 0;
}
}
//游戏结束动画
void overMovies()
{
int x = 42, y = 700;
while (y >= 200)
{
putimage(0, 0, &bg[0]);
putimage(0, 512, &bg[1]);
putimage(x, y, &end[0], SRCAND);
putimage(x, y, &end[1], SRCPAINT);
y -= 30;
Sleep(100);
}
}
int main()
{
srand(time(NULL));
jiazai();
//柱子的初始化
for (int i = 0; i < 3; i++)
{
pillars(pillar, i);
//每对柱子等距离
pillar[i].x = 288 + i * 150;
}
initgraph(288, 624);
for (int num = 0; num < 3; num++)
{
putimage(0, 0, &bg[0]);
putimage(0, 512, &bg[1]);
putimage(hero.x, hero.y, &bird[1][num], SRCAND);
putimage(hero.x, hero.y, &bird[0][num], SRCPAINT);
//移动柱子
for (int i = 0; i < 3; i++)
{
pillar[i].x -= 10;
}
if (overs())
break;
//不断创造新的柱子
for (int i = 0; i < 3; i++)
{
drawWall(pillar[i]);
if (pillar[i].x <= -158)
{
pillars(pillar, i);
}
}
if (overs())
break;
hero.y += 20;
if (overs())
break;
if (_kbhit())
{
keyDown();
if (overs())
break;
}
if (num == 2)num = 0;
Sleep(100);
}
Sleep(240);
overMovies();
_getch();
closegraph();
system("pause");
return 0;
}
因为我还给小鸟做了扇动翅膀的动画,所以这个for (int num = 0; num < 3; num++)循环是加载鸟的三张图片。
用到的素材
由于有小伙伴找反映不到对应的素材,所以我将素材也放在网盘上分享了
链接:https://pan.baidu.com/s/1ovDvFtqIKIhoCfn0Z11PkA
提取码:1234
游戏素材网站
-
The Spriters Resource
地址:https://www.spriters-resource.com
https://www.sounds-resource.com
介绍:非常丰富齐全的游戏素材网站,并且按照游戏主机分类,包括 NES、PS、GBA 等各大游戏平台的游戏,素材基本都是抠好图的 PNG 格式,非常好用。想做高仿游戏的同学不要错过。还有 3D 模型、贴图、音效等素材。就是国外网站,访问速度有点慢。
-
Open Game Art
地址:https://opengameart.org介绍:很全很好用的游戏资源网站,就是国外网站的访问速度有点慢。
-
爱给网
地址:http://www.aigei.com介绍:国内的游戏素材网站,也很好用齐全。不过有个不足:不登录不让下载。
总结
这是第一次在CSDN上写文章,如有不妥之处还请多多包涵。