当前位置: 首页 > article >正文

跨年烟花C++代码

嘿,朋友们!今天来给大家讲讲一段挺有意思的C++代码呀,这段代码主要是用来实现一个烟花效果展示的程序哦,下面咱们一点点来看哈。

效果

请添加图片描述

1. 开头包含的那些头文件

#include <graphics.h>
#include <conio.h>
#include <math.h>
#include <time.h>
#include <stdio.h>
#include<string>
#include <mmsystem.h>
#pragma comment ( lib, "Winmm.lib" )

这里面包含了好多头文件呢。像 <graphics.h> 是和图形绘制相关的,能帮咱们在屏幕上画出各种图形呀;<conio.h> 可以用来处理控制台的输入输出相关操作;<math.h> 就提供了像三角函数这些数学运算的功能;<time.h> 能获取时间相关信息;<stdio.h> 用于标准输入输出操作;<string> 是处理字符串的;而 <mmsystem.h> 以及后面关联的 Winmm.lib 呢,是和多媒体操作有关的,比如播放声音之类的操作都会用到哦。

2. 定义的一些常量和结构体

#define PI 3.14
#define NUM 13

这里定义了 PI 就是咱们数学里常用的圆周率啦,值设成了 3.14 哦。还有 NUM 定义成了 13,它在后面表示烟花或者烟花弹的数量相关的意思呢。

然后有两个结构体哦:

struct Fire
{
	int r;					// 当前爆炸半径
	int max_r;				// 爆炸中心距离边缘最大半径
	int x, y;				// 爆炸中心在窗口的坐标
	int cent2LeftTopX, cent2LeftTopY;		// 爆炸中心相对图片左上角的坐标
	int width, height;		// 图片的宽高
	int pix[240][240];		// 储存图片像素点

	bool show;				// 是否绽放
	bool draw;				// 开始输出像素点
	DWORD t1, t2, dt;		// 绽放速度
}fires[NUM];
  • Fire 结构体是用来描述烟花的相关属性的,像 r 表示当前爆炸半径呀,max_r 就是爆炸中心距离边缘最大半径,xy 是爆炸中心在窗口的坐标位置,还有一些是和图片相关的属性,比如图片的宽高、储存图片像素点的数组呀,另外还有一些像 show 用来标记是否绽放、draw 标记是不是开始输出像素点,再有几个时间相关的变量用来控制绽放速度呢。
struct Bullet
{
	int x, y;				// 烟花弹的当前坐标
	int topX, topY;				// 最高点坐标------将赋值给 FIRE 里面的 x, y
	int height;				// 烟花高度
	bool shoot;				// 是否可以发射

	DWORD t1, t2, dt;		// 发射速度
	IMAGE img[2];			// 储存花弹一亮一暗图片
	unsigned char n : 1;	// 图片下标 n++
}bullets[NUM];
  • Bullet 结构体是描述烟花弹的情况哦,像 xy 是烟花弹当前坐标,topXtopY 是最高点坐标,height 是烟花高度,shoot 标记是否可以发射,同样也有时间相关变量控制发射速度,还有个 img 数组用来储存花弹一亮一暗的图片,以及一个 n 用来作为图片下标的哦。

3. 各种函数的作用

3.1 initFire 函数
// 初始化指定的烟花和烟花弹
void initFire(int i)
{
	// 分别为:烟花中心到图片边缘的最远距离、烟花中心到图片左上角的距离 (x、y) 两个分量
	int r[13] = { 120, 120, 155, 123, 130, 147, 138, 138, 130, 135, 140, 132, 155 };
	int x[13] = { 120, 120, 110, 117, 110, 93, 102, 102, 110, 105, 100, 108, 110 };
	int y[13] = { 120, 120, 85, 118, 120, 103, 105, 110, 110, 120, 120, 104, 85 };

	/**** 初始化烟花 *****/
	fires[i].x = 0;				// 烟花中心坐标
	fires[i].y = 0;
	fires[i].width = 240;				// 图片宽
	fires[i].height = 240;				// 图片高
	fires[i].max_r = r[i];				// 最大半径
	fires[i].cent2LeftTopX = x[i];				// 中心距左上角距离
	fires[i].cent2LeftTopY = y[i];
	fires[i].show = false;			// 是否绽放
	fires[i].dt = 5;				// 绽放时间间隔
	fires[i].t1 = timeGetTime();
	fires[i].r = 0;				// 从 0 开始绽放
	fires[i].draw = false;

	/**** 初始化烟花弹 *****/
	//timeGetTime 该时间为从系统开启算起所经过的时间,单位:毫秒
	bullets[i].t1 = timeGetTime();
	bullets[i].dt = rand() % 10;		// 发射速度时间间隔
	bullets[i].n = 0;				// 烟花弹闪烁图片下标
	bullets[i].shoot = false;			// 是否发射
}

这个函数就是用来初始化指定的烟花和烟花弹啦。它给烟花和烟花弹的各个属性赋值呀,比如给烟花的坐标先初始化为 0,设置图片宽高,最大半径等,还设置了绽放相关的时间间隔等属性;对于烟花弹呢,也设置了发射速度时间间隔,初始化图片下标,标记还没发射这些操作哦。

3.2 loadFireImages 函数
// 加载图片
void loadFireImages()
{
	/**** 储存烟花的像素点颜色 ****/
	IMAGE fm, gm;
	loadimage(&fm, "fire/flower.jpg");

	for (int i = 0; i < 13; i++)
	{
		SetWorkingImage(&fm);
		getimage(&gm, i * 240, 0, 240, 240);

		SetWorkingImage(&gm);
		for (int a = 0; a < 240; a++)
			for (int b = 0; b < 240; b++)
				fires[i].pix[a][b] = getpixel(a, b);
	}

	/**** 加载烟花弹 ************/
	IMAGE sm;
	loadimage(&sm, "fire/shoot.jpg");

	for (int i = 0; i < 13; i++)
	{
		SetWorkingImage(&sm);
		int n = rand() % 5; //0..4

		getimage(&bullets[i].img[0], n * 20, 0, 20, 50);			// 暗
		getimage(&bullets[i].img[1], (n + 5) * 20, 0, 20, 50);		// 亮
	}

	//设置绘图设备为默认绘图窗口,就是当前游戏窗口
	SetWorkingImage();		// 设置回绘图窗口
}

这个函数主要干两件大事哦。一是储存烟花的像素点颜色,它会先加载一张烟花的图片,然后通过循环把这张图片分割成一个个小的图片(因为有13个烟花嘛),再把每个小图片里的像素点颜色信息存到对应的烟花结构体里的数组中呢。另一件事就是加载烟花弹的图片啦,它会随机选一部分图片区域(0到4的范围里选),获取一亮一暗两张不同状态的图片存到烟花弹结构体的 img 数组里哦,最后还会把绘图设备设置回默认绘图窗口呢。

3.3 drawFire 函数
void drawFire(int i) {
	if (!fires[i].draw) {
		return;
	}

	// 弧度 PI 3.14  2PI 6.28  360度
	for (double a = 0; a <= 6.28; a += 0.01)  //0-2PI 弧度
	{
		//三角函数
		int x1 = (int)(fires[i].cent2LeftTopX + fires[i].r * cos(a));	// 相对于图片左上角的坐标
		int y1 = (int)(fires[i].cent2LeftTopY - fires[i].r * sin(a));   // 方向和easyx的Y坐标相反

		if (x1 > 0 && x1 < fires[i].width && y1 > 0 && y1 < fires[i].height)	// 只输出图片内的像素点
		{
			int b = fires[i].pix[x1][y1] & 0xff;  //得到三原色的最低字节(B)
			int g = (fires[i].pix[x1][y1] >> 8) & 0xff; //第2个字节
			int r = (fires[i].pix[x1][y1] >> 16);

			// 烟花像素点在窗口上的坐标
			int xx = (int)(fires[i].x + fires[i].r * cos(a));
			int yy = (int)(fires[i].y - fires[i].r * sin(a));

			// 较暗的像素点不输出、防止越界
			//二维数组  当成 一位数组使用的案例 
			//颜色值接近黑色的不输出。
			// 看电影  5排第6个座位: 5*30+6
			if (r > 0x20 && g > 0x20 && b > 0x20 && xx > 0 && xx < 1200 && yy > 0 && yy < 800)
				pMem[yy * 1200 + xx] = BGR(fires[i].pix[x1][y1]);	// 显存操作绘制烟花
		}
	}
	fires[i].draw = false;
}

这个函数就是用来真正把烟花画出来的哦。不过它得先判断这个烟花是不是到了可以画的状态(也就是 draw 属性是不是 true),如果不是就直接返回啦。然后它通过一个循环,利用三角函数来算出烟花每个像素点相对图片左上角的坐标,再根据这个算出在窗口上的坐标,还会判断这个像素点是不是在图片范围内呀,以及颜色是不是太暗(接近黑色就不画出来),要是符合条件,就通过操作显存把这个像素点画到窗口上,最后把这个烟花的 draw 属性再设为 false 哦。

3.4 testFire 函数
void testFire() {
	int n = 5;

	bullets[n].x = 600;
	bullets[n].y = 600;
	bullets[n].topX = 600;
	bullets[n].topY = 200;

	// 绘制烟花的初始状态(即:在起始位置绘制烟花)
	putimage(bullets[n].x, bullets[n].y, &bullets[n].img[bullets[n].n], SRCINVERT);

	while (bullets[n].y > bullets[n].topY) {
		// 擦除
		putimage(bullets[n].x, bullets[n].y, &bullets[n].img[bullets[n].n], SRCINVERT);
		bullets[n].y -= 5;
		// 绘制
		putimage(bullets[n].x, bullets[n].y, &bullets[n].img[bullets[n].n], SRCINVERT);
		Sleep(50);
	}

	// 先擦除烟花弹
	putimage(bullets[n].x, bullets[n].y, &bullets[n].img[bullets[n].n], SRCINVERT);

	fires[n].show = true;
	fires[n].x = bullets[n].x + 10;
	fires[n].y = bullets[n].y;
	while (fires[n].r <= fires[n].max_r) {
		fires[n].draw = true;
		drawFire(n);
		fires[n].r++;
		Sleep(10);
	}
}

这是一个测试烟花效果的函数哦。它先设定了一个烟花弹的初始坐标、最高点坐标这些信息,然后就开始模拟烟花弹升空的过程啦,一边擦除原来位置的图像,一边更新位置重新绘制,等烟花弹到达最高点后,就把烟花的相关属性设置好,开始让烟花绽放,也是通过循环不断更新烟花半径然后画出来哦。

3.5 chose 函数
void chose(DWORD t1) //t1位为上一次点烟花弹的时间
{
	DWORD t2 = timeGetTime();

	if (t2 - t1 > 100) // 100ms点一次
	{
		int n = rand() % 30; //取摸的数字越大,烟花发射频率越慢,因为<13的概率就越低

		if (n < 13 && bullets[n].shoot == false && fires[n].show == false)
		{
			/**** 重置烟花弹,预备发射 *****/
			bullets[n].x = rand() % 1200;
			bullets[n].y = rand() % 100 + 600; // 600-699
			bullets[n].topX = bullets[n].x;
			bullets[n].topY = rand() % 400; // 0.399
			bullets[n].height = bullets[n].y - bullets[n].topY;
			bullets[n].shoot = true;

			// 绘制烟花的初始状态(即:在起始位置绘制烟花)
			putimage(bullets[n].x, bullets[n].y, &bullets[n].img[bullets[n].n], SRCINVERT);

			/**** 播放每个烟花弹的声音 *****/
			char cmd[50];
			sprintf_s(cmd, "play s%d", n);
			mciSendString(cmd, 0, 0, 0);
		}
		t1 = t2;
	}
}

这个函数是根据时间来决定要不要发射烟花弹哦。它先获取当前时间,然后看看距离上一次点烟花弹的时间有没有超过 100 毫秒,如果超过了,就随机选一个编号,如果这个编号对应的烟花弹没发射而且对应的烟花也没在绽放,那就重置这个烟花弹的一些属性,比如坐标呀、高度这些,还会绘制烟花弹的初始状态,并且播放这个烟花弹对应的声音呢,最后更新上一次点烟花弹的时间哦。

3.6 init 函数
void init() {
	// 创建窗口
	initgraph(1200, 800);

	// 播放背景音乐
	mciSendString("play fire/ring.mp3 repeat", 0, 0, 0);

	for (int i = 0; i < NUM; i++) {	// 初始化烟花和烟花弹
		initFire(i);
	}

	loadFireImages();

	// 这个函数用于获取绘图设备的显示缓冲区指针。
	pMem = GetImageBuffer();		// 获取窗口显存指针


	// 打开音效并设置别名
	char cmd[128];
	for (int i = 0; i < 13; i++) {
		sprintf_s(cmd, sizeof(cmd), "open fire/shoot.mp3 alias s%d", i);
		mciSendString(cmd, 0, 0, 0); // 打开13次

		sprintf_s(cmd, sizeof(cmd), "open fire/bomb.wav alias f%d", i);
		mciSendString(cmd, 0, 0, 0); // 打开13次
	}

	loadimage(&head, "fire/head.png", 400, 300, true);
}

这个可是整个程序初始化的大管家函数呀。它先创建了一个 1200×800 大小的窗口,然后播放背景音乐。接着会循环调用 initFire 函数去初始化所有的烟花和烟花弹,再调用 loadFireImages 函数加载图片,获取绘图设备的显示缓冲区指针,还会打开好多音效文件并且设置别名,最后还会加载一个头的图片哦(虽然暂时不知道具体用在哪,不过也是初始化的一部分啦)。

3.7 clearImage 函数
void clearImage() {
	for (int i = 0; i < 2000; i++)
	{
		int px1 = rand() % 1200; // 0..1199
		int py1 = rand() % 800;  // 0.799

		pMem[py1 * 1200 + px1] = BLACK;
		pMem[py1 * 1200 + px1 + 1] = BLACK;	// 对显存赋值擦出像素点		
	}
}

这个函数就是用来随机擦除一些像素点的哦,通过循环随机生成坐标,然后把对应的显存位置设置成黑色,这样就相当于擦除了一些像素点啦,让画面有那种随机的感觉哦。

3.8 shoot 函数
void shoot() {
	for (int i = 0; i < 13; i++) {
		bullets[i].t2 = timeGetTime();

		if (bullets[i].t2 - bullets[i].t1 > bullets[i].dt && bullets[i].shoot == true) {
			// 擦除
			putimage(bullets[i].x, bullets[i].y, &bullets[i].img[bullets[i].n], SRCINVERT);

			// 更新烟花弹的位置和图片状态
			if (bullets[i].y > bullets[i].topY) {
				bullets[i].n++;
				bullets[i].y -= 5;
			}

			// 在新位置上,重新绘制
			putimage(bullets[i].x, bullets[i].y, &bullets[i].img[bullets[i].n], SRCINVERT);

			/**** 上升到高度的 3 / 4,减速 *****/
			// 即距离最高点还有1/4的时候,减速
			if ((bullets[i].y - bullets[i].topY) * 4 < bullets[i].height)
				bullets[i].dt = rand() % 4 + 10; // 10..13

			/**** 上升到最大高度 *****/
			if (bullets[i].y <= bullets[i].topY) {
				// 擦除烟花弹
				putimage(bullets[i].x, bullets[i].y, &bullets[i].img[bullets[i].n], SRCINVERT);

				// 准备渲染“烟花”
				fires[i].x = bullets[i].topX + 10;		// 在烟花弹中间爆炸
				fires[i].y = bullets[i].topY;			// 在最高点绽放
				fires[i].show = true;					// 开始绽放
				bullets[i].shoot = false;				// 停止发射

				 // 关闭点烟花的音效,并播放爆炸的音效, 并重新打开点烟花的音效
				char c1[64], c2[64];
				sprintf_s(c1, "close s%d", i);
				sprintf_s(c2, "play f%d", i);
				mciSendString(c1, 0, 0, 0);
				mciSendString(c2, 0, 0, 0);

				sprintf_s(c1, sizeof(c1), "open fire/shoot.mp3 alias s%d", i);
				mciSendString(c1, 0, 0, 0);
			}

			// 更新烟花弹的时间
			bullets[i].t1 = bullets[i].t2;
		}
	}
}

这个函数是处理烟花弹升空过程的哦。它会遍历所有的烟花弹,先获取当前时间看看是不是到了该更新烟花弹位置的时候啦,如果到了,就先擦除原来位置的烟花弹图像,然后根据情况更新烟花弹的位置、图片状态(比如切换一亮一暗的图片呀),要是快到最高点了还会减速呢,等烟花弹到达最大高度后,就擦除烟花弹,准备让烟花开始绽放啦,还会处理相关的音效,关闭点烟花的音效,播放爆炸的音效,然后再重新打开点烟花的音效哦。

3.9 showFire 函数
void showFire() {
	// 烟花个阶段绽放时间间隔,制作变速绽放效果
	// 为什么数组大小定义为16?
	// 目前烟花的最大半径是155,准备以半径/10可刻度,不同的半径,绽放速度不同
	// 半径越大,绽放越慢
	//              10 20 30 40 50 
	int drt[16] = { 5, 5, 5, 5, 5, 6, 25, 25, 25, 25, 55, 55, 55, 55, 55 };

	for (int i = 0; i < NUM; i++) {
		fires[i].t2 = timeGetTime();

		// 增加爆炸半径,绽放烟花,增加时间间隔做变速效果
		if (fires[i].t2 - fires[i].t1 > fires[i].dt
			&& fires[i].show == true) {
			// 更新烟花半径
			if (fires[i].r < fires[i].max_r) {
				fires[i].r++;
				fires[i].dt = drt[fires[i].r / 10];
				fires[i].draw = true;
			}

			// 销毁烟花,并重新初始化该序号的飞弹和烟花
			if (fires[i].r >= fires[i].max_r) {
				fires[i].draw = false;
				initFire(i);

				// 关闭爆炸音效,并重新打开爆炸音效
				char cmd[64];
				sprintf_s(cmd, "close f%d", i);
				mciSendString(cmd, 0, 0, 0);

				sprintf_s(cmd, sizeof(cmd), "open fire/bomb.wav alias f%d", i);
				mciSendString(cmd, 0, 0, 0);
			}

			// 更新烟花的时间
			fires[i].t1 = fires[i].t2;
		}

		// 绘制指定的烟花
		drawFire(i);
	}
}

这个函数是用来控制烟花绽放的过程哦。它有个数组 drt 用来控制不同半径下烟花绽放的时间间隔,实现变速绽放效果呢。它会遍历所有的烟花,看看是不是到了该更新绽放状态的时候啦,如果到了,就更新烟花半径,要是半径还没到最大,就继续增加半径并且更新绽放时间间隔,要是半径达到最大了,就销毁这个烟花,重新初始化这个序号对应的烟花弹和烟花,同时也会处理相关的音效,关闭爆炸音效,再重新打开哦,最后还会调用 drawFire 函数把烟花画出来呢。

3.10 heartFire 函数
void heartFire(DWORD& st1)
{
	DWORD st2 = timeGetTime();

	static bool flag = false;
	static DWORD startTime = 0;

	if (flag && st2 - startTime > 3500) {
		putimage(430, 250, &head);
		flag = false;
	}

	if (st2 - st1 > 20000)		// 20秒
	{
		flag = true;
		startTime = timeGetTime();
		// 先擦除正在发送的烟花弹
		for (int i = 0; i < 13; i++) {
			if (bullets[i].shoot)
				putimage(bullets[i].x, bullets[i].y, &bullets[i].img[bullets[i].n], SRCINVERT);
		}


		// 心形坐标
		int x[13] = { 600, 750, 910, 1000, 950, 750, 600, 450, 250, 150, 250, 410, 600 };
		int y[13] = { 650, 530, 400, 220, 50, 40, 200, 40, 50, 220, 400, 530, 650 };
		for (int i = 0; i < NUM; i++)
		{
			bullets[i].x = x[i];
			bullets[i].y = y[i] + 750;  //每个烟花弹的发射距离都是750,确保同时爆炸
			bullets[i].topX = x[i];
			bullets[i].topY = y[i];


			bullets[i].height = bullets[i].y - bullets[i].topY;
			bullets[i].shoot = true;
			bullets[i].dt = 7;
			// 显示烟花弹
			putimage(bullets[i].x, bullets[i].y, &bullets[i].img[bullets[i].n], SRCINVERT);

			/**** 设置烟花参数 ***/
			fires[i].x = bullets[i].x + 10;
			fires[i].y = bullets[i].topY;
			fires[i].show = false;
			fires[i].r = 0;


		}
		st1 = st2;
	}
}

这个函数挺有意思的哦,它有时间相关的判断呢。如果满足一定时间条件(比如超过 20 秒呀),就会做一些操作,先是把一个头的图片显示出来一会儿(通过标记和时间判断控制显示时长哦),然后还会擦除正在发射的烟花弹,接着按照心形的坐标来设置好多烟花弹的发射位置、高度这些属性,让它们准备发射,同时设置对应的烟花的一些初始属性哦,感觉是要搞个心形烟花秀的样子呢。

3.11 daoJiShi 函数
void daoJiShi() {
	IMAGE img[6];
	char name[64];
	for (int i = 0; i < 6; i++) {
		sprintf(name, "fire/%d.png", i);
		loadimage(&img[i], name);
	}

	for (int i = 5; i >= 0; i--) {
		BeginBatchDraw();
		cleardevice();
		putimage((1200 - img[i].getwidth()) / 2, (800 - img[i].getheight()) / 2, &img[i]);
		EndBatchDraw();
		Sleep(1000);
	}
	cleardevice();
}

这个函数就是用来做倒计时显示的哦。它先加载了 6 张不同的图片(文件名是按数字命名的呢),然后从最后一张开始,一张张地在屏幕中间显示出来,每次显示间隔 1 秒,显示完了就把屏幕清空,估计是为了在正式放烟花前搞个倒计时的小效果呀。

4. main 函数

int main(void) {
	init();
	daoJiShi();

	DWORD t1 = timeGetTime();	// 筛选烟花计时
	DWORD ht1 = timeGetTime();  // 播放花样计时

	BeginBatchDraw();

	// kbhit()判断有没有按键输入
	while(1)//while (!_kbhit())
	{
		// 帧等待
		Sleep(10);

		clearImage();

		chose(t1); //点火
		shoot(); //升空
		showFire();
		heartFire(ht1);

		FlushBatchDraw();	// 显示前面的所有绘图操作

		// 烟花
		// to do...
	}

	return 0;
}

这个就是整个程序的入口啦,就像一扇大门一样哦。它先调用 init 函数进行初始化,再调用 daoJiShi 函数做倒计时显示。然后获取两个时间相关的变量,一个用来控制筛选烟花的计时,一个用来控制播放花样的计时哦。接着进入一个大的循环,在循环里先等 10 毫秒(相当于控制一下帧率呀),然后调用 clearImage 函数擦除一些像素点,再依次调用 choseshootshowFireheartFire 这些函数来分别处理点火、烟花弹升空、烟花绽放、特殊的心形烟花相关的操作,最后调用 FlushBatchDraw 把前面做的绘图操作显示出来,这样整个烟花秀就在屏幕上展示出来啦,这个循环会一直进行,直到有按键输入(虽然代码里现在注释掉了那个判断按键输入的部分,不过按道理加上后可以通过按键来结束程序之类的哦),最后返回 0 表示程序正常结束啦。

总之呀,这段代码通过好多函数相互配合,实现了一个很有意思的烟花展示程序,有各种各样的烟花效果,还有声音、倒计时这些好玩的元素呢,是不是挺厉害的呀。

5 . 完整代码

#include <graphics.h>
#include <conio.h>
#include <math.h>
#include <time.h>
#include <stdio.h>
#include<string>
#include <mmsystem.h>
#pragma comment ( lib, "Winmm.lib" )


#define PI 3.14
#define NUM 13

// 指向绘图设备的显示缓冲区。
DWORD* pMem;

IMAGE head;

// 烟花结构
struct Fire
{
	int r;					// 当前爆炸半径
	int max_r;				// 爆炸中心距离边缘最大半径
	int x, y;				// 爆炸中心在窗口的坐标
	int cent2LeftTopX, cent2LeftTopY;		// 爆炸中心相对图片左上角的坐标
	int width, height;		// 图片的宽高
	int pix[240][240];		// 储存图片像素点

	bool show;				// 是否绽放
	bool draw;				// 开始输出像素点
	DWORD t1, t2, dt;		// 绽放速度
}fires[NUM];

// 烟花弹结构
struct Bullet
{
	int x, y;				// 烟花弹的当前坐标
	int topX, topY;				// 最高点坐标------将赋值给 FIRE 里面的 x, y
	int height;				// 烟花高度
	bool shoot;				// 是否可以发射

	DWORD t1, t2, dt;		// 发射速度
	IMAGE img[2];			// 储存花弹一亮一暗图片
	unsigned char n : 1;	// 图片下标 n++
}bullets[NUM];

// 初始化指定的烟花和烟花弹
void initFire(int i)
{
	// 分别为:烟花中心到图片边缘的最远距离、烟花中心到图片左上角的距离 (x、y) 两个分量
	int r[13] = { 120, 120, 155, 123, 130, 147, 138, 138, 130, 135, 140, 132, 155 };
	int x[13] = { 120, 120, 110, 117, 110, 93, 102, 102, 110, 105, 100, 108, 110 };
	int y[13] = { 120, 120, 85, 118, 120, 103, 105, 110, 110, 120, 120, 104, 85 };

	/**** 初始化烟花 *****/
	fires[i].x = 0;				// 烟花中心坐标
	fires[i].y = 0;
	fires[i].width = 240;				// 图片宽
	fires[i].height = 240;				// 图片高
	fires[i].max_r = r[i];				// 最大半径
	fires[i].cent2LeftTopX = x[i];				// 中心距左上角距离
	fires[i].cent2LeftTopY = y[i];
	fires[i].show = false;			// 是否绽放
	fires[i].dt = 5;				// 绽放时间间隔
	fires[i].t1 = timeGetTime();
	fires[i].r = 0;				// 从 0 开始绽放
	fires[i].draw = false;

	/**** 初始化烟花弹 *****/
	//timeGetTime 该时间为从系统开启算起所经过的时间,单位:毫秒
	bullets[i].t1 = timeGetTime();
	bullets[i].dt = rand() % 10;		// 发射速度时间间隔
	bullets[i].n = 0;				// 烟花弹闪烁图片下标
	bullets[i].shoot = false;			// 是否发射
}

// 加载图片
void loadFireImages()
{
	/**** 储存烟花的像素点颜色 ****/
	IMAGE fm, gm;
	loadimage(&fm, "fire/flower.jpg");

	for (int i = 0; i < 13; i++)
	{
		SetWorkingImage(&fm);
		getimage(&gm, i * 240, 0, 240, 240);

		SetWorkingImage(&gm);
		for (int a = 0; a < 240; a++)
			for (int b = 0; b < 240; b++)
				fires[i].pix[a][b] = getpixel(a, b);
	}

	/**** 加载烟花弹 ************/
	IMAGE sm;
	loadimage(&sm, "fire/shoot.jpg");

	for (int i = 0; i < 13; i++)
	{
		SetWorkingImage(&sm);
		int n = rand() % 5; //0..4

		getimage(&bullets[i].img[0], n * 20, 0, 20, 50);			// 暗
		getimage(&bullets[i].img[1], (n + 5) * 20, 0, 20, 50);		// 亮
	}

	//设置绘图设备为默认绘图窗口,就是当前游戏窗口
	SetWorkingImage();		// 设置回绘图窗口
}

void drawFire(int i) {
	if (!fires[i].draw) {
		return;
	}

	// 弧度 PI 3.14  2PI 6.28  360度
	for (double a = 0; a <= 6.28; a += 0.01)  //0-2PI 弧度
	{
		//三角函数
		int x1 = (int)(fires[i].cent2LeftTopX + fires[i].r * cos(a));	// 相对于图片左上角的坐标
		int y1 = (int)(fires[i].cent2LeftTopY - fires[i].r * sin(a));   // 方向和easyx的Y坐标相反

		if (x1 > 0 && x1 < fires[i].width && y1 > 0 && y1 < fires[i].height)	// 只输出图片内的像素点
		{
			int b = fires[i].pix[x1][y1] & 0xff;  //得到三原色的最低字节(B)
			int g = (fires[i].pix[x1][y1] >> 8) & 0xff; //第2个字节
			int r = (fires[i].pix[x1][y1] >> 16);

			// 烟花像素点在窗口上的坐标
			int xx = (int)(fires[i].x + fires[i].r * cos(a));
			int yy = (int)(fires[i].y - fires[i].r * sin(a));

			// 较暗的像素点不输出、防止越界
			//二维数组  当成 一位数组使用的案例 
			//颜色值接近黑色的不输出。
			// 看电影  5排第6个座位: 5*30+6
			if (r > 0x20 && g > 0x20 && b > 0x20 && xx > 0 && xx < 1200 && yy > 0 && yy < 800)
				pMem[yy * 1200 + xx] = BGR(fires[i].pix[x1][y1]);	// 显存操作绘制烟花
		}
	}
	fires[i].draw = false;
}


void testFire() {
	int n = 5;

	bullets[n].x = 600;
	bullets[n].y = 600;
	bullets[n].topX = 600;
	bullets[n].topY = 200;

	// 绘制烟花的初始状态(即:在起始位置绘制烟花)
	putimage(bullets[n].x, bullets[n].y, &bullets[n].img[bullets[n].n], SRCINVERT);

	while (bullets[n].y > bullets[n].topY) {
		// 擦除
		putimage(bullets[n].x, bullets[n].y, &bullets[n].img[bullets[n].n], SRCINVERT);
		bullets[n].y -= 5;
		// 绘制
		putimage(bullets[n].x, bullets[n].y, &bullets[n].img[bullets[n].n], SRCINVERT);
		Sleep(50);
	}

	// 先擦除烟花弹
	putimage(bullets[n].x, bullets[n].y, &bullets[n].img[bullets[n].n], SRCINVERT);

	fires[n].show = true;
	fires[n].x = bullets[n].x + 10;
	fires[n].y = bullets[n].y;
	while (fires[n].r <= fires[n].max_r) {
		fires[n].draw = true;
		drawFire(n);
		fires[n].r++;
		Sleep(10);
	}
}


// C++的引用
void chose(DWORD t1) //t1位为上一次点烟花弹的时间
{
	DWORD t2 = timeGetTime();

	if (t2 - t1 > 100) // 100ms点一次
	{
		int n = rand() % 30; //取摸的数字越大,烟花发射频率越慢,因为<13的概率就越低

		if (n < 13 && bullets[n].shoot == false && fires[n].show == false)
		{
			/**** 重置烟花弹,预备发射 *****/
			bullets[n].x = rand() % 1200;
			bullets[n].y = rand() % 100 + 600; // 600-699
			bullets[n].topX = bullets[n].x;
			bullets[n].topY = rand() % 400; // 0.399
			bullets[n].height = bullets[n].y - bullets[n].topY;
			bullets[n].shoot = true;

			// 绘制烟花的初始状态(即:在起始位置绘制烟花)
			putimage(bullets[n].x, bullets[n].y, &bullets[n].img[bullets[n].n], SRCINVERT);

			/**** 播放每个烟花弹的声音 *****/
			char cmd[50];
			sprintf_s(cmd, "play s%d", n);
			mciSendString(cmd, 0, 0, 0);
		}
		t1 = t2;
	}
}

// 项目初始化
void init() {
	// 创建窗口
	initgraph(1200, 800);

	// 播放背景音乐
	mciSendString("play fire/ring.mp3 repeat", 0, 0, 0);

	for (int i = 0; i < NUM; i++) {	// 初始化烟花和烟花弹
		initFire(i);
	}

	loadFireImages();

	// 这个函数用于获取绘图设备的显示缓冲区指针。
	pMem = GetImageBuffer();		// 获取窗口显存指针


	// 打开音效并设置别名
	char cmd[128];
	for (int i = 0; i < 13; i++) {
		sprintf_s(cmd, sizeof(cmd), "open fire/shoot.mp3 alias s%d", i);
		mciSendString(cmd, 0, 0, 0); // 打开13次

		sprintf_s(cmd, sizeof(cmd), "open fire/bomb.wav alias f%d", i);
		mciSendString(cmd, 0, 0, 0); // 打开13次
	}

	loadimage(&head, "fire/head.png", 400, 300, true);
}

void clearImage() {
	for (int i = 0; i < 2000; i++)
	{
		int px1 = rand() % 1200; // 0..1199
		int py1 = rand() % 800;  // 0.799

		pMem[py1 * 1200 + px1] = BLACK;
		pMem[py1 * 1200 + px1 + 1] = BLACK;	// 对显存赋值擦出像素点		
	}
}

// 烟花弹升空
void shoot() {
	for (int i = 0; i < 13; i++) {
		bullets[i].t2 = timeGetTime();

		if (bullets[i].t2 - bullets[i].t1 > bullets[i].dt && bullets[i].shoot == true) {
			// 擦除
			putimage(bullets[i].x, bullets[i].y, &bullets[i].img[bullets[i].n], SRCINVERT);

			// 更新烟花弹的位置和图片状态
			if (bullets[i].y > bullets[i].topY) {
				bullets[i].n++;
				bullets[i].y -= 5;
			}

			// 在新位置上,重新绘制
			putimage(bullets[i].x, bullets[i].y, &bullets[i].img[bullets[i].n], SRCINVERT);

			/**** 上升到高度的 3 / 4,减速 *****/
			// 即距离最高点还有1/4的时候,减速
			if ((bullets[i].y - bullets[i].topY) * 4 < bullets[i].height)
				bullets[i].dt = rand() % 4 + 10; // 10..13

			/**** 上升到最大高度 *****/
			if (bullets[i].y <= bullets[i].topY) {
				// 擦除烟花弹
				putimage(bullets[i].x, bullets[i].y, &bullets[i].img[bullets[i].n], SRCINVERT);

				// 准备渲染“烟花”
				fires[i].x = bullets[i].topX + 10;		// 在烟花弹中间爆炸
				fires[i].y = bullets[i].topY;			// 在最高点绽放
				fires[i].show = true;					// 开始绽放
				bullets[i].shoot = false;				// 停止发射

				 // 关闭点烟花的音效,并播放爆炸的音效, 并重新打开点烟花的音效
				char c1[64], c2[64];
				sprintf_s(c1, "close s%d", i);
				sprintf_s(c2, "play f%d", i);
				mciSendString(c1, 0, 0, 0);
				mciSendString(c2, 0, 0, 0);

				sprintf_s(c1, sizeof(c1), "open fire/shoot.mp3 alias s%d", i);
				mciSendString(c1, 0, 0, 0);
			}

			// 更新烟花弹的时间
			bullets[i].t1 = bullets[i].t2;
		}
	}
}

// 绽放烟花
void showFire() {
	// 烟花个阶段绽放时间间隔,制作变速绽放效果
	// 为什么数组大小定义为16?
	// 目前烟花的最大半径是155,准备以半径/10可刻度,不同的半径,绽放速度不同
	// 半径越大,绽放越慢
	//              10 20 30 40 50 
	int drt[16] = { 5, 5, 5, 5, 5, 6, 25, 25, 25, 25, 55, 55, 55, 55, 55 };

	for (int i = 0; i < NUM; i++) {
		fires[i].t2 = timeGetTime();

		// 增加爆炸半径,绽放烟花,增加时间间隔做变速效果
		if (fires[i].t2 - fires[i].t1 > fires[i].dt
			&& fires[i].show == true) {
			// 更新烟花半径
			if (fires[i].r < fires[i].max_r) {
				fires[i].r++;
				fires[i].dt = drt[fires[i].r / 10];
				fires[i].draw = true;
			}

			// 销毁烟花,并重新初始化该序号的飞弹和烟花
			if (fires[i].r >= fires[i].max_r) {
				fires[i].draw = false;
				initFire(i);

				// 关闭爆炸音效,并重新打开爆炸音效
				char cmd[64];
				sprintf_s(cmd, "close f%d", i);
				mciSendString(cmd, 0, 0, 0);

				sprintf_s(cmd, sizeof(cmd), "open fire/bomb.wav alias f%d", i);
				mciSendString(cmd, 0, 0, 0);
			}

			// 更新烟花的时间
			fires[i].t1 = fires[i].t2;
		}

		// 绘制指定的烟花
		drawFire(i);
	}
}

void heartFire(DWORD& st1)
{
	DWORD st2 = timeGetTime();

	static bool flag = false;
	static DWORD startTime = 0;

	if (flag && st2 - startTime > 3500) {
		putimage(430, 250, &head);
		flag = false;
	}

	if (st2 - st1 > 20000)		// 20秒
	{
		flag = true;
		startTime = timeGetTime();
		// 先擦除正在发送的烟花弹
		for (int i = 0; i < 13; i++) {
			if (bullets[i].shoot)
				putimage(bullets[i].x, bullets[i].y, &bullets[i].img[bullets[i].n], SRCINVERT);
		}


		// 心形坐标
		int x[13] = { 600, 750, 910, 1000, 950, 750, 600, 450, 250, 150, 250, 410, 600 };
		int y[13] = { 650, 530, 400, 220, 50, 40, 200, 40, 50, 220, 400, 530, 650 };
		for (int i = 0; i < NUM; i++)
		{
			bullets[i].x = x[i];
			bullets[i].y = y[i] + 750;  //每个烟花弹的发射距离都是750,确保同时爆炸
			bullets[i].topX = x[i];
			bullets[i].topY = y[i];


			bullets[i].height = bullets[i].y - bullets[i].topY;
			bullets[i].shoot = true;
			bullets[i].dt = 7;
			// 显示烟花弹
			putimage(bullets[i].x, bullets[i].y, &bullets[i].img[bullets[i].n], SRCINVERT);

			/**** 设置烟花参数 ***/
			fires[i].x = bullets[i].x + 10;
			fires[i].y = bullets[i].topY;
			fires[i].show = false;
			fires[i].r = 0;


		}
		st1 = st2;
	}
}

void daoJiShi() {
	IMAGE img[6];
	char name[64];
	for (int i = 0; i < 6; i++) {
		sprintf(name, "fire/%d.png", i);
		loadimage(&img[i], name);
	}

	for (int i = 5; i >= 0; i--) {
		BeginBatchDraw();
		cleardevice();
		putimage((1200 - img[i].getwidth()) / 2, (800 - img[i].getheight()) / 2, &img[i]);
		EndBatchDraw();
		Sleep(1000);
	}
	cleardevice();
}

int main(void) {
	init();
	daoJiShi();

	DWORD t1 = timeGetTime();	// 筛选烟花计时
	DWORD ht1 = timeGetTime();  // 播放花样计时

	BeginBatchDraw();

	// kbhit()判断有没有按键输入
	while(1)//while (!_kbhit())
	{
		// 帧等待
		Sleep(10);

		clearImage();

		chose(t1); //点火
		shoot(); //升空
		showFire();
		heartFire(ht1);

		FlushBatchDraw();	// 显示前面的所有绘图操作

		// 烟花
		// to do...
	}

	return 0;
}

http://www.kler.cn/a/461239.html

相关文章:

  • Android 模拟器系统镜像选择指南
  • 2024年大型语言模型(LLMs)的发展回顾
  • MATLAB画柱状图
  • Dell服务器升级ubuntu 22.04失败解决
  • [算法] [leetcode-324] 摆动排序 II
  • 某小程序sign签名参数逆向分析
  • INT303 Big Data Analytics 笔记
  • 单元测试学习2.0+修改私有属性
  • 用VSCode+远程拉仓库上传Git仓库方法(进阶版)
  • [算法] [leetcode-70] 爬楼梯
  • 8086汇编(16位汇编)学习笔记06.串操作、流程转移指令
  • 《鸿蒙之光HarmonyOS NEXT原生应用开发入门》简介
  • Synopsys软件基本使用方法
  • deepin系统Docker使用指南:常用命令精讲
  • 建筑机器人崛起 | KMDA-7611助力智能喷涂一体机器人
  • 【数据结构】单向循环链表的使用
  • 01-2023年上半年软件设计师考试java真题解析
  • 小程序 手写tab超出滑动。view超出可以横滑动
  • Kafka高性能设计
  • 手写顺序流程图组件
  • Windows onnxruntime编译openvino
  • 部分开源数据整理
  • win32汇编环境下,对话框程序中生成listview列表控件,点击标题栏自动排序的示例
  • STM32 高级 物联网通讯之LoRa通讯
  • SNIPE-IT详细安装教程(已安装成功)
  • RabbitMQ - 2 ( 21000 字 RabbitMQ 入门级教程 )