天天酷跑游戏开发日志及源码
**
纯c语言开发的游戏项目,与天天酷跑玩法与贴图类似,日志中有着详细的开发过程,从零开始手把手带你,解决游戏开发的问题;体验开发的乐趣!!!
ps:源码在文章最下面,有详细的注释内容。
**
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 <stdio.h>
#include <graphics.h>
#include <conio.h>
#include <vector>
#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, //乌龟 0LION, //狮子 1HOOK1, //四个柱子障碍物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()返回 turech = _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'=5char 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),第一次调用时返回0if (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;
}