【C语言】实现天天酷跑游戏

天天酷跑游戏开发日志及源码

**

纯c语言开发的游戏项目,与天天酷跑玩法与贴图类似,日志中有着详细的开发过程,从零开始手把手带你,解决游戏开发的问题;体验开发的乐趣!!!

ps:源码在文章最下面,有详细的注释内容。

**【C语言】实现天天酷跑游戏_第1张图片

1.创建项目

2.导入素材

3.创建游戏界面

基于C语言和Easyx图形库
1)创建游戏窗口
2)设计游戏背景
a)三重背景以不同的速度同时移动
b)循环滚动背景的实现
3)实现游戏背景
a)加载背景资源
b)打印背景图片(背景知识:坐标)

​ 遇到问题:背景图片的png格式图片出现黑色

​ 解决方案:导入tools.h和tools.cpp,包含到头文件并引用putimagePNG2

c)滚动效果加循环fly

​ 遇到问题:背景闪烁抖动(不断打印与渲染)

​ 解决方案:双缓冲机制(一次性准备好后一起渲染)

​ BeginBatchDraw();EndBatchDraw();

4.实现角色奔跑效果

1)加载角色奔跑的图片素材
2)设置角色的初始位置
a) 定义并取值角色X坐标,Y坐标,图片帧序号
3)实现角色跑动效果
a) 图片帧序号数组+1取余实现

​ heroIndex = (heroIndex + 1) % 12;

5.实现角色跳跃效果

1)处理用户输入的按键
a)如果有按键按下,kbhit()返回 ture
b)_getch不需要按回车即可直接读取,如果是 ‘ ’ 引用jump
2)实现跳跃
a)定义跳跃开关:跳跃Y坐标最大高度,偏移量(值为负)
b)打开跳跃开关

​ 1> 角色Y坐标 > 最大高度,则向上偏移

​ 2> 角色Y坐标 < 最大高度,将偏移量改为正,则向下偏移

c)关闭跳跃开关:当角色Y坐标 > 角色自身高度(最小高度),偏移量还原默认值
3)优化帧等待效果
a)sleep缺点:会导致程序休眠失去响应,在30s内无法触发按键响应
b)getDelay优化:返回距离上一次调用间隔的时间,第一次调用时返回。在30s内可以触发按键响应
c)update开关:用来实时刷新画面

6.实现敌人和障碍物

1)实现小乌龟的随机出现
a)加载小乌龟素材
b)出现小乌龟:(每3s刷新一个小乌龟)同时保证窗口只存在一个乌龟
c)渲染小乌龟

​ 1> 定义并取值小乌龟X坐标,Y坐标,图片帧序号

​ 2> 图片帧序号数组+1取余实现小乌龟旋转效果

​ 3>实现小乌龟水平移动,当小乌龟移出窗口后消失

​ 4>定义小乌龟的出现频率,使小乌龟随2s~5s随机出现、

2)使用结构体封装障碍物
a)定义结构体obstacle_t
b)用枚举存放障碍物类型,并定义为obstacle_type
c)使用c++中vector定义动态数组用来存储各障碍物图片(相当于c中:IMAGE obstacleImgs【3】【12】)
3)封装后障碍物的初始化
a)用结构体加载小乌龟素材
b)用结构体加载狮子素材
C)定义并初始化障碍物池
4)显示多个障碍物
a)出现障碍物:每隔一段时间刷新一个障碍物
b)创建障碍物的各个参数

​ 1>定义exist,imgIndex,type,x,y

​ 2>乌龟的数值,狮子的数值

c)更新所有障碍物的坐标
d)实现障碍物动态效果

7.实现玩家下蹲技能

1)加载下蹲素材
2)下蹲按键实现
3)定义下蹲开关和更新下蹲
a)用updateHero封装更新跳跃和下蹲
b)如果不下蹲则实现跳跃,否则实现下蹲
c)在滚动效果中实现下蹲

​ 遇到问题:下蹲速度太快

​ 解决方案:使用count增加帧数让图片变慢,增加delay延时

8.添加柱子障碍物

1)加载柱子障碍物素材
2)创建柱子障碍物的各个参数
3)优化障碍物的出现频率
a)对3取余降低柱子出现概率

9.实现碰撞检测

1)计算角色和障碍物的矩形大小(在tool.h中)
2)角色的矩形计算
a)奔跑与跳跃状态(通过偏移量来计算)
b)下蹲状态
2)障碍物的矩形计算
3)检测玩家与障碍物是否相交
a)相交:玩家掉血+碰撞声音

​ 遇到问题:角色碰撞后持续掉血

​ 解决方案:新定义hited,控制其开关来解决持续掉血

10.解决音效BUG–优化下蹲–增加血条

1)预加载音效,解决音效BUG
2)优化下蹲
3)使用封装好的血条

11.实现游戏结束,增加初始界面

1)播放背景音乐
2)判断游戏结束

a)血量<= 0,加载结束图片,0表示加载到界面;关闭背景音乐;游戏暂停

b)暂停以后,直接开始下一局;血量回复;播放背景音乐

3)增加初始界面

12.解决死亡障碍(柱子与狮子同时出现)

1)记录上一次障碍物,如果上一次障碍物时柱子&&下一次出现狮子&&上一个障碍物没走远
则将下一次障碍物替换成乌龟

13.通过障碍物后自动加分机制

1)比较x坐标:障碍物x坐标 + 障碍物第一章图片的宽度 < 玩家坐标
2)障碍物通过玩家加一分

14.实现界面界面显示分数

1)加载得分数字图片
2)显示分数
a)实现方法:50 =>‘50’ ‘5’ ‘5’-‘0’=5

15.判断游戏胜利

1)当分数 > 100分,播放胜利音效及图片
2)回复玩家血条,清零分数
3)注意:胜利前最后一分不显示就胜利,用”手动“刷新解决

注意:错误 C4996 'sprintf’要改为sprintf_s

右键文件名->属性->C/C++中常规->SDL检查关闭

源码如下:

#include 
#include 
#include 
#include 
#include "tools.h"

using namespace std;	//声明命名空间

#define WIN_SCORE  3

#define WIN_WIDTH 1012
#define WIN_HEIGHT 396	//宏定义,便于维护和更改
#define OBSTACLE_COUNT 10	//宏定义障碍物池数量

IMAGE imgBgs[3];	//(全局数组)存放背景图片
int bgX[3];		//背景图片的x坐标
int bgSpeed[3] = {2, 3, 5};		//三张图不同的速度

IMAGE imgHeros[12];		//存放角色奔跑图片
int heroX;	//角色的x坐标
int heroY;	//角色的y坐标
int heroIndex;	//角色奔跑的图片帧序号

bool heroJump;	//表示角色正在跳跃开关
int jumpHeighetMax;	//跳跃的最大值
int heroJumpOff;	//表示偏移量
int update;	//表示是否马上刷新画面

//	IMAGE imgTortoise[7];	//小乌龟
//	int torToiseX;	//小乌龟的水平坐标
//	int torToiseY;	//小乌龟的垂直坐标
//	bool torToiseExist;	//当前窗口是否存在小乌龟
//	int tortoiseIndex;//小乌龟旋转的图片帧序号

int heroBlood;	//玩家血量
int score;	//得分

//枚举封装障碍物的类型
typedef enum {
	TORTOISE,	//乌龟 0
	LION,	//狮子 1
	HOOK1,	//四个柱子障碍物
	HOOK2,
	HOOK3,
	HOOK4,
	OBSTACLE_TYPE_CONST // 6 便于查看枚举数
}obstacle_type; 

vector<vector<IMAGE>>obstacleImgs;//c++存放所用障碍物的各个图片
//相当于c中:IMAGE obstacleImgs[3][12]

//结构体封装障碍物
typedef struct obstacle {
	int type;	//障碍物的类型
	int imgIndex;	//当前显示图片的序号
	int x, y;	//障碍物坐标
	int speed;	//障碍物速度
	int power;	//障碍物杀伤力
	bool exist;	//障碍物存在
	bool hited;	//表示是否已经发生碰撞
	bool passed;//表示是否被通过

}obstacle_t;

obstacle_t obstacles[OBSTACLE_COUNT];	//定义障碍物池
int lastObsIndex;	//记录上一次障碍物

IMAGE imgHeroDown[2];	//角色下蹲
bool heroDown;	//表示玩家正在下蹲开关

IMAGE imgSZ[10];//得分数字图片

//游戏初始化
void init()	

{
	initgraph(WIN_WIDTH, WIN_HEIGHT, EW_SHOWCONSOLE);	//创建游戏窗口

	//加载背景资源
	char name[64];
	for (int i = 0; i < 3; i++) {
		//"res/bg001.png"   "res/bg002.png"   "res/bg003.png"   
		sprintf(name, "res/bg%03d.png", i + 1);
		loadimage(&imgBgs[i],name);	
		bgX[i] = 0;
	}

	//加载Hero奔跑的图片素材
	for (int i = 0; i < 12; i++) {
		//	"res/hero1.png"	...	"res/hero12.png"
		sprintf(name, "res/hero%d.png", i+1);
		loadimage(&imgHeros[i], name);
	}

	//设置角色的初始位置
	heroX = WIN_WIDTH * 0.5 - imgHeros[0].getwidth() * 0.5;
	heroY = 355 - imgHeros[0].getheight();
	heroIndex = 0;
	
	heroJump = false;
	jumpHeighetMax = 355 - imgHeros[0].getheight() - 120;
	heroJumpOff = -5;

	update = true;

	//	6.1 加载小乌龟素材
	//	for (int i = 0; i < 7; i++) {
	//		//	"res/t1.png" ... "res/t7.png"
	//		sprintf(name, "res/t%d.png", i + 1);
	//		loadimage(&imgTortoise[i], name);
	//	}
	//	torToiseExist = false;
	//	torToiseY = 355 - imgTortoise[0].getheight();

	//用结构体加载小乌龟素材
	IMAGE imgTort;
	vector<IMAGE> imgTortArray;
	for (int i = 0; i < 7; i++) {
		//	"res/t1.png" ... "res/t7.png"
		sprintf(name, "res/t%d.png", i + 1);
		loadimage(&imgTort, name);
	imgTortArray.push_back(imgTort);	//存放到一维
	}
	obstacleImgs.push_back(imgTortArray);	//存放到二维

	//用结构体加载狮子素材
	IMAGE imgLion;
	vector<IMAGE> imgLionArray;
	for (int i = 0; i < 6; i++) {
		//	"res/p1.png" ... "res/p6.png"
		sprintf(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;
	}

	//加载下蹲素材
	loadimage(&imgHeroDown[0], "res/d1.png");
	loadimage(&imgHeroDown[1], "res/d2.png");
	heroDown = false;

	//加载柱子素材
	IMAGE imgH;
	for (int i = 0; i < 4; i++) {
		vector<IMAGE> imgHookArray;
		sprintf(name,"res/h%d.png",i+1);
		loadimage(&imgH,name, 63, 260, true);
		imgHookArray.push_back(imgH);
		obstacleImgs.push_back(imgHookArray);
	}

	//玩家血量
	heroBlood = 100;

	//预加载音效
	preLoadSound("res/hit.mp3");

	//播放背景音乐
	mciSendString("play res/bg.mp3 repeat", 0, 0, 0);

	lastObsIndex = -1;
	score = 0;

	//加载得分数字图片
	for (int i = 0; i < 10; i++) {
		sprintf(name, "res/sz/%d.png", i);
		loadimage(&imgSZ[i],name);
	}
}

//创建障碍物的各个参数
void createObstacle() {
	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].hited = false;
	obstacles[i].imgIndex = 0;
	//obstacles[i].type = (obstacle_type)(rand() % OBSTACLE_TYPE_CONST);	//使障碍物随机出现
	obstacles[i].type = (obstacle_type)(rand() % 3);	//对3取余降低柱子出现概率

	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;

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

	obstacles[i].x = WIN_WIDTH;	
	obstacles[i].y = 355 - obstacleImgs[obstacles[i].type][0].getheight();
	//乌龟的数值
	if (obstacles[i].type == TORTOISE) {
		obstacles[i].speed = 1;	//乌龟速度值
		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;
	}
	obstacles[i].passed = false;
}

//碰撞检测
void checkHit() {
	for (int i = 0; i < OBSTACLE_COUNT; i++) {
		if (obstacles[i].exist && obstacles[i].hited == false) {
			int a1x, a1y, a2x, a2y;		//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 = 355 - imgHeroDown[heroIndex].getheight();
				a2x = heroX + imgHeroDown[heroIndex].getwidth() - off;
				a2y = 355;
			}

			//障碍物碰撞检测
			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;
			}
		}
	}
}

//滚动效果
void go()
{
	for (int i = 0; i < 3; i++) {
		bgX[i] -= bgSpeed[i];
		if (bgX[i] < -WIN_WIDTH) {
			bgX[i] = 0;		//循环实现
		}
	}

	//实现跳跃
	if (heroJump) {
		if (heroY < jumpHeighetMax) {
			heroJumpOff = 6;
		}

		heroY += heroJumpOff;

		if (heroY > 355 - imgHeros[0].getheight()) {
			heroJump = false;
			heroJumpOff = -6;
		}
	}
	else if (heroDown) {	//实现下蹲
		static int count = 0;
		int delays[2] = {6, 30};
		count++;
		if (count >= delays[heroIndex]) {
			count = 0;
			heroIndex++;
			if (heroIndex >= 2) {
				heroIndex = 0;
				heroDown = false;
			}
		}
	}
	else{	//不跳跃
		heroIndex = (heroIndex + 1) % 12;	//实现角色跑动效果
	}

	//出现障碍物
	static int frameCount = 0;
	static int enemyFre = 50;	//小乌龟出现频率
	frameCount++;
	if (frameCount > enemyFre) {		//25ms刷新一次,刷新一个障碍物
		frameCount = 0;
		enemyFre = 50 + rand() % 50;	//障碍物在50 ~ 99随机出现
		createObstacle();
	}
	//更新所有障碍物的坐标
	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;
		}
	}

	//	6.1 出现小乌龟
		//if (!torToiseExist) {
		//	torToiseExist = true;
		//	torToiseX = WIN_WIDTH;	//小乌龟从窗口宽度边缘出现
		//	enemyFre = 200 + rand() % 300;	//小乌龟在200 ~ 500(2s~5s)随机出现
		//}
	//	6.1 实现小乌龟水平移动
	//	if (torToiseExist) {
	//	tortoiseIndex = (tortoiseIndex + 1) % 7;	//实现乌龟旋转效果
	//	torToiseX -= bgSpeed[2];
	//	if (torToiseX < -imgTortoise[0].getwidth()) {	//当小乌龟移出窗口后消失
	//		torToiseExist = false;
	//	}
	//}

	//玩家和障碍物的“碰撞检测”处理
	checkHit();

}

//渲染游戏背景
void updateBg() 
{
	putimagePNG2(bgX[0], 0, &imgBgs[0]);
	putimagePNG2(bgX[1], 119, &imgBgs[1]);
	putimagePNG2(bgX[2], 330, &imgBgs[2]);
} 

void jump() {
	heroJump = true;	//打开跳跃开关
	update = true;
}

void down() {
	heroDown = true;	//打开下蹲开关
	update = true;
	heroIndex = 0;
}

//处理用户输入的按键
void keyEvent() 
{
	char ch;

	if (_kbhit()) {		//如果有按键按下,kbhit()返回 ture
		ch = _getch();		//_getch不需要按回车即可直接读取
		if (ch == ' ') {
			jump();
		}
		else if (ch == 'z') {	//按z下蹲
			down();
		}
	}
}

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]);
		}
	}
}
	//	6.1 渲染小乌龟
	//	if (torToiseExist) {
	//		putimagePNG2(torToiseX, torToiseY, WIN_WIDTH, &imgTortoise[tortoiseIndex]);
	//	}

//更新跳跃和下蹲
void updateHero() {
	if (!heroDown) {
		putimagePNG2(heroX, heroY, &imgHeros[heroIndex]);
	}
	else {
		int y = 355 - imgHeroDown[heroIndex].getheight();
		putimagePNG2(heroX, y, &imgHeroDown[heroIndex]);
	}


}

//添加血条
void updateBloodBar() {
	drawBloodBar(10, 10, 200, 10, 2, BLUE, DARKGRAY, RED, heroBlood / 100.0);
}

//判断游戏结束
void checkOver() {
	if (heroBlood <= 0) {
		loadimage(0,"res/over.png");	//加载结束图片,0表示加载到界面
		FlushBatchDraw();
		mciSendString("stop res/bg.mp3", 0, 0, 0);	//	关闭背景音乐
		system("pause");	//游戏暂停

		//暂停以后,直接开始下一局
		heroBlood = 100;
		score = 0;
		mciSendString("play res/bg.mp3 repeat", 0, 0, 0);
	}
}

//添加初始界面
void checkStart() {
	loadimage(0, "res/over.png");
	system("pause");
}

//越过障碍物自动得分机制
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("score: %d\n", score);
		}
	}
}

//显示分数
void updateScore() {
	//实现方法:50 =>'50' '5'   '5'-'0'=5
	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;
	}
}

//检测胜利
void checkWin() {
	if (score >= WIN_SCORE) {
		FlushBatchDraw();	//手动刷新胜利前最后一分
		mciSendString("play res/win.mp3", 0, 0, 0);
		Sleep(2000);
		loadimage(0,"res/win.png");
		FlushBatchDraw();	//刷新界面	
		mciSendString("stop res/bg.mp3", 0, 0, 0);
		system("pause");

		heroBlood = 100;
		score = 0;
		mciSendString("play res/bg.mp3", 0, 0, 0);
	}
}

int main(void) 
{
	init();
	checkStart();

	int timer = 0;
	while (1) {
		keyEvent();
		timer += getDelay();	//返回距离上一次调用间隔的时间(单位:ms),第一次调用时返回0
		if (timer > 25) {		//实现sleep25ms循环效果
			timer = 0;
			update = true;
		}

		if (update) {	//如果没到30ms触发按键,直接刷新
			update = false;
			BeginBatchDraw();	//双缓冲机制(防背景闪屏)
			updateBg();
			//putimagePNG2(heroX, heroY, &imgHeros[heroIndex]);
			updateHero();
			updateEnemy();
			updateBloodBar();
			updateScore();
			checkWin();
			EndBatchDraw();

			checkOver();
			checkScore();

			go();
		}
		//Sleep(25);	//导致休眠

	}


	system("pause");

	return 0;
}

实机演示:

【C语言】实现天天酷跑游戏_第2张图片

【C语言】实现天天酷跑游戏_第3张图片

你可能感兴趣的:(c,c语言,游戏)