还在加载 请稍等一下辣ヾ(≧▽≦*)o . . .

从零开始的C语言做天天酷跑项目


前言

本篇文章将会一步一步使用C语言从零开始写出天天酷跑的项目游戏,使用的视频教程是👉C语言天天酷跑,也可以叫做博主自己的笔记,因为博主想要提升自己的开发能力(虽然我是学网安的),以下所有文字均来自以上视频的,笔记,应该是笔记加上自己的理解,你可以去上面视频加群下载素材,有可以从下面百度网盘链接下载素材。

链接:https://pan.baidu.com/s/1t7wS3nDPYzd9_yLWEqN0Lw
提取码:je2i


一.游戏背景的实现

一个游戏,首先需要有窗口,然后窗口里面有内容,第一章实现如下.

1.创建项目

2.创建窗口

3.窗口实现背景的加载和无限加载

这里博主使用的Visual Studio 2022版本来进行的开发,视频是使用的是2019版本,前提是需要提前下载好easyX图形库,和Visual Studio的安装,如果你不知道怎么配置Visual Stdio 2022可以看👉配置Visual Stdio 2022,这个视频。

图形库的下载地址:https://easyx.cn/
Visual Stdio 2022下载地址:https://visualstudio.microsoft.com/zh-hans/vs/

这里就正式开始写代码了,创建一个cpp文件,测试是否能运行C语言程序

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int main() {
	printf("hello world");

	return 0;
}

1.创建窗口

用到的函数有👇。

initgraph():创建窗口

system(“pause”):暂停黑窗口

这里定义了一个Init函数来做初始化如下👇,然后使用了easyX里面的initgraph函数,函数官方用法👉initgraph用法graphics.h,必须要安装EasyX才能使用,是一个图形库,然后定义了常量define WIN_WIDTH 1012宽度为1012然后高度#define WIN_HEIGHT 396为396。

main函数开始执行,首先执行init函数,然后创建一个宽度1012高位396的窗口,这里system(“pause”)的意思是执行到这个地方暂停黑窗口。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <graphics.h>

#define WIN_WIDTH 1012
#define WIN_HEIGHT 396

void init(){

	initgraph(WIN_WIDTH, WIN_HEIGHT);
}

int main() {
	init();
	system("pause");
}

如下图就成功的创建了一个窗口。

2.加载背景资源

现在窗口创建OK了,就需要往窗口里面塞东西了,这里需要做一下项目设置,右键项目名字 > 属性 > 高级 > 如下图所示设置,不然会有莫名其妙的红色下划线。

然后,需要把素材包里面的tools.htools.cpp还有res整个文件夹,放在项目文件同目录下面,如下图

然后回到Visual stdio,然后右键项目 > 添加 > 现有项,选择我们刚才复制的tools.htools.cpp即可,最后在我们的文件上面导入新的头文件即可。

#include "tools.h"

用到的函数有👇。

sprintf():作用是将格式化的数据写入字符串

loadimage():加载图片

putimagePNG():在想要的坐标上面显示图片

代码如下👇,从main函数开始,执行init的时候,一直到init第一个循环,为什么要这个循环呢?因为,有三张背景图片,所以for循环就下小于3,然后里面的sprintf是格式化字符串,所以定义了name数组,然后sprintf里面的bg%03d指的是最小输出为三个字符,d表示整数,0表示,如果不足三位从左边开始用0补齐,由于我们这里的图片名字是bg001bg002这种命名方法,就非常好用,后面i+1的作用是让0变成1,然后1变成2,匹配资源文件里面的命名。

loadimage()函数,上面准备好了字符串在name里面,然后使用loadimage函数取出来就好了,因为上面我们定义了IMAGE对象,只需要让name里面的东西放进IMAGE对象即可。

最后执行updateBg函数,就是将我们上面加载好的图片放进窗口进行展示,这里putimagePNG2是tools.h的内容,所以需要提前导入,putimagePNG2的使用方法是第一个参数为x轴的数据,第二个参数是y轴的数据,第三个参数为要加载的图片,经过测试,Y轴每个图片,的高度分别是0,119,330

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <graphics.h>
#include "tools.h"

#define WIN_WIDTH 1012
#define WIN_HEIGHT 396

//定义IMAGE对象,因为背景有三张图片就使用了数组
IMAGE imgBgs[3];

void init(){
	//创建窗口
	char name[64];
	initgraph(WIN_WIDTH, WIN_HEIGHT);
	for (int i = 0; i < 3; i++)
	{	
		sprintf(name,"res/bg%03d.png",i+1);
		loadimage(&imgBgs[i], name);
	}
}
void updateBg() {
	putimagePNG2(0,	0,&imgBgs[0]);
	putimagePNG2(0, 119, &imgBgs[1]);
	putimagePNG2(0, 330, &imgBgs[2]);

}
int main() {
	init();
	updateBg();
	system("pause");
}

实现效果如下图👇

3.实现背景资源的移动&&无限加载

我们需要让我们的背景从右往左移动,也就是减少,需要改变的也就是我们的X轴,我们上面把X轴写死全部为0了,我们不能写死X轴。

使用的函数如下👇。

BeginBatchDraw():执行批量绘图

EndBatchDraw():批量绘图结束

fly():自定义函数来实现x轴的,从右往左

首先,自定义了Bgspeed数组,这个数组的作用是,来定义不同背景图片的,速度。

定义了fly函数使用循环来判断,三个背景图片,bgX[i] -= Bgspeed[i];这句话的意思是当前的X轴位置减去,对应的速度,一直循环调用,这样就已经实现了,但是有几个BUG,分别是,图片走完了就没了,然后回闪烁,解决办法就是,在fly函数里面增加一个判断,判断背景图片当前的X轴,是否小于了窗口的宽度,如果小于了窗口的宽度,那么就重新变为0.

main函数里面一个while循环,来一直调用updateBg这个函数,也就是,一直刷新窗口,比如,窗口的移动就是这个刷新的,然后BeginBatchDraw()函数,这个的作用是,防止闪烁,出现闪烁的原因是,加载不是同时的,因为执行updateBg函数的时候里面回一直在屏幕上显示图片,只不过是从上往下显示,如果使用了这个BeginBatchDraw函数,那么就是先全部执行完毕之后在再屏幕上面渲染,EndBatchDraw这个函数是BeginBatchDraw结束函数,官方用法👉https://docs.easyx.cn/zh-cn/BeginBatchDraw。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <graphics.h>
#include "tools.h"
//定义常量
#define WIN_WIDTH 1012
#define WIN_HEIGHT 396
/*
	基于easyx搭建

*/

//背景图片
IMAGE imgBgs[3];
//背景图片x坐标
int bgX[3];
//定义不同背景的速度
int Bgspeed[3] = { 1,2,4 };
//游戏的初始化;
void init() {
	//创建游戏窗口
	initgraph(WIN_WIDTH, WIN_HEIGHT);

	//加载背景资源
	char name[64];
	for (int i = 0; i < 3; i++)
	{
		//res/bg001 ,res/bg002
		//sprintf将格式化的数据写入字符串
		sprintf(name, "res/bg%03d.png", i + 1);
		//loadimage加载图片
		loadimage(&imgBgs[i], name);

		//初始化背景x坐标
		bgX[i] = 0;
	}
}
//定义不同背景移动速度
void fly() {
	for (int i = 0; i < 3; i++)
	{
		bgX[i] -= Bgspeed[i];
		//判断背景图片不够长,重新拼接图片
		if (bgX[i] < -WIN_WIDTH)		//如果x轴,小于了窗口的宽度,那么X轴重新变为0
		{
			bgX[i] = 0;
		}
	}

}
//渲染游戏背景
void updateBg() {
	//渲染背景,输出到坐标,不能使用PNG版本,会出BUG
	putimagePNG2(bgX[0], 0, &imgBgs[0]);
	putimagePNG2(bgX[1], 119, &imgBgs[1]);
	putimagePNG2(bgX[2], 330, &imgBgs[2]);
}
int main() {
	init();
	//循环展示背景
	while (true)
	{
		//解决背景,闪烁的问题BeginBatchDraw和EndBatchDraw
		BeginBatchDraw();
		updateBg();
		EndBatchDraw();
		fly();
		//帧等待30毫秒
		Sleep(3);
	}

	//暂停程序的运行,不然执行一次之后会直接退出
	system("pause");

	return 0;
}

运行效果如下👇。


二.实现玩家奔跑

思路和上面一样,需要加载图片,然后把图片显示在上面就可以了。

这里是把图片放在窗口中间,然后玩家操作的有十二个图片,需要在中间循环展示十二个图片。

首先要理解如下图👇,Y轴就是345减去人物的高度

理解之后就好写代码了,和上面一样首先定义一个数组来装玩家的图片。

19	//定义主角图片对象
20	IMAGE imgHeros[12];

然后定义一个循环,这里循环还是在Init函数里面循环即可,装载方法和上面背景一样。

45	for (int i = 0; i < 12; i++)
46		{
47			//加载主角图像资源
48			sprintf(name,"res/hero%d.png",i+1);
49			loadimage(&imgHeros[i], name);
50	
51		}

这里只需要装载玩家的图片,就直接在main函数里面装载了。

80	int main() {
81		init();
82		//循环展示背景
83		while (true)
84		{
85			//解决背景,闪烁的问题BeginBatchDraw和EndBatchDraw
86			BeginBatchDraw();
87			updateBg();
88			//推送玩家图像到屏幕
89			putimagePNG2(heroX,heroY,&imgHeros[heroIndex]);
90			EndBatchDraw();
91			fly();
92			//帧等待30毫秒
93			Sleep(30);
94		}

上面的heroX和heroY分辨是定义的玩家的X轴和Y轴。

21	//定义玩家初始x轴和y轴
22	int heroX;
23	int heroY;

然后在init函数里面编写玩家X轴和Y轴的初始位置,其中.getwidth()是获取图像的宽度,.getheight是获取图像的高度

53	//设置玩家初始位置
54	heroX = WIN_WIDTH / 2 - imgHeros[0].getwidth() / 2;		//定义玩家初始X轴
55	heroY = 345 - imgHeros[0].getheight();

然后89行的heroIndex是为了,防止推送图像一直推送一张图,然后在fly函数里面做了自增。

24	//定义玩家初始帧
25	int heroIndex;

60	void fly() {
61		for (int i = 0; i < 3; i++)
62		{
63			bgX[i] -= Bgspeed[i];
64			//判断背景图片不够长,重新拼接图片
65			if (bgX[i] < -WIN_WIDTH)		//如果x轴,小于了窗口的宽度,那么X轴重新变为0
66			{
67				bgX[i] = 0;
68			}
69		}
70		//每次推图片的时候,序列都加一除以12取余,因为主角只有12个图片,11除以12取余还是11.
71		heroIndex = (heroIndex + 1) % 12;
72	}

全部代码如下👇

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <graphics.h>
#include "tools.h"
//定义常量
#define WIN_WIDTH 1012
#define WIN_HEIGHT 396
/*
	基于easyx搭建

*/

//背景图片
IMAGE imgBgs[3];
//背景图片x坐标
int bgX[3];
//定义不同背景的速度
int Bgspeed[3] = { 1,2,4 };
//定义主角图片对象
IMAGE imgHeros[12];
//定义玩家初始x轴和y轴
int heroX;
int heroY;
//定义玩家初始帧
int heroIndex;
//游戏的初始化;
void init() {
	//创建游戏窗口
	initgraph(WIN_WIDTH, WIN_HEIGHT);

	//加载背景资源
	char name[64];
	for (int i = 0; i < 3; i++)
	{
		//res/bg001 ,res/bg002
		//sprintf将格式化的数据写入字符串
		sprintf(name, "res/bg%03d.png", i + 1);
		//loadimage加载图片
		loadimage(&imgBgs[i], name);

		//初始化背景x坐标
		bgX[i] = 0;
	}
	//遍历加载主角资源,12张图片
	for (int i = 0; i < 12; i++)
	{
		//加载主角图像资源
		sprintf(name,"res/hero%d.png",i+1);
		loadimage(&imgHeros[i], name);

	}

	//设置玩家初始位置
	heroX = WIN_WIDTH / 2 - imgHeros[0].getwidth() / 2;		//定义玩家初始X轴
	heroY = 345 - imgHeros[0].getheight();

	heroIndex = 0;
}
//定义不同背景移动速度
void fly() {
	for (int i = 0; i < 3; i++)
	{
		bgX[i] -= Bgspeed[i];
		//判断背景图片不够长,重新拼接图片
		if (bgX[i] < -WIN_WIDTH)		//如果x轴,小于了窗口的宽度,那么X轴重新变为0
		{
			bgX[i] = 0;
		}
	}
	//每次推图片的时候,序列都加一除以12取余,因为主角只有12个图片,11除以12取余还是11.
	heroIndex = (heroIndex + 1) % 12;
}
//渲染游戏背景
void updateBg() {
	//渲染背景,输出到坐标,不能使用PNG版本,会出BUG
	putimagePNG2(bgX[0], 0, &imgBgs[0]);
	putimagePNG2(bgX[1], 119, &imgBgs[1]);
	putimagePNG2(bgX[2], 330, &imgBgs[2]);
}
int main() {
	init();
	//循环展示背景
	while (true)
	{
		//解决背景,闪烁的问题BeginBatchDraw和EndBatchDraw
		BeginBatchDraw();
		updateBg();
		//推送玩家图像到屏幕
		putimagePNG2(heroX,heroY,&imgHeros[heroIndex]);
		EndBatchDraw();
		fly();
		//帧等待30毫秒
		Sleep(30);
	}

	//暂停程序的运行,不然执行一次之后会直接退出
	system("pause");

	return 0;
}

运行效果如下。


三.实现玩家跳跃

思路是,当我按下键盘的某个按键,比如空格,然后玩家里面就跳跃起来,怎么实现跳跃呢?,思路是让玩家人物的Y坐标自减,但是不能一直自减,如果一直自减,那么就会上天,所以自减到一定的位置之后,停止自减,这时候人物是悬空的,然后在让人物Y轴直接开始自增,自增就是下落,但是也不能一直自增需要定义一个量,自增到什么地方的时候就可以不用自增了。

首先定义一个函数,这个函数的作用是来判断键盘按下和判断是否为空格,_kbhit()函数的作用是判断键盘按下,如果按下就返回真。

然后_getch()函数是用来判断字符,无需回车,类似于scanf,scanf输入之后需要回车,然后120行,判断是否键盘为空格,如果是空格那么久执行jump函数。

116	void KeyEvent() {
117		if (_kbhit()){
118			char ch;
119			ch = _getch();
120			if (ch == ' ') {
121				jump();
122			}
123		}
124	
125	}

这里想的是,定义一个开关来跳跃,当按下键盘的空格的时候开关打开,跳跃结束就关闭如下,然后还需要在init函数里面定义跳跃状态初始。

27	//定义玩家跳跃状态
28	bool heroJump;


65	//初始玩家跳跃状态
66	heroJump = false;

然后就可以在jump函数里面定义跳跃为真。

112	void jump() {
113		heroJump = true;
114	}

现在需要解决的另外一个问题是实现跳跃之后Y坐标自减,然后到了一定程度,就停止自减开始自增,然后又到一定程度停止自增,跳跃结束。

31	//定义最高跳跃高度;
32	int heroJumpHightMax;

然后在定义最高高度,思路是既然我们定义了人物高度,那么跳跃的时候比人物高度高多少个像素即可,如下让人物跳跃120个像素

69	//定义最大跳跃高度,初始高度减去120就是跳跃的高度
70	heroJumpHightMax = 345 - imgHeros[0].getheight() - 120;

然后还需要定义跳跃的速度,不可能让人物瞬间跳跃瞬间下降吧,所以需要定义一个跳跃速度,让人物跳跃的时候一次减多少个像素,如下。

29	//定义玩家跳跃速度
30	int heroJumpSpeed;

67	//初始玩家跳跃速度
68	heroJumpSpeed = 4;

然后可以在fly函数里面执行动作了,如下代码,87行首先判断heroJump是否为真,如果为真,那么判断heroY是否小于最大高度,第一次判断肯定没有,所以就执行91行的自增,因为C语言,Y轴是反过来的,上面是负数,下面是正数。

所以就一直自增因为是反过来的所以必须要小于,当达到最大值的时候,重新赋值为4,正数,就是往下,但是不能一直往下,所以做了第二个判断,如果大于了图片原始高度,那么就是false重新赋值-4,下面else修改了一下,如果没有跳跃就反复循环图片。

86	//执行玩家跳跃
87	if (heroJump){
88		if (heroY < heroJumpHightMax) {
89			heroJumpSpeed = 4;
90		}
91		heroY += heroJumpSpeed;
92
93		if (heroY > 345 - imgHeros[0].getheight())
94		{
95			heroJump = false;
96			heroJumpSpeed = -4;
97		}
98	}
99	else
100	{
101		//每次推图片的时候,序列都加一除以12取余,因为主角只有12个图片,11除以12取余还是11.
102		heroIndex = (heroIndex + 1) % 12;
103	}

全部代码如下👇。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <graphics.h>
#include "tools.h"
#include <conio.h>
//定义常量
#define WIN_WIDTH 1012
#define WIN_HEIGHT 396
/*
	基于easyx搭建

*/

//背景图片
IMAGE imgBgs[3];
//背景图片x坐标
int bgX[3];
//定义不同背景的速度
int Bgspeed[3] = { 1,2,4 };
//定义主角图片对象
IMAGE imgHeros[12];
//定义玩家初始x轴和y轴
int heroX;
int heroY;
//定义玩家初始帧
int heroIndex;
//定义玩家跳跃状态
bool heroJump;
//定义玩家跳跃速度
int heroJumpSpeed;
//定义最高跳跃高度;
int heroJumpHightMax;
//游戏的初始化;
void init() {
	//创建游戏窗口
	initgraph(WIN_WIDTH, WIN_HEIGHT);

	//加载背景资源
	char name[64];
	for (int i = 0; i < 3; i++)
	{
		//res/bg001 ,res/bg002
		//sprintf将格式化的数据写入字符串
		sprintf(name, "res/bg%03d.png", i + 1);
		//loadimage加载图片
		loadimage(&imgBgs[i], name);

		//初始化背景x坐标
		bgX[i] = 0;
	}
	//遍历加载主角资源,12张图片
	for (int i = 0; i < 12; i++)
	{
		//加载主角图像资源
		sprintf(name,"res/hero%d.png",i+1);
		loadimage(&imgHeros[i], name);

	}

	//设置玩家初始位置
	heroX = WIN_WIDTH / 2 - imgHeros[0].getwidth() / 2;		//定义玩家初始X轴
	heroY = 345 - imgHeros[0].getheight();
	//初始玩家资源帧
	heroIndex = 0;
	//初始玩家跳跃状态
	heroJump = false;
	//初始玩家跳跃速度
	heroJumpSpeed = -4; 
	//定义最大跳跃高度,初始高度减去120就是跳跃的高度
	heroJumpHightMax = 345 - imgHeros[0].getheight() - 120;

}
//定义不同背景移动速度 
void fly() {
	for (int i = 0; i < 3; i++)
	{
		bgX[i] -= Bgspeed[i];
		//判断背景图片不够长,重新拼接图片
		if (bgX[i] < -WIN_WIDTH)		//如果x轴,小于了窗口的宽度,那么X轴重新变为0
		{
			bgX[i] = 0;
		}
	}
	

	//执行玩家跳跃
	if (heroJump){

		if (heroY < heroJumpHightMax) {
			heroJumpSpeed = 10;
		}
	
		heroY += heroJumpSpeed;

		
		if (heroY > 345 - imgHeros[0].getheight())
		{
			heroJump = false;
			heroJumpSpeed = -4;
		} 

	}
	else
	{
		//每次推图片的时候,序列都加一除以12取余,因为主角只有12个图片,11除以12取余还是11.
		heroIndex = (heroIndex + 1) % 12;
	}
}
//渲染游戏背景
void updateBg() {
	//渲染背景,输出到坐标,不能使用PNG版本,会出BUG
	putimagePNG2(bgX[0], 0, &imgBgs[0]);
	putimagePNG2(bgX[1], 119, &imgBgs[1]);
	putimagePNG2(bgX[2], 330, &imgBgs[2]);
}
void jump() {
	heroJump = true;
}
//键盘操作
void keyEvent() {
	char ch;
	//_kbhit是检测是否键盘按下
	if (_kbhit())
	{
		ch = _getch();
		//判断是否按下的是空格,如果是那么执行jump函数
		if (ch == ' ')
		{
			jump();
		}
	}
}
int main() {
	init();
	//循环展示背景
	while (true)
	{
		keyEvent();
		//解决背景,闪烁的问题BeginBatchDraw和EndBatchDraw
		BeginBatchDraw();
		updateBg();
		//推送玩家图像到屏幕
		putimagePNG2(heroX,heroY,&imgHeros[heroIndex]);
		EndBatchDraw();
		fly();
		//帧等待30毫秒
		Sleep(30);
	}

	//暂停程序的运行,不然执行一次之后会直接退出
	system("pause");

	return 0;
}

四.优化帧等待


文章作者: 坂琴
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 坂琴 !
评论
  目录