嘿,朋友们!今天来给大家讲讲一段挺有意思的C++代码呀,这段代码主要是用来实现一个烟花效果展示的程序哦,下面咱们一点点来看哈。
效果
1. 开头包含的那些头文件
cpp
#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. 定义的一些常量和结构体
cpp
#define PI 3.14
#define NUM 13
这里定义了 PI
就是咱们数学里常用的圆周率啦,值设成了 3.14
哦。还有 NUM
定义成了 13
,它在后面表示烟花或者烟花弹的数量相关的意思呢。
然后有两个结构体哦:
cpp
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
标记是不是开始输出像素点,再有几个时间相关的变量用来控制绽放速度呢。
cpp
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
结构体是描述烟花弹的情况哦,像x
、y
是烟花弹当前坐标,topX
、topY
是最高点坐标,height
是烟花高度,shoot
标记是否可以发射,同样也有时间相关变量控制发射速度,还有个img
数组用来储存花弹一亮一暗的图片,以及一个n
用来作为图片下标的哦。
3. 各种函数的作用
3.1 initFire
函数
cpp
// 初始化指定的烟花和烟花弹
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
函数
cpp
// 加载图片
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
函数
cpp
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
函数
cpp
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
函数
cpp
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
函数
cpp
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
函数
cpp
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
函数
cpp
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
函数
cpp
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
函数
cpp
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
函数
cpp
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
函数
cpp
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 . 完整代码
cpp
#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;
}