前言
本篇文章将会一步一步使用C语言从零开始写出天天酷跑的项目游戏,使用的视频教程是👉C语言天天酷跑,也可以叫做博主自己的笔记,因为博主想要提升自己的开发能力(虽然我是学网安的),以下所有文字均来自以上视频的,笔记,应该是笔记加上自己的理解,你可以去上面视频加群下载素材,有可以从下面百度网盘链接下载素材。
一.游戏背景的实现
一个游戏,首先需要有窗口,然后窗口里面有内容,第一章实现如下.
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.h
和tools.cpp
还有res
整个文件夹,放在项目文件同目录下面,如下图
然后回到Visual stdio,然后右键项目 > 添加 > 现有项,选择我们刚才复制的tools.h
和tools.cpp
即可,最后在我们的文件上面导入新的头文件即可。
#include "tools.h"
用到的函数有👇。
sprintf():作用是将格式化的数据写入字符串
loadimage():加载图片
putimagePNG():在想要的坐标上面显示图片
代码如下👇,从main函数开始,执行init的时候,一直到init第一个循环,为什么要这个循环呢?因为,有三张背景图片,所以for循环就下小于3,然后里面的sprintf是格式化字符串,所以定义了name数组,然后sprintf里面的bg%03d
指的是最小输出为三个字符,d表示整数,0表示,如果不足三位从左边开始用0补齐
,由于我们这里的图片名字是bg001
和bg002
这种命名方法,就非常好用,后面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;
}