C项目 天天酷跑(下篇)

news2025/2/14 0:59:34

上篇再博客里面有,接下来我们实现我们剩下要实现的功能


文章目录 

碰撞检测

血条的实现

积分计数器


 

前言

我们现在要继续优化我们的程序才可以使这个程序更加的全面


碰撞的检测

定义全局变量

实现全局变量

void checkHit() {
	for (int i = 0; i < OBSTACLE_CUONT; i++) {
		if (obstacles[i].exist && obstacles[i].hited == false) {
			int ax1, ay1, ax2, ay2;
			//角色的碰撞宽度高度设置
			if (!heroDown) {
				ax1 = heroX;
				ay1 = heroY;
				ax2 = heroX + imgHero[i].getwidth();
				ay2 = heroY - imgHero[i].getheight();
			}
			else {
				ax1 = heroX;
				ay1 = 397 - imgHeroDown[i].getheight();
				ax2 = heroX + imgHeroDown[i].getwidth();
				ay2 = 397;
			}
			//障碍物的碰撞宽度高度设置
			int bx1 = obstacles[i].x;
			int by1 = obstacles[i].y;
			int bx2 = obstacles[i].x + obstacleImags[obstacles[i].type][obstacles[i].imgIndex].getwidth();
			int by2 = obstacles[i].y - obstacleImags[obstacles[i].type][obstacles[i].imgIndex].getheight()-10;
			if (rectIntersect(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2)) {
				//写到这里,只要我们有图片和障碍物相交在一起就会扣血,因为在屏幕里面,怪是要到屏幕之外才是结束存在的
				heroBoold -= obstacles[i].power;
				printf("血量剩余%d\n", heroBoold);
				obstacles[i].hited = true;
			}
		}
	}

}

我们来看到这个代码是实现碰撞检测的,我们来画一个图

我们要找出这两个人的x1,x2,y1,y2的坐标后面的by-10是因为我这图片的问题,就是kunkun和别的尺寸有问题才-10,一般是不需要减10的,正常写就好接下来我们就要判断是否发生碰撞了

接下里我们就要学习是否发生碰撞这个判断条件了

bool rectIntersect(int x01, int y01, int x02, int y02,
    int x11, int y11, int x12, int y12)
{
    int zx = abs(x01 + x02 - x11 - x12);
    int x = abs(x01 - x02) + abs(x11 - x12);
    int zy = abs(y01 + y02 - y11 - y12);
    int y = abs(y01 - y02) + abs(y11 - y12);
    return  (zx <= x && zy <= y);
}

这个是我们要用到的函数,这个函数参数

x01--人的x1    y01--人的y1   x02--人的x2   y02--人的y2   x11--障碍物的x1   y11--障碍物的y1

x12--障碍物的x2  y12--障碍物的y2 

zx和x的理解

这个就是zx和x之间的原理 

 

这个是zy和y的原理跟x差不多

当我们实现这个碰撞检测的时候,我们就可以继续写了,我们就利用这个碰撞检测来实现这个血条的扣血

在run函数里面实现进行每一次的检测

这里我们打开了hitd这个开关,我们不需要写一个关闭这个开关的程序,因为每一次创建一个障碍物的时候,他都会进行一次初始化,这个开关它会关闭,碰到之后再继续打开

血条的扣血

我们定义一个这个函数来显示血条 

 这个是血条扣除的函数

void drawBloodBar(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);
}

参数部分:

  • x, y: 血条左上角的坐标
  • width, height: 血条的宽度和高度
  • lineWidth: 边框的宽度
  • boardColor: 背景色(未使用,但可以用于描边)
  • emptyColor: 进度条背景(未填充部分)的颜色
  • fillColor: 进度条已填充部分的颜色
  • percent: 进度条已填充部分的百分比,取值范围为0到1

 接下来分析这个函数里面的代码内容

LINESTYLE 是一个数据类型,用于表示线条的样式。它通常在图形编程库(比如 EasyX)中用来存储与线条相关的属性,比如线条的宽度、样式、端点样式等

典型的 LINESTYLE 结构体可能包含:

  1. 线条宽度width):指定线条的宽度
  2. 线条样式style):指定线条的样式,常见的样式包括实线、虚线等
  3. 线条端点样式cap):设置线条的端点样式,比如圆形端点、方形端点等
  4. 线条连接样式join):设置线段连接点的样式,如圆形连接、尖角连接等

示例:

在 EasyX 中,LINESTYLE 的定义可能类似于:

typedef struct _LINESTYLE {
    int width;   // 线条宽度
    int style;   // 线条样式
    int cap;     // 端点样式
    int join;    // 连接点样式
} LINESTYLE;

这个是eaxyx里面的

 

总结来说,LINESTYLE 是一个结构体,用来存储和管理与线条绘制相关的多种样式设置

LINESTYLE lineStyle;
getlinestyle(&lineStyle);
int lineColor = getlinecolor();
int fileColor = getfillcolor();

这个代码是

  • getlinestyle(&lineStyle):获取当前的线条样式
  • getlinecolor():获取当前的线条颜色
  • getfillcolor():获取当前的填充颜色

这个是获取你系统自带的默认的颜色,避免你后面没有颜色和样例

这个是一个浮点数,就是血条的百分比含量 ,当为小于0的时候,就归属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);
  • setlinecolor(BLUE);

    • 这行代码设置 线条的颜色为蓝色。通常,这会影响矩形的边框颜色(即轮廓)。
    • BLUE 是 EasyX 图形库中的预定义颜色常量,表示蓝色。
  • setlinestyle(PS_SOLID | PS_ENDCAP_ROUND, lineWidth);

    • 这行代码设置 线条的样式
      • PS_SOLID 表示线条是 实线(不是虚线或点线)。
      • PS_ENDCAP_ROUND 表示线条的端点是 圆形,即线段的两端是圆头,而不是方头或平头。
      • lineWidth 是线条的宽度,通常是一个整数,表示线条的粗细(例如 2 表示线条宽度为 2 像素)。
  • setfillcolor(emptyColor);

    • 这行代码设置 填充颜色,即矩形内部的颜色。
    • emptyColor 是传入的一个变量,表示血条的背景颜色,通常是透明或灰色,表示血条没有被填充的部分。
  • fillrectangle(x, y, x + width, y + height);

    • 这行代码用于 绘制矩形
    • x, y 是矩形的左上角坐标,x + width, y + height 是矩形的右下角坐标。这样,矩形的大小和位置就由这些坐标确定了。
    • 这个矩形会被 填充为之前设置的 emptyColor,即矩形的背景颜色。

这个下一个就是恢复样例和颜色了,避免下一次使用不是默认的样例和颜色

  • setlinestyle(PS_SOLID | PS_ENDCAP_FLAT, 0);

    • 这行代码 恢复线条样式
      • PS_SOLID 表示线条是 实线
      • PS_ENDCAP_FLAT 表示线条的端点是 平头,即线段的两端是直角,而不是圆形端点。
      • 0 表示 线宽为 0,这意味着不会显示边框,这通常用于消除矩形或其他图形的外部轮廓,只显示填充

这个后面就是填充血条的颜色,上面那个是空血条的颜色

  • setfillcolor(fillColor);

    • 这行代码设置 填充颜色fillColor,也就是血条的 实际填充颜色,即显示血量的颜色。通常这个颜色是 红色绿色 或其他表示血量的颜色。
  • setlinecolor(fillColor);

    • 这行代码将 线条颜色 设置为 fillColor,即设置血条边框的颜色为填充颜色。这个通常是为了确保血条的边框与填充颜色一致,给人一致的视觉效果。

核心句子

if (percent > 0) {
    fillrectangle(x + 0.5 * lineWidth, y + lineWidth * 0.5, x + width * percent, y + height - 0.5 * lineWidth);
}
  • 左上角坐标x + 0.5 * lineWidthy + lineWidth * 0.5
    • x + 0.5 * lineWidth:由于在绘制矩形时需要考虑线条宽度,所以 x 坐标偏移了 0.5 * lineWidth,确保边框的线宽(lineWidth)不会影响矩形的显示位置,确保绘制的矩形与边框之间有一定的间隔。
    • y + lineWidth * 0.5:同样,y 坐标也进行了偏移,确保矩形的上边界与线条宽度相适应。
  • 右下角坐标x + width * percenty + height - 0.5 * lineWidth
    • x + width * percent:这里的 x + width * percent 计算了矩形的 实际宽度,它是根据当前血量的百分比 percent 来决定的。如果 percent 为 0,矩形的宽度为 0;如果 percent 为 1,矩形的宽度将是血条的最大宽度 width
    • y + height - 0.5 * lineWidthy + height 为矩形的下边界,减去 0.5 * lineWidth 是为了避免线条的宽度影响矩形的底部显示。

我们这里不需要修改这个左上角标的值的,只需要修改右下角标的值,这样就可以实现动态绘画这个血条了

setlinecolor(lineColor);
setfillcolor(fillColor);
setlinestyle(&lineStyle);

这个是恢复之前的颜色,避免下一次使用不是默认的值,我们之前就把初始化的东西用变量储存起来了

积分计数器

void checkpassed() {
	for (int i = 0; i < OBSTACLE_CUONT; i++) {
		if (obstacles[i].exist &&
			obstacles[i].passed == false &&
			obstacles[i].x + obstacleImags[obstacles[i].type][0].getwidth()) {
			score++;
			obstacles[i].passed = true;
			printf("%d", score);
		}
	}
}

这个就是我们来检测是否跳过这个障碍物,我们只需要检测这个开关是否关闭,还有这个长度是否超过这个图片的长度还有这个障碍物是否存在,执行完成之后,就打开这个开关,防止一直加分,然后就是我们要加一个分数绘制在这个窗口

void updateScore() {
	//ASCLL码值 ‘5’-‘0’=5
	char str[8];
	sprintf(str, "%d", score);
	int x = 20;
	int y = 25;
	for (int i = 0; str[i] != 0; i++) {
		int num = str[i] - '0';
		putimage(x, y, &imgSZ[num]);
		x += imgSZ[num].getwidth();
	}
}

我们只需要把这个score格式化一下从int到char类型,然后利用sprintf来读取对应的数字到数组里面,然后就是用for循环来来便利 ,这里也运用了ASCII值的转换,后买你x+=这一个代码是为了是图片连起来

源码

//图片的长宽
#define _CRT_SECURE_NO_WARNINGS 1
#define WIN_LONG 1150
#define WIN_WIDTH 538
#define OBSTACLE_CUONT 20

#include<time.h>
#include<stdio.h>
#include<graphics.h>
//这个是用来调用按键是否输入的
#include<conio.h>
//这个是用来使用变长数组的
#include<vector>
#include<windows.h>
//播放音乐的时候用的两行代码
#include<mmsystem.h>
#pragma comment(lib,"winmm.lib")

#include"tools.h"
using namespace std;  //声明命名空间
/* 背 景 制 作 */
//背景图数组
IMAGE imgBgs[2];
//背景图的x
int bgX[2];
//用来存放速度
int bgspeed[2] = { 4,8 };
/* 角 色 制 作*/
//角色奔跑
IMAGE imgHero[12];
//玩家会被弹飞还有下蹲啥的,所以要设置全局变量
int heroX;   //x坐标
int heroY;   //y坐标
int heroIndex;  //玩家奔跑时图片的序号

bool heroJump;  //表示玩家正在跳跃(跳跃开关)
bool heroDown;  //表示玩家正在下蹲(下蹲开关)

int heroBoold;

int jumpHeightMax;
int herojumpOff;
int update;      //表示是否需要马上刷新画面

int score;

/* 乌 龟 制 作*/
//IMAGE imgTortoise; //小乌龟
//int torToiseX;     //小乌龟的x坐标
//int torToiseY;     //小乌龟的y坐标
//bool torToiseExist;//当前窗口是否有小乌龟
//由于障碍物越来越多,则需要进行封装,这样就可以简化代码行数,实现代码不拥挤


typedef enum {
	TORTOISE,   //奶龙0
	LION,       //狮子1
	//四个柱子
	HOOK1,
	HOOK2,
	HOOK3,
	HOOK4,
	OBSTACLE_TYPE_COUNT//常看有几种障碍物(因为是从0开始,所以更加方便看有几种类型)ho
}obstacle_type;

//IMAGE obstacleImage[3][12];用这个的话会浪费些许内存,因为乌龟只有一个图片
vector<vector<IMAGE>>obstacleImags(OBSTACLE_TYPE_COUNT,vector<IMAGE>(12));//里面一个成员的又一个成员(可变数组)
//相当于int data[ int ]

typedef struct obstacle {
	obstacle_type type;//障碍物的类型
	int imgIndex;       //当前显示的图片的序号
	//但是这样有障碍物的数字代表不是特别可读,所以我们可以利用枚举类型,更加可读
	int x, y;           //障碍物的x与y坐标
	int speed;
	int power;          //伤害
	bool exist;
	bool hited;        //碰撞
	bool passed;
}obstacle_t;

//由于我们不可以把障碍物直接全部都是弄出来,要逐个逐个的,就像捕鱼达人一样,所以我们要建立一个池子来存储这个预备队员,就像篮球,一个上场一个下场
obstacle_t obstacles[OBSTACLE_CUONT];
//玩家下滑图片储存的数组
IMAGE imgHeroDown[2];
//创建窗口

IMAGE imgSZ[10];

void init() {
	//创建窗口
	//第三个参数是为了显示控制台的参数
	//第三个参数是为了显示控制台的参数
	//在initgraph后面, EX_SHOWCONSOLE这个在打包删除了
	initgraph(WIN_LONG, WIN_WIDTH);
    
	/*背 景 制 作*/
	//加载背景图  loadimage函数(取地址,具体的文件名字)
	char name[64];
	for (int i = 0; i < 2; i++) {
		//"ret/bg001.png"  "ret/bg002.png"  "ret/bg003.png"
		sprintf(name, "ret/bg%03d.png", i + 1);
		//这个是要在win32平台和多字节字符串的情况下才可以运行
		loadimage(&imgBgs[i], name);
		//初始化背景图的x轴坐标
		bgX[i] = 0;
	}
	/*人 物 制 作*/
	//加载玩家奔跑
	for (int i = 0; i < 12; i++) {
		//"跑步1"  "跑步2"
		sprintf(name, "ret/跑步%d.png", i + 1);
		loadimage(&imgHero[i], name);
	}
	//设置玩家初始位置
	//窗口宽度*0.5-自己宽度*0.5
	heroX = 600 * 0.5 - 200 * 0.5 - 80;
	heroY = 300;
	heroIndex = 0;
	//玩家跳跃
	heroJump = false;
	jumpHeightMax = 80;//记得是减,不是加,注意坐标原点
	herojumpOff = -6;

	update = true;

	/*小 乌 龟 制 作*/
	//loadimage(&imgTortoise, "ret/奶龙.png");
	//torToiseExist = false;
	//torToiseY = 400;
	/*小 奶 龙 改 版*/
	IMAGE imgTort;
	loadimage(&imgTort, "ret/奶龙.png");
	vector<IMAGE>imgTorArray;
	imgTorArray.push_back(imgTort);
	obstacleImags[TORTOISE] = imgTorArray;

	/*kunkun 图片制作*/
	IMAGE imgLion;
	vector<IMAGE>imgLionArray;
	for (int i = 0; i < 12; i++) {
		sprintf(name, "ret/kun%d.png", i + 1);
		loadimage(&imgLion, name);
		imgLionArray.push_back(imgLion);
	}
	obstacleImags[LION] = imgLionArray;

	//初始化障碍物池
	for (int i = 0; i < OBSTACLE_CUONT; i++) {
		obstacles[i].exist = false;
	}

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

	//加载柱子
	// 创建单独的数组或 vector 存储每个障碍物的图片
	IMAGE imgH1;
	loadimage(&imgH1, "ret/hook1.png", 63, 310);
	vector<IMAGE> imgHook1Array;
	imgHook1Array.push_back(imgH1);
	obstacleImags[HOOK1] = imgHook1Array;

	IMAGE imgH2;
	loadimage(&imgH2, "ret/hook2.png", 63, 310);
	vector<IMAGE> imgHook2Array;
	imgHook2Array.push_back(imgH2);
	obstacleImags[HOOK2] = imgHook2Array;

	IMAGE imgH3;
	loadimage(&imgH3, "ret/hook3.png", 63, 310);
	vector<IMAGE> imgHook3Array;
	imgHook3Array.push_back(imgH3);
	obstacleImags[HOOK3] = imgHook3Array;

	IMAGE imgH4;
	loadimage(&imgH4, "ret/hook4.png", 63, 310);
	vector<IMAGE> imgHook4Array;
	imgHook4Array.push_back(imgH4);
	obstacleImags[HOOK4] = imgHook4Array;


	heroBoold = 100;

	mciSendString("open ret/bk.mp3", NULL, 0, NULL);
	mciSendString("play ret/bk.mp3 repeat", NULL, 0, NULL);
     
	//加载数字
	for (int i = 0; i < 10; i++) {
		sprintf(name, "ret/sz/%d.png", i);
		loadimage(&imgSZ[i], name);
	}
}

void creatObstacle() {
	srand((unsigned)time(NULL));
	int i = 0;
	for ( i = 0; i < OBSTACLE_CUONT; i++) {
		if (obstacles[i].exist == false) {
			break;
		}
	}
	if (i >= OBSTACLE_CUONT) {
		return;
	}
	obstacles[i].exist = true;
	obstacles[i].hited = false;
	obstacles[i].imgIndex = 0;
	obstacles[i].passed = false;
	//obstacles[i].type = (obstacle_type)(rand() % OBSTACLE_TYPE_COUNT);//由于vs的语法要求比较严,所以我们进行强制转换为枚举类型
	obstacles[i].x = WIN_LONG;
	obstacles[i].y = 380;
	obstacles[i].type=(obstacle_type)(rand() % 3);
	if (obstacles[i].type == HOOK1) {
		obstacles[i].type = (obstacle_type)((int)(obstacles[i].type)+rand()%4);
	}
	//速度,伤害的配置的话,就是各有所需
	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 = 10;
	}
	else if (obstacles[i].type >= HOOK1 && obstacles[i].type <= HOOK4) {
		obstacles[i].speed = 0;
		obstacles[i].power = 10;
		obstacles[i].y = 0;
	}
	
}

//玩家和障碍物的碰撞检测处理

void checkHit() {
	for (int i = 0; i < OBSTACLE_CUONT; i++) {
		if (obstacles[i].exist && obstacles[i].hited == false) {
			int ax1, ay1, ax2, ay2;
			//角色的碰撞宽度高度设置
			if (!heroDown) {
				ax1 = heroX;
				ay1 = heroY;
				ax2 = heroX + imgHero[i].getwidth();
				ay2 = heroY - imgHero[i].getheight();
			}
			else {
				ax1 = heroX;
				ay1 = 397 - imgHeroDown[i].getheight();
				ax2 = heroX + imgHeroDown[i].getwidth();
				ay2 = 397;
			}
			//障碍物的碰撞宽度高度设置
			int bx1 = obstacles[i].x;
			int by1 = obstacles[i].y;
			int bx2 = obstacles[i].x + obstacleImags[obstacles[i].type][obstacles[i].imgIndex].getwidth();
			int by2 = obstacles[i].y - obstacleImags[obstacles[i].type][obstacles[i].imgIndex].getheight()-10;
			if (rectIntersect(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2)) {
				//写到这里,只要我们有图片和障碍物相交在一起就会扣血,因为在屏幕里面,怪是要到屏幕之外才是结束存在的
				heroBoold -= obstacles[i].power;
				printf("血量剩余%d\n", heroBoold);
				obstacles[i].hited = true;
			}
		}
	}

}

void run() {
	srand((unsigned)time(NULL));
	for (int i = 0; i < 2; i++) {
		bgX[i] -= bgspeed[i];
		//用于返回到当时图片的位置,这样才可以在后面继续奔跑
		if (bgX[i] < -WIN_WIDTH) {
			bgX[i] = 0;
		}
	}
	//玩家帧序列
	heroIndex = (heroIndex + 1) % 12;
	//0~11的移动(因为就这么多图片)

	//实现跳跃(改变玩家的i坐标)
	//有个上升下降的过程()
	//因为这个是套在while里面的,随着函数的不断推进,他会一直跳跃,直到触发另外一个if开关
	if (heroJump) {
		//下降阶段
		if (heroY < jumpHeightMax) {
			herojumpOff = 6;
		}
		//一开始处于上升,后面处于下降
		heroY += herojumpOff;
		//280初始值
		if (heroY > 280) {
			heroJump = false;
			herojumpOff = -6;
		}
	}
	else if (heroDown) {
		static int count = 0;
		int delays[2] = { 15,20 };
		count++;

	//由于下蹲就两张图片,所以我们要延续时间(这里可以用静态变量,来延缓帧数)
		if (count >= delays[heroIndex]) {
			count = 0;
			heroIndex++;
			if (heroIndex >= 2) {
				heroIndex = 0;
				heroDown = false;
			}
		}
	}
	else{ 
	heroIndex = (heroIndex + 1) % 12;
		}
	
	//生成障碍物(如果跟着系统的30ms来整出小奶龙的话,那就满篇小奶龙了)
    //我们可以计算,这个函数调用的次数*30ms来决定这个乌龟是否生成,比如我们要3s生成一次,那我们就调用100次*30ms就可以了进率1000
	//那我们要知道调用多少次,用静态变量,因为这样调用完之后销毁也会保存在静态区
	static int frameCount = 0;
	static int enemyFre = 50;
	frameCount++;
	if (frameCount > enemyFre) {
		frameCount = 0;
		enemyFre = 50 + rand() % 50; // 50~99
		creatObstacle();
	}
	
	//更新奶龙位置
	//if (torToiseExist) {
	//	torToiseX -= 4;
	//	//当奶龙完全让自己出去的时候
	//	if (torToiseX < -151) {
	//		torToiseExist = false;
	//	}
	//}

	//更新障碍物的坐标
	for (int i = 0; i < OBSTACLE_CUONT; i++) {
		if (obstacles[i].exist) {
			obstacles[i].x -= obstacles[i].speed + bgspeed[1];
			if (obstacles[i].x < -300) {
				obstacles[i].exist = false;
			}
			//由于我们的坤坤为了可以出现变动,所以哦我们要加这个变化图片
			int len = obstacleImags[obstacles[i].type].size();        //type为第几行
			obstacles[i].imgIndex = (obstacles[i].imgIndex + 1) % len;//(我们不用障碍物的图片数量不一样,所以我们要用len)

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


//渲染游戏背景
void updateBg() {
	//x轴是确定图片滚到哪里的  y轴是确定位置的
	//这里的滚动是有这个bgX来变化来进行图片的滚动
	//由于这个是一次一次吧图片加载出来的,但是我们想一次性一起加载出来这个时候就要添加
	//BeginBatchDraw()和EndBatchDraw()
	//一个是开始批量绘制图形,一个是结束开始批量绘制图形
	putimage(bgX[0], 0, &imgBgs[0]);
	putimage(bgX[1],397, &imgBgs[1]);
	
}
void jump() {
	//实现跳跃的时候,坐标是慢慢移动的,而不是直接蹦上去瞬移了
	heroJump = true;//启动开关
	update = true;  //在30毫秒过程中,如果在30毫秒点击空格跳跃,则这个立马刷新,直接步入到下面那个if语句然后直接进入,这样可以接受按键
}
void down() {
	heroDown = true;
	update = true;
	heroIndex = 0;
}



//处理用户按键输入
void keyEvent() {
	char ch;
	//如果有按键按下,则这个kbhit函数是返回为真,没有则反之
   	if (_kbhit()) {
		/*scanf("%c", &c);*/  //这里用scanf的话这里是要按下回车才可以继续执行,降低了可玩性,所以不用scanf
		ch = _getch();    //这个是scanf的pilus版,这个是不需要按下回车,直接读取这个输入的字符
		//由于vs版本,这个getch和kbhit都是要加_这个的,不加会报警告
		if (ch == ' ') {
			jump();//功能相对独立的要封装成一个函数
		}
		else if (ch == 's') {
			down();
		}
	}
	/*char c;
	scanf("%c", &c);*///这里会卡死直接这样写,因为他会等待这个玩家输入
	//解决方法就是判断玩家到底有没有按键按下去,写一个if语句
}

void checkover() {
	if (heroBoold <= 0) {
		loadimage(0, "ret/kunkunjie.png");
		FlushBatchDraw();
		mciSendString("stop ret/bk.mp3", 0, 0, 0);
		system("pause");

		//暂停之后刷新游戏角色数据
		heroBoold = 100;
		score = 0;
		mciSendString("play ret/bk.mp3 repeat", 0, 0, 0);
	}
}

void checkpassed() {
	for (int i = 0; i < OBSTACLE_CUONT; i++) {
		if (obstacles[i].exist &&
			obstacles[i].passed == false &&
			obstacles[i].x + obstacleImags[obstacles[i].type][0].getwidth()) {
			score++;
			obstacles[i].passed = true;
			printf("%d", score);
		}
	}
}

void updatezhang() 
{
	//渲染奶龙
	//if (torToiseExist) {
	//	putimage(torToiseX, torToiseY, &imgTortoise);
	//}
	for (int i = 0; i < OBSTACLE_CUONT;i++) {
		if (obstacles[i].exist) {
			//我们要取走第几行呢?我们可以利用我们的枚举类型,这个是对应我们的图片的行数(type对应着枚举->行数)
			putimage(obstacles[i].x, obstacles[i].y, &obstacleImags[obstacles[i].type][obstacles[i].imgIndex]);
		}
	}

}
void updateHero() {
	//这个奔跑和跳跃这个可以实现,但是下蹲不行了,所以需要加一个判断
	if (!heroDown) {
		putimage(heroX, heroY, &imgHero[heroIndex]);
	}
	else {
		int y = 395 - 15;
		putimage(heroX, y, &imgHeroDown[heroIndex]);
	}

}

void updateBoold() {
	drawBloodBar(10, 10, 200, 10, 2, BLUE, DARKGRAY, RED, heroBoold / 100.0);
}

void updateScore() {
	//ASCLL码值 ‘5’-‘0’=5
	char str[8];
	sprintf(str, "%d", score);
	int x = 20;
	int y = 25;
	for (int i = 0; str[i] != 0; i++) {
		int num = str[i] - '0';
		putimage(x, y, &imgSZ[num]);
		x += imgSZ[num].getwidth();
	}
}

int main() {

	init();

	//显示菜单
	loadimage(0, "ret/kunkunkai.png");
	system("pause");

	int timer = 0;
	while (1) {
		/*由于根据游戏设计,这里是要循环多次才可以跳好
		 所以我们这里需要设计一个按键来执行立马跳跃,就
		 不用循环多次了(按键跳跃)
		*/
		keyEvent();
		//这个的好处就是可以随时接受按键接受
		timer += getDelay();  //升级版sleep
		if (timer > 30) {
			timer = 0;
			update = true;//这里是sleep的升级版本,没进过30秒钟刷新一次,然后进入游戏,在30秒的过程中,这个画面在逐帧加载,这样就可以持续进行画面显示
			//没有这个,程序就没有逐帧的慢速显示,就会很快很快
		}
		if (update) {
			update = false;
			BeginBatchDraw();
			updateBg();
			//这个里面不要直接放图片,因为我们后续还有撞出窗外的操作要实现
			//所以不要直接放putimage()在这里
			//由于玩家需要下蹲则这个代码不行了
			/*putimage(heroX, heroY, &imgHero[heroIndex]);*/
			updateHero();
			updateBoold();
			updatezhang();
			updateScore();
			EndBatchDraw();
			//这个run是改变这个图片下一次所在的位置的
			checkover();
			checkpassed();
			run();
		}

		//这个休眠是让他暂停一会,要不然太快了显示的是一个快影子
		//休眠的话,如果在30毫秒钟之内按按键的话是不会响应得,所以这个sleep在这个小游戏里面没有什么实质的影响,但是在大型游戏里面是有影响的
		/*Sleep(30);*///所以我们一般不用sleep

		
	}//定义一个死循环不断的对图片进行滚动
	
	//system("pause");//用于暂停观察
	//int system(const char* 命令);
	return 0;
}

总结

接下来我们的项目就基本结束了,打包安装需要安装插件,感兴趣的可以取收一下微软本地的自带插件,可以在vs管理扩展包下载,这里的源码你直接复制会报错,你得自己学习调节属性和安装插件还有文件的学习,因为天底下哪有免费的午餐

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2266063.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设计模式详解(建造者模式)

1、简述 建造者模式&#xff08;Builder Pattern&#xff09;是一种创建型设计模式&#xff0c;它通过将对象的构造过程与表示分离&#xff0c;使得相同的构造过程可以创建不同的表示。建造者模式尤其适用于创建复杂对象的场景。 2、什么是建造者模式&#xff1f; 建造者模式…

【Git 常用操作:pull push】

Git 基本概念 Git 是一个先进的开源的分布式版本控制系统&#xff0c;常用于管理工作内容、项目代码等功能。 Git 工作流程 图片来源&#xff1a;https://www.runoob.com/git/git-basic-operations.html 说明&#xff1a; workspace&#xff1a;工作区staging area&#xff…

个人笔记:ORM数据库框架EFCore使用示例,运行通过,附源码

个人笔记&#xff1a;ORM数据库框架EFCore使用示例&#xff0c;运行通过&#xff0c;附源码 0.新建项目1. 设置环境1.1. 添加 NuGet 包1.2. 创建模型类 2. 创建上下文类3. 创建数据库和表3.1. 启用迁移3.2. 更新数据库 4. 插入数据5. 查询数据6. 更新数据7. 删除数据8. 完整示例…

IndexOf Apache Web For Liunx索引服务器部署及应用

Apache HTTP Server 是一款广泛使用的开源网页服务器软件,它支持多种协议,包括 HTTP、HTTPS、FTP 等 IndexOf 功能通常指的是在一个目录中自动生成一个索引页面的能力,这个页面会列出该目录下所有的文件和子目录。比如网上经常看到的下图展现的效果,那么接下来我们就讲一下…

Ubuntu 24.04.1 LTS快速源码安装postgresql15

虽然在Ubuntu中我们可以通过apt直接获取postgresql安装&#xff0c;但有些时候&#xff0c;为了自行配置postgresql安装路径和部分组件参数&#xff0c;我们需要源码安装postgresql。今天我们就通过源码编译postgresql15源码进行安装。 一、获取安装包 我们登录postgresql官网…

数据结构之栈,队列,树

目录 一.栈 1.栈的概念及结构 2.栈的实现 3.实现讲解 1.初始化栈 2.销毁栈 3.压栈 4.出栈 5.返回栈顶元素 6.返回栈内元素个数 7.判断栈内是否为空 二.队列 1.队列的概念及结构 2.队列的实现 3.实现讲解 1.初始化队列 2.销毁队列 3.单个成员入队列 4.单个成员…

Git基本操作快速入门(30min)

Git基本操作快速入门&#xff08;30min&#xff09; 文章目录 Git基本操作快速入门&#xff08;30min&#xff09;1. 建立本地仓库2. 本地仓库链接到远端仓库3. 将本地仓库推送到远端4. Git常用命令 作为一名程序员&#xff0c;使用Github来进行代码的版本管理是必修课&#xf…

汽车IVI中控开发入门及进阶(42):OpenVG

概览: OpenVG是一个无版权、跨平台的API,它为高级用户界面和矢量图形库(如SVG)提供了一个低级硬件加速接口。OpenVG主要针对需要便携式加速高质量矢量图形以获得引人注目的用户界面和文本的消费电子产品、手持设备、可穿戴设备和汽车设备,同时使硬件加速能够在非常低的功…

[1111].集成开发工具Pycharm安装与使用

所有博客大纲 后端学习大纲 Python大纲 1.下载&#xff1a; 官方下载地址 2.安装&#xff1a; 1.双击exe文件&#xff0c;然后下一步选择安装目录 2.选择桌面快捷方式及安装&#xff1a; 3.安装完成 3.启动&#xff1a; 4.设置&#xff1a; 4.1.设置运行时环境&#xff1a;…

Windows11 安装 Ubuntu-20.04,同时安装配置 zsh shell,配置 git 别名(alias),大大提高开发效率

背景&#xff1a;家里配置了一台 Windows 电脑&#xff0c;有时候需要用到 vscode 开发测试一些代码&#xff0c;在使用过程中发现原生 windows 敲代码不是很友好&#xff0c;于是想到配置 wsl&#xff0c;安装 Ubuntu&#xff0c;并安装配置 zsh shell&#xff0c;同时配置 gi…

面试场景题系列:设计限流器

首先看看使用API限流器的好处。 •预防由拒绝服务攻击(Denial of Service&#xff0c;DoS)引起的资源耗尽问题。大型科技公司发布的所有API几乎都强制执行某种形式的限流操作。例如&#xff0c;推特限制每个用户每3小时最多发300条推文。谷歌文档API的默认限制是每个用户每60秒…

Node.js 工具:在 Windows 11 中配置 Node.js 的详细步骤

一、概述 记录时间 [2024-12-25] 本文讲述如何在 Windows 11 中进行 Node.js 工具的安装和配置。 以下是详细的步骤和说明。 二、安装 Node.js 1. 官网下载 通过官网&#xff0c;下载 Node.js&#xff0c;上面有好几种下载方式&#xff0c;文中下载的是 zip 压缩包。 如图&…

DevOps实战:用Kubernetes和Argo打造自动化CI/CD流程(2)

DevOps实战&#xff1a;用Kubernetes和Argo打造自动化CI/CD流程&#xff08;2&#xff09; 背景 架构图 正片开始之前&#xff0c;请一定先熟悉上面的架构图&#xff0c;跟着我的步骤&#xff0c;一步一步执行成功&#xff0c;相信后续根据自己特定的需求定制CI/CD。 需求 …

【Java 学习】详细讲解---包和导包、Scanner类、输入源

1. 包 1.1 什么是包&#xff1f; 举个例子&#xff0c;你和你的同学有不同的家庭&#xff0c;你们都有自己的爸爸妈妈&#xff0c;都有自己的家。在自己的家中你们可以按照自己爱好摆放东西&#xff0c;都互不干扰。但是&#xff0c;假如你们的家都在一起&#xff0c;你们就不…

EasyExcel停更,FastExcel接力

11月6日消息&#xff0c;阿里巴巴旗下的Java Excel工具库EasyExcel近日宣布&#xff0c;将停止更新&#xff0c;未来将逐步进入维护模式&#xff0c;将继续修复Bug&#xff0c;但不再主动新增功能。 EasyExcel以其快速、简洁和解决大文件内存溢出的能力而著称&#xff0c;官方…

Python PDF批量加密工具

Python PDF批量加密工具 1.简介&#xff1a; ‌一个Python写的PDF批量加密工具。PDF批量加密‌是一种保护PDF文件安全性的方法&#xff0c;通过为多个PDF文件设置相同的密码&#xff0c;防止未经授权的用户访问这些文件。批量加密可以大大提高工作效率&#xff0c;特别是在处…

项目2路由交换

背景 某学校为满足日常教学生活需求&#xff0c;推动数字校园的建设&#xff0c;学校有办公楼和学生宿舍楼和服务器集群三块区域&#xff0c;请合理规划IP地址和VLAN&#xff0c;实现企业内部能够互联互通现要求外网能通过公网地址访问服务器集群&#xff0c;学生和老师能正常…

知识梳理笔记--Kerberos 协议

Kerberos 协议概述 Kerberos 是一种计算机网络认证协议&#xff0c;旨在为不安全的网络提供强认证服务。它通过中心化的身份验证系统&#xff08;即 Key Distribution Center&#xff0c;KDC&#xff09;来确保通信双方的身份验证和数据加密。Kerberos 协议主要用于确保计算机系…

9个用于测试自动化的最佳AI测试工具(2024)

选择一款优质的基于生成式AI人工智能的测试工具能够确保测试过程的准确性和效率&#xff0c;从而加速整个软件测试周期。相反&#xff0c;设计不佳的测试工具可能无法发现错误&#xff0c;并可能存在安全问题。它们可能产生误报或漏报&#xff0c;误导开发与测试团队&#xff0…

uni-app 跨端开发精美开源UI框架推荐

&#x1f380;&#x1f380;&#x1f380;uni-app 跨端开发系列 &#x1f380;&#x1f380;&#x1f380; 一、uni-app 组成和跨端原理 二、uni-app 各端差异注意事项 三、uni-app 离线本地存储方案 四、uni-app UI库、框架、组件选型指南 五、uni-app 蓝牙开发 六、uni-app …