前言:全文代码是模仿程序员rock开发的游戏代码思路,如果大家想根据视频一步步进行制作可以直接去b站进行搜索。全文代码主要分为两个部分,一个是完整版的开发代码(博主根据视频进行一步一步制作而成,素材也均来自于程序员rock);另一个是博主考虑到自身水平的原因,在源码的基础上进行弱化修改,并且博主会在后文提到可以修改的地方,并且其中的素材是由博主ps手动扣出来的,会在后面的文章中详细说到。
目录
完整版
一.库与宏编译以及一些变量的设置
二.init()函数作为游戏初始化
三.creatobstacle()函数创建障碍物
四.checkhit()函数检测玩家碰撞
五. fly()函数实现图片的数据更新
六.updata()函数渲染游戏背景
七.updataren()函数进行人物图像渲染
八.人物跳跃开关
九.人物下蹲开关
十. anjian()函数处理用户按键读入
十一.updateEnemy()函数处理小怪渲染
十二. updatablood()函数进行血条渲染
十三.updatascore()函数处理分数渲染
十四.checkover()函数进行结束判定
十五.checkscore()函数进行分数更新
十六.checkwin()函数检测胜利
十七.drawblood()函数用来画血条
十八.main()主程序
完整程序
效果展示:
弱化版
详细讲解
接下来进入正文
完整版
一.库与宏编译以及一些变量的设置
#include<stdio.h>
#include<graphics.h>
#include<conio.h>
#include"tools.h"
#include<vector> using namespace std;#define WHIDE 1012
#define HEIGHT 396
#define OBSTACLE_COUNT 10
#define win 20 //胜利分数
//三重循环
IMAGE imgbgs[3];
int bgx[3];//背景图片的x坐标
int bgspeed[3] = { 1,2,4 };IMAGE imgbgsren[12];
int renx;//人物横坐标
int reny;//人物纵坐标
int renindex;//玩家奔跑的图片帧
int jumpmax;//人物跳跃最大高度
bool renjump;//判断人物有没有跳
int rendownspeed;//下降
bool updata;//图片刷新开关
int blood;//人物血量
int score;//分数//IMAGE imgTortoise;//小怪1
//int torToiseX;//小怪1的x坐标
//int torToiseY;
//bool torToiseXExist;//当前窗口是否有小怪1typedef enum { //枚举类型guai1, guai2, zhu1, zhu2, zhu3, zhu4, //怪1为0,怪2为1sum //sum来表示一共有多少种怪
}obstacle_type;vector<vector<IMAGE>>obstacleimgs;//定义可变二维数组,行表示哪一种障碍物,列表示障碍物图片typedef struct obstacle {//障碍物封装obstacle_type type;//障碍物类型int imgindex;//当前图片序号int x, y; //障碍物的x,y坐标int speed;//速度int power;//障碍物杀伤力(俗称扣多少血)bool exist;//判断障碍物是否存在bool hit;//判断是否碰撞bool passed;//表示是否通过
}obstacle_t;obstacle_t obstacles[OBSTACLE_COUNT];//直接建一个池子,看有多少障碍物
int lastobindex; //上一个障碍物的序号//人物下蹲素材
IMAGE imgrendown[2];
bool rendown;//人物下蹲判定//数字素材
IMAGE imgsz[10];
二.init()函数作为游戏初始化
//游戏初始化
void init() {//创建游戏窗口initgraph(WHIDE, HEIGHT, EX_SHOWCONSOLE);//显示控制台//加载背景资源char name[64];for (int i = 0; i < 3; i++) {sprintf(name, "res/bg%03d.png", i + 1);loadimage(&imgbgs[i], name);}//加载角色帧图片(奔跑)for (int i = 0; i < 12; i++) {sprintf(name, "res/hero%d.png", i + 1);loadimage(&imgbgsren[i], name);}//设置玩家初始位置,之后应该不会变renx = WHIDE / 2 - imgbgsren[0].getwidth() / 2;reny = 345 - imgbgsren[0].getheight();renindex = 0;renjump = false;//人物开始是不跳jumpmax = reny - 120;rendownspeed = -4;//下降速度blood = 100;//加载小怪1//loadimage(&imgTortoise, "res/t2.png");//torToiseXExist = false;//小怪1初始化//torToiseY = 345 - imgTortoise.getheight()+5;IMAGE imgguai1;loadimage(&imgguai1, "res/t2.png");vector<IMAGE>boxguai1;//容器也就是数组boxguai1.push_back(imgguai1);//然后接下来把一维数组放到二维数组里去obstacleimgs.push_back(boxguai1);//接下来加载小怪2IMAGE imagguai2;vector<IMAGE> boxguai2;for (int i = 0; i < 6; i++) {sprintf(name, "res/p%d.png", i + 1);loadimage(&imagguai2, name);boxguai2.push_back(imagguai2);}obstacleimgs.push_back(boxguai2);//初始化障碍物池子for (int i = 0; i < OBSTACLE_COUNT; i++) {obstacles[i].exist = false;}//加载下蹲素材loadimage(&imgrendown[0], "res/d1.png");loadimage(&imgrendown[1], "res/d2.png");rendown = false;//障碍物(柱子动画)IMAGE imgH;for (int i = 0; i < 4; i++) {vector<IMAGE>imgzhu;sprintf(name, "res/h%d.png", i + 1);loadimage(&imgH, name, 63, 260, true); //记载的时候进行缩放imgzhu.push_back(imgH);obstacleimgs.push_back(imgzhu);}//预加载音效preLoadSound("res/hit.mp3"); //这玩意就是外部接口mciSendString("play res/bg.mp3 repeat", 0, 0, 0); //背景音乐lastobindex = -1;score = 0;//加载数字图片for (int i = 0; i < 10; i++) {sprintf(name, "res/sz/%d.png", i);loadimage(&imgsz[i], name);}
}
三.creatobstacle()函数创建障碍物
void creatobstacle() {int i;for (i = 0; i < OBSTACLE_COUNT; i++) {if (!obstacles[i].exist) {break;}}if (i >= OBSTACLE_COUNT) {return;}obstacles[i].exist = true;obstacles[i].hit = false;obstacles[i].imgindex = 0;obstacles[i].type = (obstacle_type)(rand() % 3);//强行转化if (lastobindex >= 0 &&obstacles[lastobindex].type >= zhu1 &&obstacles[lastobindex].type <= zhu4 &&obstacles[i].type == guai2 &&obstacles[lastobindex].x > (WHIDE - 500)) {obstacles[i].type = guai1;}lastobindex = i;if (obstacles[i].type == zhu1) { //优化障碍物的出现obstacles[i].type = (obstacle_type)(zhu1 + rand() % 4);}obstacles[i].x = WHIDE;obstacles[i].y = 350 - obstacleimgs[obstacles[i].type][0].getheight();if (obstacles[i].type == guai1) {obstacles[i].speed = 0;obstacles[i].power = 5; //小怪1的伤害}else if (obstacles[i].type == guai2) {obstacles[i].speed = 4;obstacles[i].power = 20;}else if (obstacles[i].type >= zhu1 && obstacles[i].type <= zhu4) {obstacles[i].speed = 0;obstacles[i].power = 20;obstacles[i].y = 0;}obstacles[i].passed = false;
}
四.checkhit()函数检测玩家碰撞
//玩家碰撞检测
void checkhit() {for (int i = 0; i < OBSTACLE_COUNT; i++) { //矩形的话只需要知道两个对角即可确定if (obstacles[i].exist && obstacles[i].hit == false) { //利用开源代码,检测两个矩形是否相交int alx, aly, a2x, a2y;int off = 30;if (!rendown) { //如果人没有蹲,就是跑或者跳alx = renx + off;aly = reny + off;a2x = renx + imgbgsren[renindex].getwidth() - off;a2y = reny + imgbgsren[renindex].getheight();}else {alx = renx + off;aly = 345 - imgrendown[renindex].getheight();a2x = renx + imgrendown[renindex].getwidth() - off;a2y = 345;}IMAGE img = obstacleimgs[obstacles[i].type][obstacles[i].imgindex];//第几种障碍物的第几张图片int blx = obstacles[i].x + off;int bly = obstacles[i].y + off;int b2x = obstacles[i].x + img.getwidth() - off;int b2y = obstacles[i].y + img.getheight() - 10;if (rectIntersect(alx, aly, a2x, a2y, blx, bly, b2x, b2y)) {//发生碰撞blood -= obstacles[i].power;printf("血量剩余 %d\n", blood);playSound("res/hit.mp3");obstacles[i].hit = true;}}}
}
五. fly()函数实现图片的数据更新
//图片移动
void fly() {for (int i = 0; i < 3; i++) {bgx[i] -= bgspeed[i];if (bgx[i] < -WHIDE)bgx[i] = 0;}//人物跳跃实现if (renjump) { //卧槽改什么啊,y坐标啊,怎么什么都不会啊,你是宝贝吗if (reny < jumpmax) {//第一次是起跳所以开始负的,接下来一定是下降所以为正即可rendownspeed = 4;}reny += rendownspeed;if (reny > 345 - imgbgsren[0].getheight()) {//已经降到地面了renjump = false;rendownspeed = -4;}}else if (rendown) {static int count = 0;int delays[2] = { 4,32 }; //想要分别控制两张图片的站场时间count++;if (count >= delays[renindex]) { //站场时间不同count = 0;renindex++;if (renindex >= 2) {renindex = 0;rendown = false;}}}else {//不跳也不蹲renindex = (renindex + 1) % 12;}//创建障碍物static int frameCount = 0;//静态变量永久有效//static int torToiseFre = 100;static int enemyFre = 50;frameCount++;if (frameCount > enemyFre) {frameCount = 0;//if (!torToiseXExist) {//torToiseXExist = true;//torToiseX = WHIDE;//enemyFre = 200 + rand() % 301;//}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].getheight() * 2) {obstacles[i].exist = false;}int len = obstacleimgs[obstacles[i].type].size();obstacles[i].imgindex = (obstacles[i].imgindex + 1) % len;}}//if (torToiseXExist) {//设置小怪1的移动//torToiseX -= bgspeed[2];//if (torToiseX < -imgTortoise.getwidth()) {//torToiseXExist = false;//}//}checkhit();
}
上面的creatobstacle函数以及checkhit函数均在这里进行应用
六.updata()函数渲染游戏背景
//渲染游戏背景
void updatabg() {putimagePNG2(bgx[0], 0, &imgbgs[0]);putimagePNG2(bgx[1], 119, &imgbgs[1]);putimagePNG2(bgx[2], 330, &imgbgs[2]);
}
七.updataren()函数进行人物图像渲染
//人物图像渲染
void updataren() {if (!rendown) {putimagePNG2(renx, reny, &imgbgsren[renindex]);}else {int y = 345 - imgrendown[renindex].getheight();putimagePNG2(renx, y, &imgrendown[renindex]);}
}
八.人物跳跃开关
//人物跳跃开关
void jump() {renjump = true;updata = true; //人物帧等待(就是直接不等30ms了,直接刷新)
}
九.人物下蹲开关
//人物下蹲开关
void down() {rendown = true;updata = true;renindex = 0;
}
十. anjian()函数处理用户按键读入
//处理用户的按键输入
void anjian() {char ch;if (kbhit()) { //判断是否有输入ch = getch();//getch直接读取字符,没有缓冲区啦if (ch == ' ') {jump();}else if (ch == 'b') {down();}}}
十一.updateEnemy()函数处理小怪渲染
void updateEnemy() {//渲染小怪1//if (torToiseXExist) {// putimagePNG2(torToiseX, torToiseY, WHIDE, &imgTortoise);//}for (int i = 0; i < OBSTACLE_COUNT; i++) {if (obstacles[i].exist){putimagePNG2(obstacles[i].x, obstacles[i].y, WHIDE, &obstacleimgs[obstacles[i].type][obstacles[i].imgindex]);}}
}
十二. updatablood()函数进行血条渲染
//渲染血条
void updatablood() {drawblood(10, 10, 200, 10, 2, BLUE, DARKGRAY, RED, blood / 100.0);
}
十三.updatascore()函数处理分数渲染
void updatascore() {char str[3];sprintf(str, "%d", score);int x = 20;int y = 25;for (int i = 0; str[i] != '\0'; i++) {int sz = str[i] - '0';putimagePNG(x, y, &imgsz[sz]);x = x + imgsz[sz].getwidth() + 5;}
}
十四.checkover()函数进行结束判定
void checkover() {if (blood <= 0) {loadimage(0, "res/over.png");FlushBatchDraw();mciSendString("stop res/bg.mp3", 0, 0, 0);system("pause");//准备设置一个按钮,开始下一盘if (getch() == 'w') {blood = 100;score = 0;mciSendString("play res/bg.mp3 repeat", 0, 0, 0); //背景音乐}}
}
十五.checkscore()函数进行分数更新
void checkscore() {for (int i = 0; i < OBSTACLE_COUNT; i++) {if (obstacles[i].exist && obstacles[i].passed == false && obstacles[i].x + obstacleimgs[obstacles[i].type][0].getwidth() < renx && obstacles[i].hit == false) {score++;obstacles[i].passed = true;printf("score=%d\n", score);}}
}
十六.checkwin()函数检测胜利
void checkwin() {if (score >= win) {FlushBatchDraw();mciSendString("play res/win.mp3", 0, 0, 0);Sleep(2000);loadimage(0, "res/win.png");FlushBatchDraw();system("pause");if (getch() == 'w') { //重新开始mciSendString("stop res/win.mp3", 0, 0, 0);blood = 100;score = 0;("play res/bg.mp3 repeat", 0, 0, 0); //背景音乐}}
}
十七.drawblood()函数用来画血条
//画血条
void drawblood(int x, int y, int width, int height, int lineWidth, int boardColor, int emptyColor, int fillColor, float percent) {LINESTYLE lineStyle;getlinestyle(&lineStyle);int lineColor = getlinecolor();int fileColor = getfillcolor();if (percent < 0) {percent = 0;}setlinecolor(BLUE);setlinestyle(PS_SOLID | PS_ENDCAP_ROUND, lineWidth);setfillcolor(emptyColor);fillrectangle(x, y, x + width, y + height);setlinestyle(PS_SOLID | PS_ENDCAP_FLAT, 0);setfillcolor(fillColor);setlinecolor(fillColor);if (percent > 0) {fillrectangle(x + 0.5 * lineWidth, y + lineWidth * 0.5, x + width * percent, y + height - 0.5 * lineWidth);}setlinecolor(lineColor);setfillcolor(fillColor);setlinestyle(&lineStyle);
}
十八.main()主程序
int main() {init();int time = 0;loadimage(0, "res/over.png");if (getch() == ' ') {while (1) {anjian();time += getDelay();//背景帧等待if (time > 30) { //可以优化帧等待time = 0;updata = true;}if (updata) {updata = false;BeginBatchDraw();updatabg();//putimagePNG2(renx, reny, &imgbgsren[renindex]);updataren();updateEnemy();updatablood();updatascore();checkwin();EndBatchDraw();checkover();checkscore();fly();}}}system("pause");return 0;
}
我知道有很多人不会一个一个看,直接放完整版代码。
完整程序
#define _CRT_SECURE_NO_WARNINGS
/*开发日志
* 1.创建游戏窗口
* 2.导入素材
* 3.创建游戏页面
* (1)如何使图片移动
* (2)渲染背景,使背景透明化(png图片背景黑色删除)
* 4.主角登场
* 人物奔跑
* 人物跳跃(空格为跳)
* 5.帧等待
* 6.小怪显示
* 7.结构体封装
* 1.定义障碍物结构体
* 2.利用c++的可变数组加载不同障碍物的图片
* 3.建立障碍物池子(也就是结构体数组)
* 8.人物下蹲操作
* 此时进行人物渲染函数封装
* 9.设置悬挂障碍物
* 优化障碍物出现频率
* 10.核心算法====碰撞算法--第一次碰撞没声音,解决方案,外接接口
* 11.添加血条
* 12.解决一些bug:小怪出现太频繁,躲避不了
* 简单解决办法:就是就检测到柱子出现时,我们不要生成小怪2
* 13.最终打包封装
*/#include<stdio.h>
#include<graphics.h>
#include<conio.h>
#include"tools.h"
#include<vector> using namespace std;#define WHIDE 1012
#define HEIGHT 396
#define OBSTACLE_COUNT 10
#define win 20 //胜利分数
//三重循环
IMAGE imgbgs[3];
int bgx[3];//背景图片的x坐标
int bgspeed[3] = { 1,2,4 };IMAGE imgbgsren[12];
int renx;//人物横坐标
int reny;//人物纵坐标
int renindex;//玩家奔跑的图片帧
int jumpmax;//人物跳跃最大高度
bool renjump;//判断人物有没有跳
int rendownspeed;//下降
bool updata;//图片刷新开关
int blood;//人物血量
int score;//分数//IMAGE imgTortoise;//小怪1
//int torToiseX;//小怪1的x坐标
//int torToiseY;
//bool torToiseXExist;//当前窗口是否有小怪1typedef enum { //枚举类型guai1, guai2, zhu1, zhu2, zhu3, zhu4, //怪1为0,怪2为1sum //sum来表示一共有多少种怪
}obstacle_type;vector<vector<IMAGE>>obstacleimgs;//定义可变二维数组,行表示哪一种障碍物,列表示障碍物图片typedef struct obstacle {//障碍物封装obstacle_type type;//障碍物类型int imgindex;//当前图片序号int x, y; //障碍物的x,y坐标int speed;//速度int power;//障碍物杀伤力(俗称扣多少血)bool exist;//判断障碍物是否存在bool hit;//判断是否碰撞bool passed;//表示是否通过
}obstacle_t;obstacle_t obstacles[OBSTACLE_COUNT];//直接建一个池子,看有多少障碍物
int lastobindex; //上一个障碍物的序号//人物下蹲素材
IMAGE imgrendown[2];
bool rendown;//人物下蹲判定//数字素材
IMAGE imgsz[10];//游戏初始化
void init() {//创建游戏窗口initgraph(WHIDE, HEIGHT, EX_SHOWCONSOLE);//显示控制台//加载背景资源char name[64];for (int i = 0; i < 3; i++) {sprintf(name, "res/bg%03d.png", i + 1);loadimage(&imgbgs[i], name);}//加载角色帧图片(奔跑)for (int i = 0; i < 12; i++) {sprintf(name, "res/hero%d.png", i + 1);loadimage(&imgbgsren[i], name);}//设置玩家初始位置,之后应该不会变renx = WHIDE / 2 - imgbgsren[0].getwidth() / 2;reny = 345 - imgbgsren[0].getheight();renindex = 0;renjump = false;//人物开始是不跳jumpmax = reny - 120;rendownspeed = -4;//下降速度blood = 100;//加载小怪1//loadimage(&imgTortoise, "res/t2.png");//torToiseXExist = false;//小怪1初始化//torToiseY = 345 - imgTortoise.getheight()+5;IMAGE imgguai1;loadimage(&imgguai1, "res/t2.png");vector<IMAGE>boxguai1;//容器也就是数组boxguai1.push_back(imgguai1);//然后接下来把一维数组放到二维数组里去obstacleimgs.push_back(boxguai1);//接下来加载小怪2IMAGE imagguai2;vector<IMAGE> boxguai2;for (int i = 0; i < 6; i++) {sprintf(name, "res/p%d.png", i + 1);loadimage(&imagguai2, name);boxguai2.push_back(imagguai2);}obstacleimgs.push_back(boxguai2);//初始化障碍物池子for (int i = 0; i < OBSTACLE_COUNT; i++) {obstacles[i].exist = false;}//加载下蹲素材loadimage(&imgrendown[0], "res/d1.png");loadimage(&imgrendown[1], "res/d2.png");rendown = false;//障碍物(柱子动画)IMAGE imgH;for (int i = 0; i < 4; i++) {vector<IMAGE>imgzhu;sprintf(name, "res/h%d.png", i + 1);loadimage(&imgH, name, 63, 260, true); //记载的时候进行缩放imgzhu.push_back(imgH);obstacleimgs.push_back(imgzhu);}//预加载音效preLoadSound("res/hit.mp3"); //这玩意就是外部接口mciSendString("play res/bg.mp3 repeat", 0, 0, 0); //背景音乐lastobindex = -1;score = 0;//加载数字图片for (int i = 0; i < 10; i++) {sprintf(name, "res/sz/%d.png", i);loadimage(&imgsz[i], name);}
}//画血条
void drawblood(int x, int y, int width, int height, int lineWidth, int boardColor, int emptyColor, int fillColor, float percent) {LINESTYLE lineStyle;getlinestyle(&lineStyle);int lineColor = getlinecolor();int fileColor = getfillcolor();if (percent < 0) {percent = 0;}setlinecolor(BLUE);setlinestyle(PS_SOLID | PS_ENDCAP_ROUND, lineWidth);setfillcolor(emptyColor);fillrectangle(x, y, x + width, y + height);setlinestyle(PS_SOLID | PS_ENDCAP_FLAT, 0);setfillcolor(fillColor);setlinecolor(fillColor);if (percent > 0) {fillrectangle(x + 0.5 * lineWidth, y + lineWidth * 0.5, x + width * percent, y + height - 0.5 * lineWidth);}setlinecolor(lineColor);setfillcolor(fillColor);setlinestyle(&lineStyle);
}void creatobstacle() {int i;for (i = 0; i < OBSTACLE_COUNT; i++) {if (!obstacles[i].exist) {break;}}if (i >= OBSTACLE_COUNT) {return;}obstacles[i].exist = true;obstacles[i].hit = false;obstacles[i].imgindex = 0;obstacles[i].type = (obstacle_type)(rand() % 3);//强行转化if (lastobindex >= 0 &&obstacles[lastobindex].type >= zhu1 &&obstacles[lastobindex].type <= zhu4 &&obstacles[i].type == guai2 &&obstacles[lastobindex].x > (WHIDE - 500)) {obstacles[i].type = guai1;}lastobindex = i;if (obstacles[i].type == zhu1) { //优化障碍物的出现obstacles[i].type = (obstacle_type)(zhu1 + rand() % 4);}obstacles[i].x = WHIDE;obstacles[i].y = 350 - obstacleimgs[obstacles[i].type][0].getheight();if (obstacles[i].type == guai1) {obstacles[i].speed = 0;obstacles[i].power = 5; //小怪1的伤害}else if (obstacles[i].type == guai2) {obstacles[i].speed = 4;obstacles[i].power = 20;}else if (obstacles[i].type >= zhu1 && obstacles[i].type <= zhu4) {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].hit == false) { //利用开源代码,检测两个矩形是否相交int alx, aly, a2x, a2y;int off = 30;if (!rendown) { //如果人没有蹲,就是跑或者跳alx = renx + off;aly = reny + off;a2x = renx + imgbgsren[renindex].getwidth() - off;a2y = reny + imgbgsren[renindex].getheight();}else {alx = renx + off;aly = 345 - imgrendown[renindex].getheight();a2x = renx + imgrendown[renindex].getwidth() - off;a2y = 345;}IMAGE img = obstacleimgs[obstacles[i].type][obstacles[i].imgindex];//第几种障碍物的第几张图片int blx = obstacles[i].x + off;int bly = obstacles[i].y + off;int b2x = obstacles[i].x + img.getwidth() - off;int b2y = obstacles[i].y + img.getheight() - 10;if (rectIntersect(alx, aly, a2x, a2y, blx, bly, b2x, b2y)) {//发生碰撞blood -= obstacles[i].power;printf("血量剩余 %d\n", blood);playSound("res/hit.mp3");obstacles[i].hit = true;}}}
}//图片移动
void fly() {for (int i = 0; i < 3; i++) {bgx[i] -= bgspeed[i];if (bgx[i] < -WHIDE)bgx[i] = 0;}//人物跳跃实现if (renjump) { //卧槽改什么啊,y坐标啊,怎么什么都不会啊,你是宝贝吗if (reny < jumpmax) {//第一次是起跳所以开始负的,接下来一定是下降所以为正即可rendownspeed = 4;}reny += rendownspeed;if (reny > 345 - imgbgsren[0].getheight()) {//已经降到地面了renjump = false;rendownspeed = -4;}}else if (rendown) {static int count = 0;int delays[2] = { 4,32 }; //想要分别控制两张图片的站场时间count++;if (count >= delays[renindex]) { //站场时间不同count = 0;renindex++;if (renindex >= 2) {renindex = 0;rendown = false;}}}else {//不跳也不蹲renindex = (renindex + 1) % 12;}//创建障碍物static int frameCount = 0;//静态变量永久有效//static int torToiseFre = 100;static int enemyFre = 50;frameCount++;if (frameCount > enemyFre) {frameCount = 0;//if (!torToiseXExist) {//torToiseXExist = true;//torToiseX = WHIDE;//enemyFre = 200 + rand() % 301;//}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].getheight() * 2) {obstacles[i].exist = false;}int len = obstacleimgs[obstacles[i].type].size();obstacles[i].imgindex = (obstacles[i].imgindex + 1) % len;}}//if (torToiseXExist) {//设置小怪1的移动//torToiseX -= bgspeed[2];//if (torToiseX < -imgTortoise.getwidth()) {//torToiseXExist = false;//}//}checkhit();
}//渲染游戏背景
void updatabg() {putimagePNG2(bgx[0], 0, &imgbgs[0]);putimagePNG2(bgx[1], 119, &imgbgs[1]);putimagePNG2(bgx[2], 330, &imgbgs[2]);
}//人物图像渲染
void updataren() {if (!rendown) {putimagePNG2(renx, reny, &imgbgsren[renindex]);}else {int y = 345 - imgrendown[renindex].getheight();putimagePNG2(renx, y, &imgrendown[renindex]);}
}//人物跳跃开关
void jump() {renjump = true;updata = true; //人物帧等待(就是直接不等30ms了,直接刷新)
}//人物下蹲开关
void down() {rendown = true;updata = true;renindex = 0;
}//处理用户的按键输入
void anjian() {char ch;if (kbhit()) { //判断是否有输入ch = getch();//getch直接读取字符,没有缓冲区啦if (ch == ' ') {jump();}else if (ch == 'b') {down();}}}void updateEnemy() {//渲染小怪1//if (torToiseXExist) {// putimagePNG2(torToiseX, torToiseY, WHIDE, &imgTortoise);//}for (int i = 0; i < OBSTACLE_COUNT; i++) {if (obstacles[i].exist){putimagePNG2(obstacles[i].x, obstacles[i].y, WHIDE, &obstacleimgs[obstacles[i].type][obstacles[i].imgindex]);}}
}//渲染血条
void updatablood() {drawblood(10, 10, 200, 10, 2, BLUE, DARKGRAY, RED, blood / 100.0);
}void updatascore() {char str[3];sprintf(str, "%d", score);int x = 20;int y = 25;for (int i = 0; str[i] != '\0'; i++) {int sz = str[i] - '0';putimagePNG(x, y, &imgsz[sz]);x = x + imgsz[sz].getwidth() + 5;}
}void checkover() {if (blood <= 0) {loadimage(0, "res/over.png");FlushBatchDraw();mciSendString("stop res/bg.mp3", 0, 0, 0);system("pause");//准备设置一个按钮,开始下一盘if (getch() == 'w') {blood = 100;score = 0;mciSendString("play res/bg.mp3 repeat", 0, 0, 0); //背景音乐}}
}void checkscore() {for (int i = 0; i < OBSTACLE_COUNT; i++) {if (obstacles[i].exist && obstacles[i].passed == false && obstacles[i].x + obstacleimgs[obstacles[i].type][0].getwidth() < renx && obstacles[i].hit == false) {score++;obstacles[i].passed = true;printf("score=%d\n", score);}}
}void checkwin() {if (score >= win) {FlushBatchDraw();mciSendString("play res/win.mp3", 0, 0, 0);Sleep(2000);loadimage(0, "res/win.png");FlushBatchDraw();system("pause");if (getch() == 'w') { //重新开始mciSendString("stop res/win.mp3", 0, 0, 0);blood = 100;score = 0;("play res/bg.mp3 repeat", 0, 0, 0); //背景音乐}}
}int main() {init();int time = 0;loadimage(0, "res/over.png");if (getch() == ' ') {while (1) {anjian();time += getDelay();//背景帧等待if (time > 30) { //可以优化帧等待time = 0;updata = true;}if (updata) {updata = false;BeginBatchDraw();updatabg();//putimagePNG2(renx, reny, &imgbgsren[renindex]);updataren();updateEnemy();updatablood();updatascore();checkwin();EndBatchDraw();checkover();checkscore();fly();}}}system("pause");return 0;
}
效果展示:
弱化版
接下来是弱化版的代码:
#include<stdio.h>
#include<graphics.h>
#include<conio.h>
#include"tools.h" #define WHIDE 1012
#define HEIGHT 396
#define win 10 //胜利分数IMAGE guai1;//小怪1
int guai1x;//小怪1的x坐标
int guai1y;
bool guai1exist;//当前窗口是否有小怪1
bool hit1;
bool pass1;IMAGE guai2;
int guai2x;
int guai2y;
bool guai2exist;
bool hit2;
bool pass2;IMAGE guai3;
int guai3x;
int guai3y;
bool guai3exist;
bool hit3;
bool pass3;IMAGE guai4;
int guai4x;
int guai4y;
bool guai4exist;
bool hit4;
bool pass4;//二重循环
IMAGE imgbgs[2];
int bgx[2];//背景图片的x坐标
int bgspeed[2] = { 0,4 };IMAGE imgbgsren[12];
int renx;//人物横坐标
int reny;//人物纵坐标
int renindex;//玩家奔跑的图片帧
int jumpmax;//人物跳跃最大高度
bool renjump;//判断人物有没有跳
int rendownspeed;//下降
int score;//分数//人物下蹲素材
IMAGE imgrendown[2];
bool rendown;//人物下蹲判定//数字素材
IMAGE imgsz[10];void init() {//创建游戏窗口initgraph(WHIDE, HEIGHT);//显示控制台//加载背景资源char name[64];for (int i = 0; i < 2; i++) {sprintf(name, "res/bg%03d.png", i + 1);loadimage(&imgbgs[i], name);}//加载角色帧图片(奔跑)for (int i = 0; i < 12; i++) {sprintf(name, "res/hero%d.png", i + 1);loadimage(&imgbgsren[i], name);}//设置玩家初始位置,之后应该不会变renx = WHIDE / 2 - imgbgsren[0].getwidth() / 2;reny = 345 - imgbgsren[0].getheight();renindex = 0;renjump = false;//人物开始是不跳jumpmax = reny - 130;rendownspeed = -3;//下降速度//加载小怪1loadimage(&guai1, "res/t2.png");guai1exist = false;//小怪1初始化guai1y = 355 - guai1.getheight();//加载小怪2loadimage(&guai2, "res/t1.png");guai2exist = false;guai2y = 355 - guai2.getheight();//加载小怪3loadimage(&guai3, "res/h1.png",63,260);guai3exist = false;guai3y = -10;//加载小怪4loadimage(&guai4, "res/h4.png",63,260);guai4exist = false;guai4y = -10;//加载下蹲素材loadimage(&imgrendown[0], "res/d1.png");loadimage(&imgrendown[1], "res/d2.png");rendown = false;//预加载音效preLoadSound("res/hit.mp3"); //这玩意就是外部接口mciSendString("play res/bg.mp3 repeat", 0, 0, 0); //背景音乐score = 0;//加载数字图片
for (int i = 0; i < 10; i++) {sprintf(name, "res/sz/%d.png", i);loadimage(&imgsz[i], name);
}
}//玩家碰撞检测
void checkhit() {int alx, aly, a2x, a2y, b1x, b2x, b1y, b2y;int off = 30;if (guai1exist == true && hit1 == false) {if (!rendown) { //如果人没有蹲,就是跑或者跳alx = renx + off;aly = reny + off;a2x = renx + imgbgsren[renindex].getwidth() - off;a2y = reny + imgbgsren[renindex].getheight();}else {alx = renx + off;aly = 345 - imgrendown[renindex].getheight();a2x = renx + imgrendown[renindex].getwidth() - off;a2y = 345;}b1x = guai1x + off+10;b1y = guai1y + off+10;b2x = guai1x + guai1.getwidth() - off;b2y = guai1y + guai1.getheight() - 10;if (rectIntersect(alx, aly, a2x, a2y, b1x, b1y, b2x, b2y)) {//发生碰撞hit1 = true;mciSendString("play res/hit.mp3",0,0,0);if (score > 0) {score--;}}}if (guai2exist == true && hit2 == false) {if (!rendown) { //如果人没有蹲,就是跑或者跳alx = renx + off;aly = reny + off;a2x = renx + imgbgsren[renindex].getwidth() - off;a2y = reny + imgbgsren[renindex].getheight();}else {alx = renx + off;aly = 345 - imgrendown[renindex].getheight();a2x = renx + imgrendown[renindex].getwidth() - off;a2y = 345;}b1x = guai2x + off+10;b1y = guai2y + off+10;b2x = guai2x + guai2.getwidth() - off;b2y = guai2y + guai2.getheight() - 10;if (rectIntersect(alx, aly, a2x, a2y, b1x, b1y, b2x, b2y)) {//发生碰撞hit2 = true;mciSendString("play res/hit.mp3", 0, 0, 0);if (score > 0) {score--;}}}if (guai3exist == true && hit3 == false) {if (!rendown) { //如果人没有蹲,就是跑或者跳alx = renx + off;aly = reny + off;a2x = renx + imgbgsren[renindex].getwidth() - off;a2y = reny + imgbgsren[renindex].getheight();}else {alx = renx + off;aly = 345 - imgrendown[renindex].getheight();a2x = renx + imgrendown[renindex].getwidth() - off;a2y = 345;}b1x = guai3x + off;b1y = guai3y + off;b2x = guai3x + guai3.getwidth() - off;b2y = guai3y + guai3.getheight() - 10;if (rectIntersect(alx, aly, a2x, a2y, b1x, b1y, b2x, b2y)) {//发生碰撞hit3 = true;mciSendString("play res/hit.mp3", 0, 0, 0);if (score > 0) {score--;}}}if (guai4exist == true && hit4 == false) {if (!rendown) { //如果人没有蹲,就是跑或者跳alx = renx + off;aly = reny + off;a2x = renx + imgbgsren[renindex].getwidth() - off;a2y = reny + imgbgsren[renindex].getheight();}else {alx = renx + off;aly = 345 - imgrendown[renindex].getheight();a2x = renx + imgrendown[renindex].getwidth() - off;a2y = 345;}b1x = guai4x + off;b1y = guai4y + off;b2x = guai4x + guai4.getwidth() - off;b2y = guai4y + guai4.getheight() - 10;if (rectIntersect(alx, aly, a2x, a2y, b1x, b1y, b2x, b2y)) {//发生碰撞hit4 = true;mciSendString("play res/hit.mp3", 0, 0, 0);if (score > 0) {score--;}}}
}//图片移动
void fly() {for (int i = 0; i < 2; i++) {bgx[i] -= bgspeed[i];if (bgx[i] < -WHIDE)bgx[i] = 0;}//人物跳跃实现if (renjump) { if (reny < jumpmax) {//第一次是起跳所以开始负的,接下来一定是下降所以为正即可rendownspeed = 4;}reny += rendownspeed;if (reny > 345 - imgbgsren[0].getheight()) {//已经降到地面了renjump = false;rendownspeed = -4;}}else if (rendown) {static int count = 0;int delays[2] = { 4,32 }; //想要分别控制两张图片的站场时间count++;if (count >= delays[renindex]) { //站场时间不同count = 0;renindex++;if (renindex >= 2) {renindex = 0;rendown = false;}}}else {//不跳也不蹲renindex = (renindex + 1) % 12;}//创建障碍物static int frameCount = 0;//静态变量永久有效static int guai1count = 0;static int guai2count = 1;static int guai3count = 2;static int guai4count = 3;static int enemyFre = 50;frameCount++;if (frameCount > enemyFre) {frameCount = 0;int count = rand() % 4;if (!guai1exist && count == guai1count) {guai1exist = true;hit1 = false;pass1=false;guai1x = WHIDE;enemyFre = 50 + rand() % 50;}if (!guai2exist && count == guai2count) {guai2exist = true;hit2 = false;pass2 = false;guai2x = WHIDE;enemyFre = 50+ rand() % 50;}if (!guai3exist && count == guai3count) {guai3exist = true;hit3 = false;pass3 = false;guai3x = WHIDE;enemyFre = 50 + rand() % 50;}if(!guai4exist && count == guai4count){guai4exist = true;hit4 = false;pass4 = false;guai4x = WHIDE;enemyFre = 50 + rand() % 50;}}//更新坐标//设置小怪1的移动if (guai1exist) {guai1x -= bgspeed[1];if (guai1x < -guai1.getwidth()) {guai1exist = false;}}//设置小怪2的移动if (guai2exist) {guai2x -= bgspeed[1];if (guai2x < -guai2.getwidth()) {guai2exist = false;}}//设置小怪3的移动if (guai3exist) {guai3x -= bgspeed[1];if (guai3x < -guai3.getwidth()) {guai3exist = false;}}//设置小怪4的移动if (guai4exist) {guai4x -= bgspeed[1];if (guai4x < -guai4.getwidth()) {guai4exist = false;}}checkhit();
}void updatabg() {putimagePNG2(-100, 0, &imgbgs[0]);putimagePNG2(bgx[1], 330, &imgbgs[1]);
}//人物图像渲染
void updataren() {if (!rendown) {putimagePNG(renx, reny, &imgbgsren[renindex]);}else {int y = 345 - imgrendown[renindex].getheight();putimagePNG(renx, y, &imgrendown[renindex]);}
}//人物跳跃开关
void jump() {renjump = true;
}//人物下蹲开关
void down() {rendown = true;renindex = 0;
}//渲染分数
void updatascore() {char str[3];sprintf(str, "%d", score);int x = 512;int y = 25;for (int i = 0; str[i] != '\0'; i++) {int sz = str[i] - '0';putimagePNG(x, y, &imgsz[sz]);x = x + imgsz[sz].getwidth() + 5;}
}void updateEnemy() {//渲染小怪1if (guai1exist) {putimagePNG2(guai1x, guai1y, WHIDE, &guai1);}if (guai2exist) {putimagePNG2(guai2x, guai2y, WHIDE, &guai2);}if (guai3exist) {putimagePNG2(guai3x, guai3y, WHIDE, &guai3);}if (guai4exist) {putimagePNG2(guai4x, guai4y, WHIDE, &guai4);}
}void checkwin() {if (score >= win) {updatascore();FlushBatchDraw();mciSendString("play res/win.mp3", 0, 0, 0);Sleep(2000);loadimage(0, "res/win.png");FlushBatchDraw();system("pause"); //继续游戏mciSendString("stop res/win.mp3", 0, 0, 0);score = 0;}
}void checkscore() {if (guai1exist && pass1 == false && guai1x + guai1.getwidth() < renx && hit1 == false) {score++;pass1 = true;}if (guai2exist && pass2 == false && guai2x + guai2.getwidth() < renx && hit2 == false) {score++;pass2 = true;}if (guai3exist && pass3 == false && guai3x + guai3.getwidth() < renx && hit3 == false) {score++;pass3 = true;}if (guai4exist && pass4 == false && guai4x + guai4.getwidth() < renx && hit4 == false) {score++;pass4 = true;}
}int main() {while (1) {init();loadimage(0, "res/over.png");if (getch() == 'o') { //开始游戏while (1) {char ch;if (kbhit()) { //判断是否有输入ch = getch();//getch直接读取字符,没有缓冲区啦if (ch == 'w') {jump();}else if (ch == 's') {down();}else if (ch == 'p') {break;}}BeginBatchDraw();updatabg();updataren();updateEnemy();updatascore();checkscore();checkwin();EndBatchDraw();fly();Sleep(20);}}}system("pause");return 0;
}
这里面的代码是由最基础的代码组成,即使是非常小白也可以轻易看懂。
给大家展示一下效果:
由于人物的帧动画实在难找也非常不好制作(主要是博主是ps新手,不是特别会弄),就直接拿rock里的素材啦,哈哈。其中的音效和背景音乐也是博主自己剪辑出来的。
详细讲解
说是详细讲解,就是跟大家说一下我在写代码过程中遇到的困难以及总结出来的小技巧,可以减少大家走弯路的时间。
1.大家会发现在游戏代码中有一个tools.h的头文件,这其实是rock程序员大佬自己写的一个头文件,详细内容我就不在这里放了,素材提取地址我放在文章最后。需要在写游戏时加入到程序中,建议大家使用vs进行编写,直接添加即可。
2.很多小伙伴直接将代码拷贝过去时发现vs会报错,这里讲一下一些设置的调整。
打开如图的属性界面(右键单击“图中天天酷跑”即可),打开“高级”,找到“字符集”,下拉选择“使用多字节字符集”;接下来打开“c/c++”,打开“常规”,选择“SDL检查”,该选项为“否”。这样子应该就可以运行了。
3.初学者在修改代码的时候,可以为障碍物添加数组,或者用结构体进行封装再添加数组,这样可以大大减少运行空间以及代码长度;当然,要是实在懒得动脑,直接照搬我的弱化版一直往上添加变量即可。
4.大家在写程序的时候建议把代码,头文件,素材放在同一个文件夹中,这样不容易出错。
5.大家在写小游戏的时候,如果要多次使用if--else if--else语句,建议不要多次使用else if,可以直接多次使用if判断,博主尝试过在弱化版中使用else if,结果某一段else if会无法运行,程序不会报错,语法也没有问题,就当作是一个经验吧。
6.写小游戏的时候建议多定义全局变量,但不适用于大型游戏,多多进行函数封装,这样不容易弄混。
嗯。。。。就这样吧。
接下来献上素材地址:
链接: https://pan.baidu.com/s/1sgm2xraVIhFAfzeIvHdIgA?pwd=n3pe 提取码: n3pe (弱化版素材)
链接: https://pan.baidu.com/s/12u29t1FbXj8WRiY6o_DYKQ?pwd=v8iv 提取码: v8iv (完整版素材)