跨年烟花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就是爆炸中心距离边缘最大半径,x、y是爆炸中心在窗口的坐标位置,还有一些是和图片相关的属性,比如图片的宽高、储存图片像素点的数组呀,另外还有一些像show用来标记是否绽放、draw标记是不是开始输出像素点,再有几个时间相关的变量用来控制绽放速度呢。
struct Bullet
{int x, y; // 烟花弹的当前坐标int topX, topY; // 最高点坐标------将赋值给 FIRE 里面的 x, yint height; // 烟花高度bool shoot; // 是否可以发射DWORD t1, t2, dt; // 发射速度IMAGE img[2]; // 储存花弹一亮一暗图片unsigned char n : 1; // 图片下标 n++
}bullets[NUM];
Bullet结构体是描述烟花弹的情况哦,像x、y是烟花弹当前坐标,topX、topY是最高点坐标,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..4getimage(&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+6if (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-699bullets[n].topX = bullets[n].x;bullets[n].topY = rand() % 400; // 0.399bullets[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..1199int py1 = rand() % 800; // 0.799pMem[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 函数擦除一些像素点,再依次调用 chose、shoot、showFire、heartFire 这些函数来分别处理点火、烟花弹升空、烟花绽放、特殊的心形烟花相关的操作,最后调用 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, yint 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..4getimage(&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+6if (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-699bullets[n].topX = bullets[n].x;bullets[n].topY = rand() % 400; // 0.399bullets[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..1199int py1 = rand() % 800; // 0.799pMem[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;
}
相关文章:
跨年烟花C++代码
嘿,朋友们!今天来给大家讲讲一段挺有意思的C代码呀,这段代码主要是用来实现一个烟花效果展示的程序哦,下面咱们一点点来看哈。 效果 1. 开头包含的那些头文件 #include <graphics.h> #include <conio.h> #include &…...
centos服务器 /1ib64/libm.so.6: version “GLIBc 2.27’ not found 异常
centos服务器 /1ib64/libm.so.6: version “GLIBc 2.27’ not found 异常 问题 在服务器使用open3d时,报错缺失GLIBC_2.27,因为后续操作出问题会导致服务器挂,所以最好先备份一下。 解决 查询glibc版本 输入指令查询系统glibc版本&#x…...
职场:如何快速适应职场新环境?
快速适应职场新环境是每个新员工都需要面对的重要挑战。为了能够顺利过渡并尽快融入新团队,以下是一些实用的策略: 1. 保持积极心态 开放心态:进入新环境时,保持开放的心态对于适应变化至关重要。要愿意接受新的挑战,…...
axios的替代方案onion-middleware
onion-middleware的由来 嗯。。。闲来无事瞎搞的!!!!主要用来实现请求/相应拦截,当然队列性的数据操作都是可以的 直接上使用教程 安装 npm install onion-middleware使用 import { OnionMiddleware } from onion…...
设计模式——泛型单例类
游戏中很多管理类都需要写成单例类,每次重复把管理类设置为单例类很繁琐, 这里直接写一个泛型单例类作为模板父类,方便其他需要写成单例类的类直接继承设置为单例类; using UnityEngine;public class Singleton<T> : Mono…...
三维卷积( 3D CNN)
三维卷积( 3D CNN) 1.什么是三维卷积 1.1 三维卷积简介 二维卷积是在单通道的一帧图像上进行滑窗操作,输入是高度H宽度W的二维矩阵。 三维卷积输入多了深度C这个维度,输入是高度H宽度W深度C的三维矩阵。在卷积神经网络中&…...
【JAVA】Java开发小游戏 - 简单的2D平台跳跃游戏 基本的2D平台跳跃游戏框架,适合初学者学习和理解Java游戏开发的基础概念
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默, 忍不住分享一下给大家。点击跳转到网站 学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……) 2、学会Oracle数据库入门到入土用法(创作中……) 3、手把…...
分享3个国内使用正版GPT的网站【亲测有效!2025最新】
1. molica 传送入口:https://ai-to.cn/url/?umolica 2. 多帮AI 传送入口:https://aigc.openaicloud.cn?inVitecodeMYAAGGKXVK 3. 厉害猫 传送入口:https://ai-to.cn/url/?ulihaimao...
CSDN Markdown编辑器设置视频居中完美解决方案
表格做中间容器,把视频放在表格里面,利用表格居中语法实现表格内元素居中对齐,从而完美实现视频居中。 【三角符文】jevil战无伤通关 这玩意整了我两个星期,焦头烂额都找不到解决方案。今天偶然想到可以用表格试试,没想…...
Java到底是值传递还是引用传递????
在搞懂这个问题之前, 我们要首先了解什么是值传递, 什么是引用传递? 值传递: 传递的是数据的副本,修改副本不会影响原始数据。引用传递: 传递的是数据的引用(地址),修改引用会直接影响原始数据. 也就是说,值传递和引…...
初学stm32 --- 电源监控
目录 STM32 电源监控介绍 上电/掉电复位POR/PDR(F1) 可编程电压检测器(PVD)(F1) PVD相关寄存器介绍(F1) 电源控制寄存器 PWR_CR 电源控制/状态寄存器 PWR_CSR PVD相关HAL库驱动介绍 PVD的使用步骤 …...
Win10本地部署大语言模型ChatGLM2-6B
鸣谢《ChatGLM2-6B|开源本地化语言模型》作者PhiltreX 作者显卡为英伟达4060 安装程序 打开CMD命令行,在D盘新建目录openai.wiki if not exist D:\openai.wiki mkdir D:\openai.wiki 强制切换工作路径为D盘的openai.wiki文件夹。 cd /d D:\openai.wik…...
[ LeetCode 75 ] 1768. 交替合并字符串
题目描述:(相关标签:双指针、字符串) 给你两个字符串 word1 和 word2 。请你从 word1 开始,通过交替添加字母来合并字符串。如果一个字符串比另一个字符串长,就将多出来的字母追加到合并后字符串的末尾。 返…...
(三)通过WebGL绘制一个简单的三角形来理解渲染管线
理解 WebGL 绘图原理的关键是了解它的渲染管线。WebGL 渲染管线实际上是由多个阶段组成的,每个阶段都有特定的任务,最终输出的是屏幕上的图像。为了让你能轻松理解这些原理,我将通过一个简单的例子来详细解释。 绘制一个简单的三角形 我们将…...
医学图像分析工具02:3D Slicer || 医学影像可视化与分析工具 支持第三方插件
3D Slicer 是一款功能全面的开源医学影像分析软件,广泛应用于影像处理、三维建模、影像配准和手术规划等领域。它支持多种医学影像格式(如 DICOM、NIfTI)和丰富的插件扩展,是神经科学、放射学和生物医学研究中不可或缺的工具。 在…...
Ollama VS LocalAI:本地大语言模型的深度对比与选择指南
随着人工智能技术的快速发展,大语言模型逐渐成为多个行业的重要工具。从生成内容到智能问答,大模型展现了强大的应用潜力。然而,云端模型的隐私性、使用成本和网络依赖等问题也促使更多用户关注本地化解决方案。Ollama 和 LocalAI 是近年来备…...
虚表 —— 隐藏行(简单版)
因为隐藏行改变了listview内部行号处理机制,需要处理大量细节,如listview内部用于传递行号的各种消息、通知等、封装的各种读取行号的函数等。 所以在工作量很大,一处纰漏可能导致重大bug的情况下,仅对隐藏行功能进行了简单封装&…...
CAD批量打印可检索的PDF文件
本文虽介绍CAD使用方法,但还是劝告大家尽早放弃使用CAD软件。。。。太TM难用了 当你打开CAD时发现如下一堆图纸,但是不想一个一个打印时。你可以按照下面操作实现自动识别图框实现批量打印。 1.安装批量打印插件 2.安装后打开CAD,输入命令Bp…...
2025.1.7(c++基础知识点)
作业(练习) 练习:要求在堆区连续申请5个int的大小空间用于存储5名学生的成绩,分别完成空间的申请、成绩的录入、升序排序、成绩输出函数以及空间释放函数,并在主程序中完成测试 要求使用new和delete完成 #include &…...
jenkins入门12-- 权限管理
Jenkins的权限管理 由于jenkins默认的权限管理体系不支持用户组或角色的配置,因此需要安装第三发插件来支持角色的配置,我们使用Role-based Authorization Strategy 插件 只有项目读权限 只有某个项目执行权限...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...
【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道
文/法律实务观察组 在债务重组领域,专业机构的核心价值不仅在于减轻债务数字,更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明,合法债务优化需同步实现三重平衡: 法律刚性(债…...
