从B站学的,up是:程序员Rock
DAY 1
步骤一:使用visual Studio 2019,创建c++空项目
步骤二:导入素材
步骤三:游戏界面
天天酷跑是基于easyX图形库的
1)创建游戏窗口
2)实现游戏背景
3) 实现游戏背景的滚动
#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
完成了三个部分
-
添加人物在背景中
-
为人物添加跳跃动作
-
在背景中添加乌龟
添加人物在背景中
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
完成了三个部分
- 使用结构体封装障碍物
- 封装后障碍物的初始化
- 显示多个障碍物
使用结构体封装障碍物
| 障碍物 | 属性 |
|---|---|
| 乌龟 | 障碍物类型 |
| 狮子 | 图片的索引 |
| 柱子 | 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
完成了两个部分
- 实现玩家的下蹲功能
- 添加柱子这个东东
实现玩家的下蹲功能
总体来说,玩家的下蹲功能与玩家的跳跃功能相似 首先定义玩家的下蹲开关:
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
完成了两个部分
-
优化障碍物出现频率
-
添加掉血功能
优化障碍物出现频率
原来的出现障碍物的代码是:
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 完结
完成了五个部分
-
添加血条,优化躲不掉的障碍
-
添加初始画面,判断游戏结束
-
通过障碍加分,并显示分数
-
判断游戏胜利
-
打包 结束
添加血条,解决躲不掉的障碍
// 调用接口 直接一行代码完成血条
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里下载这个插件
在解决方案中新建项目
选这个下一步,不要改变他的位置,只把项目名称改成你需要的名字
之后按照目录添加一下自己的素材
把上面的Debug换成Release
之后右键这两个从上到下点击重新生成,两个项目都要点哦
就OK了。
所有项目文件已经放在下面链接,需要请自取
提取码:iott