会 C 语言就能做游戏? C 语言实现 flappy bird

前言

flappy bird 是一款在 2014 年爆火的游戏,相信大家都曾听说过或者玩过,现在我们将会采用 C 语言来实现这个游戏的基本玩法。我们不用再像之前编写 C 程序一样面对黑乎乎的命令行窗口,而是从制作一款小游戏中接触可视化编程。

程序效果

准备工作

VS2019 下载及配置

我选择的开发工具是 VS2019(点击前往下载教程)去掉了安全检查以及将字符集换成了多字符集(点击前往教程)

图形库 EasyX 下载

可视化编程需要图形库,需要自己下载(点击前往下载安装教程),这里我们选择easyx,这个图形库对新手比较友好。
在这里插入图片描述

程序编写

相信很多未曾接触过 EasyX 图形库的同学可能担心不能立即上手这个图形库,但没有关系,本文是面向只有 C 语言基础的同学,用到的 API 都会有说明,仍然不太理解可以去查阅 EasyX 的官方文档(点击跳转官方文档

快速上手

之前我们都是从命令行窗口读取命令,并输出结果到命令行中的,但想要显示图形,那就得有一个图形窗口了,这时我们就可以使用 EasyX 的 initgraph 函数去创建一个图形窗口了,既然有创建窗口对应的函数,那么自然也有关闭窗口函数 closegraphinitgraphclosegraph 函数原型如下

/*
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


游戏素材网站

  1. The Spriters Resource
    地址:https://www.spriters-resource.com
    https://www.sounds-resource.com
    介绍:非常丰富齐全的游戏素材网站,并且按照游戏主机分类,包括 NES、PS、GBA 等各大游戏平台的游戏,素材基本都是抠好图的 PNG 格式,非常好用。想做高仿游戏的同学不要错过。

    还有 3D 模型、贴图、音效等素材。就是国外网站,访问速度有点慢。

  2. Open Game Art
    地址:https://opengameart.org

    介绍:很全很好用的游戏资源网站,就是国外网站的访问速度有点慢。

  3. 爱给网
    地址:http://www.aigei.com

    介绍:国内的游戏素材网站,也很好用齐全。不过有个不足:不登录不让下载。

总结

这是第一次在CSDN上写文章,如有不妥之处还请多多包涵。

  • 29
    点赞
  • 161
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值