"我"的天天酷跑--C语言

417 阅读5分钟

从B站学的,up是:程序员Rock

DAY 1

步骤一:使用visual Studio 2019,创建c++空项目

步骤二:导入素材

点击获取百度网盘连接

步骤三:游戏界面

    天天酷跑是基于easyX图形库的
    1)创建游戏窗口
    2)实现游戏背景
    3) 实现游戏背景的滚动

image.png

#define WIN_WIDTH 1012 宏定义窗口大小
#define WIN_HEIGHT 396

IMAGE imBgs[3];// 背景图片 
int bgX[3]; // 背景的X坐标位置
int bgSpeed[3] = { 1 , 2, 4 };// 背景的滚动速度的数组

// 游戏的初始化
void init() {
	//创建游戏窗口
	initgraph(WIN_WIDTH,WIN_HEIGHT);
	// 加载游戏的背景资源
	char name[64];
	for (int i = 0; i < 3; i++) {
		// 格式化输出
		sprintf_s(name, "res/bg%03d.png",i + 1);
		loadimage(&imBgs[i],name);
		bgX[i] = 0;
	}
}
// 渲染游戏背景
/*
    三重背景同时移动,给人一种错落的感觉 
*/
void updateBg() {
	// 注意放的png透明部分后黑色边框 考虑用作者的接口
	putimagePNG2(bgX[0], 0, &imBgs[0]);
	putimagePNG2(bgX[1], 119, &imBgs[1]);
	putimagePNG2(bgX[2], 330, &imBgs[2]);
}
// 将背景滚动起来
void flyBg() {
	for (int i = 0; i < 3; i++) {
		bgX[i] -= bgSpeed[i];
		if (bgX[i] < -WIN_WIDTH) {
			bgX[i] = 0;
		}
	}
}

最后main函数中出现图片闪动的问题 在updateBg()函数前后分别加上BeginBatchDraw()和EndBatchDraw();就可以解决。

BeginBatchDraw:这个函数用于开始批量绘图,所有的绘图都不再显示在屏幕上,而是在内存中进行。

EndBatchDram:将之前在内存中的所有图片一并展示。

int main(void) {
	init();
	while (true)
	{
		BeginBatchDraw();
		updateBg();
		EndBatchDraw();
		flyBg();
	}
	system("pause"); // 暂停
	return 0;
}

DAY 2

完成了三个部分
  1. 添加人物在背景中

  2. 为人物添加跳跃动作

  3. 在背景中添加乌龟

添加人物在背景中
IMAGE imgHeros[12];// 角色有十二帧图片
int heroX; // 玩家的x坐标
int heroY;// 玩家的Y坐标
int heroIndex; // 玩家奔跑时图片的帧序号

void init() {
// 加载玩家奔跑图片帧素材
	for (int i = 0; i < 12; i++) {
		//"res/hero1.png"
		sprintf_s(name, "res/hero%d.png", i + 1);
		loadimage(&imgHeros[i], name);
	}
	// 玩家的初始位置
	heroX = WIN_WIDTH * 0.5 - imgHeros[0].getwidth() * 0.5; 
	heroY = 345 - imgHeros[0].getheight();
	heroIndex = 0;
}

void flyBg() {
        heroIndex = (heroIndex + 1) % 12; // 不跳跃的时候再改变他的跑步帧数
}
为人物添加跳跃动作
bool heroJump; // 表示玩家跳跃开关
int jumpHeightMax; // 跳跃的做大高度
int heroJumpOff; // 玩家跳跃偏移量
void init() {
	heroJump = false; // 默认玩家是不跳跃的
	jumpHeightMax = 345 - imgHeros[0].getheight() - 120; // 最大跳跃高度
	heroJumpOff = -6;
}

void flyBg() {
    // 实现跳跃
	if (heroJump) {
		if (heroY < jumpHeightMax) {
			heroJumpOff = 6;
		}
		heroY += heroJumpOff;
		if (heroY > 345 - imgHeros[0].getheight()) {
			heroJump = false;
			heroJumpOff = -6;
		}
	}
	else {
		heroIndex = (heroIndex + 1) % 12; // 不跳跃的时候再改变他的跑步帧数
	}


}

按下空格键表示要跳跃,用keyEvent()函数来接收所敲击的键盘是哪一个

// 用户按键输入
void keyEvent() {
	char ch;
	if (_kbhit()) { // 如果有按键按下去,则返回true
		ch = _getch();  // 不需要按下回车即可直接读取
		if (ch == ' ') {
			jump();
		}
	}

}

void jump() {
	heroJump = true;
	update = true;
}
在背景中添加乌龟
IMAGE imgTortoise; // 乌龟的图片
int torToiseX; // 乌龟的水平坐标
int torToiseY; // 乌龟的垂直坐标
bool torToiseExist; //  画面是否有乌龟存在

void init() {
	// 加载乌龟素材
	loadimage( &imgTortoise,"res/t1.png");
	torToiseExist = false;
	torToiseY = 345 - imgTortoise.getheight() + 5;
}


void flyBg() {
	// 创建乌龟
	static int frameCount = 0;
	static int torToiseFre = 100; // 乌龟出现的频率
	frameCount++;
	if (frameCount > torToiseFre) {
		frameCount = 0;
		if (!torToiseExist) {
			torToiseExist = true;
			torToiseX = WIN_WIDTH;

			torToiseFre = 200 + rand() % 300;
		}
	}
	if (torToiseExist) {
		torToiseX -= bgSpeed[2];
		if (torToiseX < -imgTortoise.getwidth()) {
			torToiseExist = false;
      	}
	}
}



void updateEnemy() {
	// 渲染乌龟
	if (torToiseExist) {
		putimagePNG2(torToiseX, torToiseY, WIN_WIDTH, &imgTortoise);
	}
}

main方法

int main(void) {
	init();
	int timer = 0;
	while (true)
	{
		keyEvent();
		timer += getDelay();
		if (timer > 15) {
			timer = 0;
			update = true;
			}
		if (update) {
			update = false;
			BeginBatchDraw();
			updateBg();
			updateEnemy();
			putimagePNG2(heroX, heroY, &imgHeros[heroIndex]);
			EndBatchDraw();
			flyBg();
		}
		// Sleep(1);
	}
	

	system("pause"); // 暂停
	return 0;
}

DAY 3

完成了三个部分
  1. 使用结构体封装障碍物
  2. 封装后障碍物的初始化
  3. 显示多个障碍物
使用结构体封装障碍物
障碍物属性
乌龟障碍物类型
狮子图片的索引
柱子X,Y坐标
障碍物速度
障碍物伤害
封装成如下的结构体
typedef struct  obstacle{
	obstacle_type type; // 障碍物类型
	int imgindex;
	int x, y; // 障碍物坐标
	int speed; //奔跑速度
	int power; // 杀伤力
	bool exist;
}obstacle_t;

枚举类型

C语言等计算机编程语言中是一种构造数据类型。它用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型

//存放障碍物的各个图片
vector<vector<IMAGE>> obstacleImgs;// 相当于 IMAGE obstacleImgs [3][5]  但是是可变的,可变的二维数组
typedef enum {
	TORTOISE, // 乌龟 0
	LION,	  // 狮子 1
	OBSTACLE_TYPE_COUNT  //2
} obstacle_type;
// 障碍物的池
obstacle_t obstacles[OBSTACLE_COUNT];
封装后障碍物的初始化
vodi init(){
        // 加载乌龟素材
	IMAGE imgTort;
	loadimage(&imgTort, "res/t1.png");
	vector<IMAGE> imgTortArray;
	imgTortArray.push_back(imgTort); // 先添加到一维数组中去
	obstacleImgs.push_back(imgTortArray); // 再添加到二维数组中去 

	//加载狮子素材
	IMAGE imgLion;
	vector<IMAGE> imgLionArray;
	for (int i = 0; i < 6; i++) {
		sprintf_s(name, "res/p%d.png", i + 1); // 将数据格式化输出到字符串
		loadimage(&imgLion, name);
		imgLionArray.push_back(imgLion);
	}
	obstacleImgs.push_back(imgLionArray);

	// 初始化障碍物池
	for (int i = 0; i < OBSTACLE_COUNT; i++) {
		obstacles[i].exist = false;
	}
}
显示多个障碍物

// 创建障碍物
void creatObstacle() {
	int i;
	for (i = 0; i < OBSTACLE_COUNT; i++) {
		if (obstacles[i].exist == false){
			break;
		}
	}
	if (i >= OBSTACLE_COUNT) {
		return;
	}

	obstacles[i].exist = true;
	obstacles[i].imgindex = 0;
	obstacles[i].type = (obstacle_type)(rand() % OBSTACLE_TYPE_COUNT);
	obstacles[i].x = WIN_WIDTH;
	obstacles[i].y = 345 + 5 - obstacleImgs[obstacles[i].type][0].getheight();
	if (obstacles[i].type == TORTOISE) {
		obstacles[i].speed = 0;
		obstacles[i].power = 5;
	}
	else if (obstacles[i].type == LION)
	{
		obstacles[i].speed = 4;
		obstacles[i].power = 20;
	}
	{

	}
}

void flyBg() {
// 创建障碍物
	static int frameCount = 0;
	static int enemyFre = 50;
	frameCount++;
	if (frameCount > enemyFre) {
		frameCount = 0;
		enemyFre = 50 + rand() % 50;
		creatObstacle();
	}
	// 更新所有障碍物的坐标
	for (int i = 0; i < OBSTACLE_COUNT; i++) {
		if (obstacles[i].exist) {
			obstacles[i].x -= obstacles[i].speed + bgSpeed[2];
			if (obstacles[i].x < -obstacleImgs[obstacles[i].type][0].getwidth() * 2) {
				obstacles[i].exist = false;
			}
			
			int len = obstacleImgs[obstacles[i].type].size();
			obstacles[i].imgindex = (obstacles[i].imgindex + 1) % len;
		}
	}



}

void updateEnemy() {
	//  渲染障碍物的统一方法 
	for (int i = 0; i < OBSTACLE_COUNT; i++) {
		if (obstacles[i].exist) {
			putimagePNG2(obstacles[i].x, obstacles[i].y, WIN_WIDTH,
				&obstacleImgs[obstacles[i].type][obstacles[i].imgindex]);
		}
	}
}

DAY 4

完成了两个部分
  1. 实现玩家的下蹲功能
  2. 添加柱子这个东东
实现玩家的下蹲功能

总体来说,玩家的下蹲功能与玩家的跳跃功能相似 首先定义玩家的下蹲开关:

bool heroDown; // 表示玩家的下蹲开关

玩家下蹲包括两个动作

void init(){
    // 加载玩家下蹲素材
	loadimage(&imgHeroDown[0], "res/d1.png");
	loadimage(&imgHeroDown[1], "res/d2.png");
	heroDown = false;
}
// 玩家的下蹲函数
void down() {
	heroDown = true;
	update = true;
	heroIndex = 0;
}

// 用户按键输入
void keyEvent() {
	char ch;
	if (_kbhit()) { // 如果有按键按下去,则返回true
		ch = _getch();  // 不需要按下回车即可直接读取
		if (ch == ' ') {
			jump();
		}
		else if(ch == 'a') {
			down();
		}
	}
}


void updateHero() {
	if (!heroDown) {
		putimagePNG2(heroX, heroY, &imgHeros[heroIndex]);
	}
	else {
        // 玩家下蹲
		int y = 345 - imgHeroDown[heroIndex].getheight();
		putimagePNG2(heroX,y,&imgHeroDown[heroIndex]);
	}
	
}

void flyBg() {
        if (heroJump) {
		if (heroY < jumpHeightMax) {
			heroJumpOff = 6;
		}
		heroY += heroJumpOff;
		if (heroY > 345 - imgHeros[0].getheight()) {
			heroJump = false;
			heroJumpOff = -6;
		}
	}
	else if (heroDown) { // 下蹲时
		static int count = 0;
		int delays[2] = { 4, 10 };
		count++;
		if (count >= delays[heroIndex]){
			count = 0;
			heroIndex++;
			if (heroIndex > 2) {
				heroIndex = 0;
				heroDown = false;
			}
		}
	}else{
		// 不跳跃 不下蹲时
		heroIndex = (heroIndex + 1) % 12; // 不跳跃的时候再改变他的跑步帧数
	}
}
添加柱子这个东东

同样,添加柱子这个东西就是和添加障碍物一样,这里有四种柱子

//枚举类型
typedef enum {
	TORTOISE, // 乌龟     0
	LION,	  // 狮子     1
	HOOK1,    // 柱子1    2
	HOOK2,    // 柱子2    3
	HOOK3,    // 柱子3    4
	HOOK4,    // 柱子4    5
	OBSTACLE_TYPE_COUNT //6
} obstacle_type;

void init(){
	IMAGE imgHook;
	for (int i = 0; i < 4; i++) {
		vector<IMAGE> imgHookArray;
		sprintf_s(name, "res/h%d.png", i + 1);
		loadimage(&imgHook, name,63,260,true);
		imgHookArray.push_back(imgHook);
		obstacleImgs.push_back(imgHookArray);
	}
}

// 创建障碍物
void creatObstacle() {
	int i;
	for (i = 0; i < OBSTACLE_COUNT; i++) {
		if (obstacles[i].exist == false){
			break;
		}
	}
	if (i >= OBSTACLE_COUNT) {
		return;
	}

	obstacles[i].exist = true;
	obstacles[i].imgindex = 0;
	obstacles[i].type = (obstacle_type)(rand() % OBSTACLE_TYPE_COUNT);
	obstacles[i].x = WIN_WIDTH;
	obstacles[i].y = 345 + 5 - obstacleImgs[obstacles[i].type][0].getheight();
	if (obstacles[i].type == TORTOISE) {
		obstacles[i].speed = 0;
		obstacles[i].power = 5;
	}
	else if (obstacles[i].type == LION){
		obstacles[i].speed = 4;
		obstacles[i].power = 20;
	}
	else if(obstacles[i].type >= HOOK1 && obstacles[i].type <= HOOK4){
		obstacles[i].speed = 0;
		obstacles[i].power = 20;
		obstacles[i].y = 0;
	}
	
}

DAY 5

完成了两个部分
  1. 优化障碍物出现频率

  2. 添加掉血功能

优化障碍物出现频率

原来的出现障碍物的代码是:

obstacles[i].type = (obstacle_type)(rand() % OBSTACLE_TYPE_COUNT);

因为柱子有 HOOK1 ~ HOOK5,上面这样写随机到柱子的概率很大,改成如下代码:

	obstacles[i].type = (obstacle_type)(rand() % 3);
	if (obstacles[i].type == HOOK1) {
		obstacles[i].type += rand() % 4;
	}

这样随机到柱子的可能性就小了很多

添加碰撞障碍掉血功能
//  碰撞检测
void checkHit() {
	for (int i = 0; i < OBSTACLE_COUNT; i++) {
		if (obstacles[i].exist && obstacles[i].hited == false) {
			int a1x, a1y, a2x, a2y;
			int off = 30;
			if (!heroDown) { // 不下蹲,就是在奔跑或跳跃
				a1x = heroX + off;
				a1y = heroY + off;
				a2x = heroX + imgHeros[heroIndex].getwidth() - off;
				a2y = heroY + imgHeros[heroIndex].getheight();
			}else{
				a1x = heroX + off;
				a1y = 345 - imgHeroDown[heroIndex].getheight();
				a2x = heroX + imgHeroDown[heroIndex].getwidth() - off;
				a2y = 345;
			}
			IMAGE img = obstacleImgs[obstacles[i].type][obstacles[i].imgindex];
			int b1x = obstacles[i].x + off;
			int b1y = obstacles[i].y + off;
			int b2x = obstacles[i].x + img.getwidth() - off;
			int b2y = obstacles[i].y + img.getheight() - 10;
			if (rectIntersect(a1x,a1y,a2x,a2y,b1x,b1y,b2x,b2y)) {
				heroBlood -= obstacles[i].power;
				printf("血量剩余 %d\n", heroBlood);
				playSound("res/hit.mp3");
				obstacles[i].hited = true;
			}
		}
	}
}

DAY 6 完结

完成了五个部分
  1. 添加血条,优化躲不掉的障碍

  2. 添加初始画面,判断游戏结束

  3. 通过障碍加分,并显示分数

  4. 判断游戏胜利

  5. 打包 结束

添加血条,解决躲不掉的障碍
// 调用接口 直接一行代码完成血条
void updateBloodBar() {
	drawBloodBar(10, 10, 200, 10, 2, BLUE, DARKGRAY, RED,heroBlood / 100.0 );
}

// 躲不掉的障碍是当出现柱子之后紧接着出来一个奔跑的狮子,根本躲不掉,定义一个上一个出现的障碍物
int lastObsIndex; // 上一个出现的障碍物,值init()中加载为-1

void creatObstacle() {
	if (lastObsIndex >= 0 &&
			obstacles[lastObsIndex].type >= HOOK1 &&
			obstacles[lastObsIndex].type <= HOOK4 &&
			obstacles[i].type == LION &&
			obstacles[lastObsIndex].x > (WIN_WIDTH - 500)) {
		obstacles[i].type = TORTOISE;
	}
	lastObsIndex = i;
}
添加初始画面,判断游戏结束
// main 函数中添加初始画面
int main(void){
    loadimage(0, "res/over.png");
    system("pause");
}
// 判断游戏结束
void checkOver(){
	if (heroBlood <= 0) {
		loadimage(0,"res/over.png");
		FlushBatchDraw();
		mciSendString("stop res/bg.mp3",0,0,0);
		system("pause");
		heroBlood = 100;
		score = 0;
		mciSendString("play res/bg.mp3 repeat", 0, 0, 0);
	}

}
通过障碍加分,并显示分数
// 检查分数:"障碍物存在"且"障碍物未被通过","未被撞击"且此时"玩家的x坐标已经大于障碍物右边的坐标" 则分数 + 1
void checkScore() {
	for (int i = 0; i < OBSTACLE_COUNT; i++) {
		if (obstacles[i].exist &&
				obstacles[i].passed == false &&
				obstacles[i].hited == false &&
				obstacles[i].x + obstacleImgs[obstacles[i].type][0].getwidth() < heroX) {
			score++;
			obstacles[i].passed = true;
			// printf("分数:%d\n",score);
		}
	}
}

// 显示分数
void updateScore() {
	// 50 => "50" 转换成字符串 然后切割取出来
	char str[8];
	sprintf(str, "%d", score);
	int x = 20;
	int y = 25;
	for (int i = 0; str[i]; i++) {
		int sz = str[i] - '0';
		putimagePNG(x, y, &imgSZ[sz]);
		x += imgSZ[sz].getwidth() + 5;
	}
}

判断游戏胜利
// 宏定义 结束所需要的分数
#define WIN_SCORE 66 // 66分结束游戏
// 检查胜利
void checkWin() {
	if (score >= WIN_SCORE) {
		mciSendString("play res/win.mp3",0,0,0);
		Sleep(2000);
		loadimage(0, "res/win.png");
		FlushBatchDraw();
		mciSendString("stop res/win.mp3",0,0,0);
		system("pause");
		heroBlood = 100;
		score = 0;
		mciSendString("play res/bg.mp3 repeat", 0, 0, 0);
	}
}

// 最后主要的main函数如下
int main(void) {
	init();
	// 显示初始画面
	loadimage(0, "res/over.png");
	system("pause");
	int timer = 0;
	while (true)
	{
		keyEvent();
		timer += getDelay();
		if (timer > 15) {
			timer = 0;
			update = true;
			}
		if (update) {
			update = false;
			BeginBatchDraw();
			updateBg();
			updateHero();
			updateEnemy();
			updateBloodBar();
			updateScore();		
			EndBatchDraw();
			checkWin();
			checkOver();
			checkScore();		
			flyBg();
		}
	}
	system("pause"); // 暂停
	return 0;
}
打包打包

在你的VisualStudio里下载这个插件 image.png

在解决方案中新建项目 image.png

选这个下一步,不要改变他的位置,只把项目名称改成你需要的名字 image.png

之后按照目录添加一下自己的素材

image.png

把上面的Debug换成Release image.png 之后右键这两个从上到下点击重新生成,两个项目都要点哦 image.png image.png

就OK了。

所有项目文件已经放在下面链接,需要请自取

点击获取所有文件

提取码:iott