跨年烟花C++代码

news2025/1/9 2:40:59

嘿,朋友们!今天来给大家讲讲一段挺有意思的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.coloradmin.cn/o/2273534.html

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

相关文章

Unity 2d描边基于SpriteRender,高性能的描边解决方案

目标 以Unity默认渲染管线为例&#xff0c;打造不需要图片内边距&#xff0c;描边平滑&#xff0c;高性能的描边解决方案 前言 在2d游戏中经常需要给2d对象添加描边&#xff0c;来突出强调2d对象 当你去网上查找2d描边shader&#xff0c;移植到项目里面&#xff0c;大概率会…

自动驾驶相关知识学习笔记

一、概要 因为想知道SIL、HIL是什么仿真工具&#xff0c;故而浏览了自动驾驶相关的知识。 资料来源《自动驾驶——人工智能理论与实践》胡波 林青 陈强 著&#xff1b;出版时间&#xff1a;2023年3月 二、图像的分类、分割与检测任务区别 如图所示&#xff0c;这些更高阶的…

“深入浅出”系列之FFmpeg:(1)音视频开发基础

我的音视频开发大部分内容是跟着雷霄骅大佬学习的&#xff0c;所以笔记也是跟雷老师的博客写的。 一、音视频相关的基础知识 首先播放一个视频文件的流程如下所示&#xff1a; FFmpeg的作用就是将H.264格式的数据转换成YUV格式的数据&#xff0c;然后SDL将YUV显示到电脑屏幕上…

日志服务 SQL 引擎全新升级

作者&#xff1a;戴志勇、顾汉杰&#xff08;执少&#xff09; SQL 作为 SLS 基础功能&#xff0c;每天承载了用户大量日志数据的分析请求&#xff0c;既有小数据量的快速查询&#xff08;如告警、即席查询等&#xff09;&#xff1b;也有上万亿数据规模的报表级分析。SLS 作为…

20250107在WIN10下使用无线ADB连接Andorid7.1.2

connected to 192.168.3.217:5555 adb shell 20250107在WIN10下使用无线ADB连接Andorid7.1.2 2025/1/7 18:12 缘起&#xff1a;公司买了一台6000-7000&#xffe5;的地面站【Andorid7.1.2】&#xff0c;需要通过ifconfig命令来获取其中的网络信息。 虽然系统是VERY非常的陈旧&a…

浙江省自然资源厅:基于“浙里办”的自然资源移动政务服务创新实践——“浙里自然资源”

摘 要&#xff1a;本文基于浙江省自然资源移动政务服务的创新实践&#xff0c;设计和实现“浙里自然资源”应用&#xff0c;依托浙江省省域空间治理数字化平台特有的架构基础&#xff0c;在提升功能性和可用性、加强运营力度、丰富服务内容等方面采取了管理举措和技术创新。通…

使用 Jupyter Notebook:安装与应用指南

文章目录 安装 Jupyter Notebook1. 准备环境2. 安装 Jupyter Notebook3. 启动 Jupyter Notebook4. 选择安装方式&#xff08;可选&#xff09; 二、Jupyter Notebook 的基本功能1. 单元格的类型与运行2. 可视化支持3. 内置魔法命令 三、Jupyter Notebook 的实际应用场景1. 数据…

NeurIPS 2024 | 像素级LLM实现图像视频理解、生成、分割和编辑大统一(昆仑万维等)

Accepted by NeurIPS 2024 文章链接&#xff1a;https://arxiv.org/pdf/2412.19806 项目链接&#xff1a;https://vitron-llm.github.io/ Github链接&#xff1a;https://github.com/SkyworkAI/Vitron 亮点直击 首次提出了一种通用的视觉多模态大语言模型&#xff08;MLLM&…

嵌入式技术之Linux(Ubuntu) 一

一、Linux入门 1.硬件和操作系统以及用户的关系 一个传感器&#xff0c;获得数据后&#xff0c;需要向服务器发送数据。传感器传数据给上位机。 上位机需要一个程序来接收数据&#xff0c;那么这个上位机是什么机器&#xff1f; 我们的笔记本电脑就可以当成上位机。 两个手…

用户界面软件02

基于表单的用户界面 在“基于表单的用户界面”里面&#xff0c;用户开始时选中某个业务处理&#xff08;模块&#xff09;&#xff0c;然后应用程序就使用一系列的表单来引导用户完成整个处理过程。大型机系统上的大部分用户界面都是这样子的。[Cok97]中有更为详细的讨论。 面…

YOLOv8/YOLOv11改进 添加CBAM、GAM、SimAM、EMA、CAA、ECA、CA等多种注意力机制

目录 前言 CBAM GAM SimAM EMA CAA ECA CA 添加方法 YAML文件添加 使用改进训练 前言 本篇文章将为大家介绍Ultralytics/YOLOv8/YOLOv11中常用注意力机制的添加&#xff0c;可以满足一些简单的涨点需求。本文仅写方法&#xff0c;原理不多讲解&#xff0c;需要可跳…

【express-generator】05-路由中间件和错误处理(第一阶段收尾)

一、前言 上篇文章我们介绍了express-generator的请求体解析&#xff0c;重点讲了常用的请求体数据格式&#xff08;JSON/URL 编码的表单数据&#xff09;以及一个FILE文件上传&#xff0c;同时搭配代码示范进行辅助理解。 二、本篇重点 我们继续第一阶段的知识&#xff0c;…

python无需验证码免登录12306抢票 --selenium(2)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 [TOC](python无需验证码免登录12306抢票 --selenium(2)) 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 就在刚刚我抢的票&#xff1a;2025年1月8日…

深度学习驱动的蛋白质设计技术与实践

通过设计特定的蛋白质结构&#xff0c;可以实现预期的生物功能&#xff0c;如催化特定化学反应、识别和结合特定分子、调控生物信号传导等&#xff0c;为生物医学、药物研发、生物技术等领域提供重要工具和解决方案。传统的蛋白质设计方法主要依赖于已知蛋白质结构的同源建模、…

【动态重建】时间高斯分层的长体积视频

标题&#xff1a;Representing Long Volumetric Video with Temporal Gaussian Hierarchy 来源&#xff1a;浙江大学 链接&#xff1a;https://zju3dv.github.io/longvolcap/ 文章目录 摘要一、前言二、主要方法2.1 时间高斯分层2.2 高效渲染2.3 层次结构更新2.4 紧凑的外观模型…

【STM32+CubeMX】 新建一个工程(STM32F407)

相关文章&#xff1a; 【HAL库】 STM32CubeMX 教程 1 --- 下载、安装 目录 第一部分、新建工程 第二部分、工程文件解释 第三部分、编译验证工程 友情约定&#xff1a;本系列的前五篇&#xff0c;为了方便新手玩家熟悉CubeMX、Keil的使用&#xff0c;会详细地截图每一步Cu…

el-date-picker 不响应change事件的解决办法

前言 接到需要把element plus组件的日期时间选择器的input输入框展示隐藏&#xff0c;遇到点击确认按钮change事件不被触发问题&#xff0c;解决办法如下&#xff1a; ①visible-change的回调参考 即根据visible-change的方法里的回调参数进行需要操作 const visibleChange …

api开发如何在代码中使用京东商品详情接口的参数?

选择编程语言和相关工具 以 Python 为例&#xff0c;你可以使用requests库来发送 HTTP 请求获取接口数据。如果是 Java&#xff0c;可以使用OkHttp等库。 Python 示例 假设你已经安装了requests库&#xff0c;以下是一个简单的代码示例来获取和使用京东商品详情接口参数&#…

【docker系列】可视化Docker 管理工具——Portainer

1. 介绍 Portainer是一个可视化的Docker操作界面&#xff0c;提供状态显示面板、应用模板快速部署、容器镜像网络数据卷的基本操作&#xff08;包括上传下载镜像&#xff0c;创建容器等操作&#xff09;、事件日志显示、容器控制台操作、Swarm集群和服务等集中管理和操作、登录…

机器学习基础-大语言模型

目录 大语言模型的基本概念 “大”体现在什么地方&#xff1f; 预训练微调两阶段的基本流程和作用 第一阶段&#xff1a;利用语言模型进行无监督预训练 第二阶段&#xff1a;通过监督微调的模式解决下游任务 BERT模型中MLM和NSP机制基本概念 MLM NSP Prompt学习的基本概…