C语言手写天天酷跑代码详解
- 项目总览:
- 一、项目开发日志
- 二、引入库与宏编译
- 三、全局变量与结构体的定义
- 四、主函数中的内容
- 五、逐个自定义函数拆解分析
- 1.初始化init()函数
- 2.处理用户按键输入keyEvent() 函数
- 3.渲染游戏背景 updateBg()函数
- 4.渲染下蹲图片 updateHero()函数
- 5.渲染障碍物图片 updateEnemy()函数
- 6.渲染血条 updateBloodBar()函数
- 7.渲染分数图片显示 updateScore()函数
- 8.检查游戏是否胜利 checkWin()函数
- 9.检查游戏是否结束(死亡) checkOver()函数
- 10.计算得分 checkScore()函数
- 11.实现跳跃,下蹲,障碍物的移动等动态效果fly() 函数
- 六、效果展示:
- 1.视频效果:[点击链接直达](https://www.bilibili.com/video/BV1Vr4y1B7bn?spm_id_from=333.999.0.0)
- 2.图片效果:
项目总览:
1.开发语言:C语言加上一点点C++的函数
2.IDE:VS2022(点击跳转到配置环境教程) 3.开发用时:3天左右
4.代码所需要使用的第三方函数与所有图形素材及本项目源码文件均已经上传到我的GitHub,链接:https://github.com/2394799692/Cool-running-every-day1.0 或点此跳转
以下是本篇文章正文内容,欢迎朋友们进行指正,一起探讨,共同进步。——来自考研路上的lwj。QQ:2394799692
一、项目开发日志
/*
* 天天酷跑开发日志
* 1.创建项目
* 2.导入素材
* 3.实际的开发流程:从用户界面入手
* 1)创建游戏窗口
* 2)实现游戏背景(基于“easyx”图形库)
* a:三重背景不同的速度
* b:循环滚动背景的实现
* 3)实现游戏背景
* a.加载背景资源
* b,渲染(坐标)
* 遇到问题:背景图片的png格式图片出现黑色
* 解决办法:百度引入第三方头文件中的putimagePNG2函数解决
* 4.实现玩家的奔跑
* 5.实现玩家的跳跃(空格)
* 6.实现随机小乌龟
* 7.创建障碍物结构体数据类型
* 8.使用障碍物结构体后重新初始化
* 9.封装后多个障碍物的显示
* 10.实现玩家下蹲技能
* 11.实现“柱子”障碍物
* 12.实现血条与计分
* 13.判断游戏胜利
*/
二、引入库与宏编译
#define _CRT_SECURE_NO_WARNINGS//使用scanf函数防止报错
#define WIN_SCORE 20//用于定义游戏获胜的分数条件
#define WIDTH 1012//游戏背景的宽度
#define HEIGHT 396//高度
#define OBSTACLE_COUNT 10//障碍物数量
#include<stdio.h>//标准输入输出库函数
#include<graphics.h>//引入图形库
#include<conio.h>//按键输入库
#include"tools.h"//添加本地头文件
#include<vector>//引入c++库用于使用容器变长数组
三、全局变量与结构体的定义
using namespace std;//声明命名空间
//游戏初始化
IMAGE imgBgs[3];//全局变量,存放三重背景
int bgX[3];//背景图片的x坐标
int bgSpeed[3] = { 1,2,4 };//背景移动的速度
IMAGE imgHeros[12];//奔跑
int heroX;//玩家的x坐标
int heroY;
int heroIndex;//玩家奔跑的图片帧序号
bool heroJump;//玩家跳跃状态
int jumpHeightMax;//设置跳跃最大高度
int heroJumoOff;//设置偏移量
int update;//表示是否需要马上刷新
//IMAGE imgTortoise;//小乌龟
//int torToiseX;//小乌龟的水平坐标
//int torToiseY;
//bool torToiseXExist;//当前窗口是否有小乌龟
int heroBlood;//玩家血量
int score;//分数
typedef enum {//定义障碍物类型结构体
TORTOISE,
LION,
HOOK1,
HOOK2,
HOOK3,
HOOK4,
OBSTACLE_TYPE_COUNT
}obstacle_type;
vector<vector<IMAGE>>obstacleImgs;//相当于IMAGE obstacleImgs[][]
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];
四、主函数中的内容
int main(void) {
init();//初始化函数
loadimage(0, "res/over.png");//初始画面
system("pause");//调用DOS系统的暂停命令 pause 来暂停程序执行,按任意一个键后将继续执行。
int timer = 0;
while (1) {
keyEvent();//用于接收空格和tab键来去执行对应的跳跃/下蹲函数
timer+=getDelay();//第三方封装函数用于返回距离上次间隔调用的时间
if (timer > 30) {//间隔30帧刷新一次页面
timer = 0;
update = true;
}
if (update) {//刷新页面
update = false;
BeginBatchDraw();//去除闪烁
updateBg();//渲染游戏背景
//putimagePNG2(heroX, heroY, &imgHeros[heroIndex]);
updateHero();//渲染下蹲图片
updateEnemy();//渲染障碍物
updateBloodBar();//调用第三方接口渲染血条
updateScore();//渲染分数图片输出
checkWin();//检查游戏是否胜利
EndBatchDraw();//去除闪烁
checkOver();//检查游戏是否结束
checkScore();//计算得分
fly();//实现跳跃,下蹲,障碍物的移动等动态效果
}
}
system("pause");//调用DOS系统的暂停命令 pause 来暂停程序执行,按任意一个键后将继续执行。
return 0;
}
五、逐个自定义函数拆解分析
1.初始化init()函数
void init() {
//创建游戏窗口
initgraph(WIDTH, HEIGHT,EW_SHOWCONSOLE);//调用“easyx”图形库中的函数实现页面大小
char name[64];
//加载游戏背景资源
for (int i = 0; i < 3; i++) {
sprintf(name, "res/bg%03d.png", i + 1);//宽3位不够前面补充0
loadimage(&imgBgs[i], name);//载入游戏背景
bgX[i] = 0;//背景图片的x坐标设为0
heroJump = false;//跳跃状态初始化为否
}
//加载玩家Hero奔跑的图片帧素材
for (int i = 0; i < 12; i++) {
sprintf(name, "res/hero%d.png", i + 1);
loadimage(&imgHeros[i], name);
}
//设置玩家的初始位置
heroX = WIDTH * 0.5 - imgHeros[0].getwidth() * 0.5;
heroY = 345 - imgHeros[0].getheight();
heroIndex = 0;
heroJump = false;
jumpHeightMax = 345 - imgHeros[0].getheight() - 120;
heroJumoOff = -4;
update = true;
//加载小乌龟
/*loadimage(&imgTortoise, "res/t1.png");
torToiseXExist = false;//小乌龟初始化
torToiseY = 345 - imgTortoise.getheight()+5;*/
//用结构体加载小乌龟
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(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,60,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;//起始分数为0
//加载数字得分图片
for (int i = 0; i < 10; i++) {
sprintf(name, "res/sz/%d.png", i);
loadimage(&imgSZ[i], name);
}
}
2.处理用户按键输入keyEvent() 函数
//处理用户按键输入
void jump() {
heroJump = true;
update = true;
}
void down() {
update = true;
heroDown = true;
heroIndex = 0;
}
void keyEvent() {
char ch;
if (_kbhit()) {//如果有按键按下,返回true
ch = getch();//不需要按回车即可读取
if (ch == ' ') {//如果按下空格执行跳跃函数
jump();
}
else if (ch == ' ') {//如果按下tab键执行下蹲函数
down();
}
}
}
3.渲染游戏背景 updateBg()函数
void updateBg() {
putimagePNG2(bgX[0],0,&imgBgs[0]);
putimagePNG2(bgX[1], 119, &imgBgs[1]);
putimagePNG2(bgX[2], 330, &imgBgs[2]);
}
4.渲染下蹲图片 updateHero()函数
void updateHero() {
if (!heroDown) {//下蹲图片渲染
putimagePNG2(heroX, heroY, &imgHeros[heroIndex]);
}
else {
int y = 345 - imgHeroDown[heroIndex].getheight();
putimagePNG2(heroX, y, &imgHeroDown[heroIndex]);
}
}
5.渲染障碍物图片 updateEnemy()函数
void updateEnemy() {
//渲染小乌龟
/*if (torToiseXExist) {
putimagePNG2(torToiseX, torToiseY,WIDTH,&imgTortoise);
}*/
for (int i = 0; i < OBSTACLE_COUNT; i++) {
if (obstacles[i].exist) {
putimagePNG2(obstacles[i].x, obstacles[i].y, WIDTH, &obstacleImgs[obstacles[i].type][obstacles[i].imgIndex]);
}
}
}
6.渲染血条 updateBloodBar()函数
void updateBloodBar() {//调用第三方接口
drawBloodBar(10, 10, 200, 10, 2, BLUE, DARKGRAY, RED, heroBlood / 100.0);
}
7.渲染分数图片显示 updateScore()函数
void updateScore() {
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;
}
}
8.检查游戏是否胜利 checkWin()函数
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 repeat", 0, 0, 0);
}
}
9.检查游戏是否结束(死亡) checkOver()函数
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);
}
}
10.计算得分 checkScore()函数
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);
}
}
}
11.实现跳跃,下蹲,障碍物的移动等动态效果fly() 函数
void fly() {
for (int i = 0; i < 3; i++) {
bgX[i] -=bgSpeed[i];
if (bgX[i] < -WIDTH) {
bgX[i] = 0;
}
}
//实现跳跃
if (heroJump) {
if (heroY < jumpHeightMax) {//防止飞天
heroJumoOff = 4;
}
heroY += heroJumoOff;//上升
if (heroY > 345 - imgHeros[0].getheight()) {//防止遁地
heroJump = false;
heroJumoOff = -4;
}
}
else if (heroDown) {
static int count = 0;
int delays[2] = { 8,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) {
frameCount = 0;
enemyFre =50 + rand() % 50;//50-99随机数
createObstacle();
}
//if (torToiseXExist) {
// //设置小乌龟的移动
// torToiseX -= bgSpeed[2];
// if (torToiseX < -imgTortoise.getwidth()) {
// torToiseXExist = false;
// }
//}
//更新所有障碍物的坐标
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) {//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;
}
}
//玩家和障碍物的碰撞检测
checkHit();
}
六、效果展示:
1.视频效果:点击链接直达
2.图片效果:
1)开始页面:
2)进入游戏页面:
3)跳跃页面:
4)下蹲页面:
5)游戏结束页面: