七夕程序:特效烟花 —— 分析

来源:互联网 发布:yum tcpdump 编辑:程序博客网 时间:2024/05/17 01:10

时隔两个月,我作业总算写完了=w=,最近参加了一个EasyX图形库官方群的活动,要求是做一个有关七夕的作品,我就写了个七夕烟花。

整个程序最重要的部分就是烟花,为了做出真正符合实际的烟花,我加入了重力效果,空气阻力效果,毕竟我做这个程序的时候才初二(写这篇博文的时候也是初二= =),还没学到物理,就用朋友的帮助和自己的想法构造物理效果。

以下是代码:

///////////////////////////////////////////////////// 程序名称:七夕烟花// 编译环境:Visual Studio 2017 / EasyX_2017测试版// 作  者:Allen_益文友 <540809998@qq.com>// 最后修改:2017-8-1// #pragma comment( lib,"winmm.lib")#include <windows.h>#include <graphics.h>#include <string>  #include <conio.h>#include <time.h>#include <vector>using namespace std;// 每颗烟花的粒子数量#define PARTICLE_NUMBER 100// 同时存在的最大烟花数#define MAX_FIREWORK_NUMBER 6// 速度struct Speed{    float x;    // 表示横向运动速度    float y;    // 表示纵向运动速度};// 烟花粒子struct Particle{    float x = 0;        // 表示横坐标    float y = 0;        // 表示纵坐标    Speed speed;        // 表示粒子当前运动速度    bool enable = true; // 表示粒子是否存在};// 烟花struct Firework{    float x;                    // 烟花爆炸点x坐标    float y;                    // 烟花爆炸点y坐标    vector<Particle> particles; // 储存了该烟花的粒子群    double R = 255;             // 烟花的颜色R    double G = 255;             // 烟花的颜色G    double B = 255;             // 烟花的颜色B    float radius = 2.0;         // 表示烟花伪半径,该值越大,烟花半径越大    int colorChangeBuffer = 0;  // 表示颜色变化的缓冲变量    static int number;          // 记录了当前存在的烟花数量};int Firework::number = 0;       // 重置烟花数量vector<Firework> fws;           // 烟花群const double gravity = 0.01;    // 重力值const double air = 0.002;       // 空气阻力// 全屏模糊函数void Blur(){    static DWORD* pMem = GetImageBuffer();  // 获取显存地址    for (int i = 800; i < 800 * 599; i++)    {        pMem[i] = RGB(            (GetRValue(pMem[i]) + GetRValue(pMem[i - 800]) + GetRValue(pMem[i - 1]) + GetRValue(pMem[i + 1]) + GetRValue(pMem[i + 800])) / 5,            (GetGValue(pMem[i]) + GetGValue(pMem[i - 800]) + GetGValue(pMem[i - 1]) + GetGValue(pMem[i + 1]) + GetGValue(pMem[i + 800])) / 5,            (GetBValue(pMem[i]) + GetBValue(pMem[i - 800]) + GetBValue(pMem[i - 1]) + GetBValue(pMem[i + 1]) + GetBValue(pMem[i + 800])) / 5);    }}// 精准延时函数void HpSleep(int ms){    static clock_t oldclock = clock();      // 静态变量,记录上一次 tick    oldclock += ms * CLOCKS_PER_SEC / 1000; // 更新 tick    if (clock() > oldclock)                 // 如果已经超时,无需延时        oldclock = clock();    else        while (clock() < oldclock)          // 延时            Sleep(1);                       // 释放 CPU 控制权,降低 CPU 占用率}// 在(x,y)坐标处添加烟花bool AddFirework(short x, short y){    if (Firework::number == MAX_FIREWORK_NUMBER) return false;  // 若当前烟花数量达到最大值,返回 false    mciSendString(_T("play firework from 0"), NULL, 0, NULL);    Firework fw;    // 创建烟花    // 设置烟花爆炸点的坐标    fw.x = x;    fw.y = y;    // 设置烟花颜色    fw.R = rand() % 146 + 110;    fw.G = rand() % 146 + 110;    fw.B = rand() % 146 + 110;    // 随机烟花粒子位置    for (int i = 0; i < PARTICLE_NUMBER; i++)    {        Particle p; // 创建粒子        // 在圆形范围内随机粒子速度        do        {            p.speed.x = (rand() % 201) / (200 / fw.radius / fw.radius) - fw.radius;            p.speed.y = (rand() % 201) / (200 / fw.radius / fw.radius) - fw.radius;        } while ((p.speed.x * p.speed.x + p.speed.y * p.speed.y) >= fw.radius * fw.radius);        fw.particles.push_back(p);  // 将粒子添加进粒子群    }    Firework::number++;             // 烟花数量+1    fws.push_back(fw);              // 将该烟花添加进烟花群    return true;}// 移动全部烟花粒子void MoveFireworkParticle(){    // 创建临时变量    int x, y;    int R;    int G;    int B;    for (int i = 0; i < Firework::number; i++)    {        for (int j = 0; j < PARTICLE_NUMBER; j++)        {            // 若粒子不存在,跳过该粒子            if (!fws[i].particles[j].enable)                continue;            x = int(fws[i].particles[j].x + fws[i].x);            y = int(fws[i].particles[j].y + fws[i].y);            // 移动粒子位置            fws[i].particles[j].x -= fws[i].particles[j].speed.x;            fws[i].particles[j].y -= fws[i].particles[j].speed.y;            // 受重力影响的速度变化            fws[i].particles[j].speed.y -= gravity;            // 受空气阻力影响的速度变化            if (fws[i].particles[j].speed.x > 0)                fws[i].particles[j].speed.x -= air;            if (fws[i].particles[j].speed.x < 0)                fws[i].particles[j].speed.x += air;            // 设置粒子颜色            if (fws[i].colorChangeBuffer++ == 30)            {                fws[i].R = (fws[i].R - 1 < 0) ? 0 : fws[i].R - 0.4;                fws[i].G = (fws[i].G - 1 < 0) ? 0 : fws[i].G - 0.4;                fws[i].B = (fws[i].B - 1 < 0) ? 0 : fws[i].B - 0.4;                fws[i].colorChangeBuffer = 0;            }            setfillcolor(RGB(fws[i].R, fws[i].G, fws[i].B));                                                // 应用颜色            solidcircle(int(fws[i].particles[j].x + fws[i].x), int(fws[i].particles[j].y + fws[i].y), 1);   // 绘制粒子        }    }}// 清除已经消失的烟花void ClearDisappearFirework(){    vector<Firework>::iterator it;  // 创建迭代器    // 找出所有已消失的烟花    for (it = fws.begin(); it != fws.end();)    {        // 若烟花绘制次数已达到最大生命周期,从烟花群中删除该烟花        if ((*it).R == 0 && (*it).G == 0 && (*it).B == 0)        {            it = fws.erase(it);            Firework::number--;        }        else            ++it;    }}int main(){    initgraph(800, 600);                // 开启图形界面    BeginBatchDraw();                   // 开启批量绘图    srand((unsigned)time(NULL));        // 设置随即种子    SetConsoleTitle(_T("七夕烟花"));    // 更改窗口名称    IMAGE Magpiebridge;    loadimage(&Magpiebridge, _T("res\\Magpiebridge.jpg"));    IMAGE man;    loadimage(&man, _T("res\\man.jpg"));    IMAGE woman;    loadimage(&woman, _T("res\\woman.jpg"));    IMAGE woman_;    loadimage(&woman_, _T("res\\woman_.jpg"));    IMAGE Introduction;    loadimage(&Introduction, _T("res\\Introduction.jpg"));    // 加载音乐/音效    mciSendString(_T("open res\\music.mp3 alias music"), NULL, 0, NULL);    mciSendString(_T("open res\\firework.mp3 alias firework"), NULL, 0, NULL);    // 播放背景音乐    mciSendString(_T("play music repeat"), NULL, 0, NULL);    putimage(0, 0, &Introduction); // 显示图片    FlushBatchDraw();              // 进行批量绘图    getch();                       // 获取按键输入    for (int i = 0; i < 90; i++)    {        setbkcolor(WHITE);                  // 设置背景颜色        cleardevice();                      // 清除屏幕内容        setcolor(BLACK);                    // 设置颜色        setbkmode(TRANSPARENT);             // 设置背景颜色        settextstyle(20, 0, _T("黑体"));  // 设置文字属性        // 绘制文字        outtextxy(0, 0, _T("七夕节这天,牛郎织女又在鹊桥相遇,他们两人十分快乐,还放起了烟花。"));        outtextxy(600, 25, _T("(按任何键继续)")); // 绘制文字        putimage(0, 460, &Magpiebridge);            // 显示图片        putimage(0 + i, 200, &man);                 // 显示图片        putimage(400 - i, 200, &woman, SRCAND);     // 显示图片        putimage(400 - i, 200, &woman_, SRCPAINT);  // 通过掩码图制造透明区        FlushBatchDraw();                           // 进行批量绘图        HpSleep(10);                                // 延时    }    // 清空按键缓存区,防止上次按键信息遗留到此getch    FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE));    getch();           // 获取按键输入    setbkcolor(BLACK); // 设置背景颜色    cleardevice();     // 清除屏幕内容    FlushBatchDraw();  // 进行批量绘图    // 自动生成烟花的缓冲变量    int autoCreateFirework = 0;    while (true)    {        // 按下鼠标新增烟花        while (MouseHit())        {            MOUSEMSG m = GetMouseMsg();            if (m.mkLButton)            {                AddFirework(m.x, m.y);            }        }        // 按下q键退出程序        if (_kbhit())        {            if (_getch() == 'q') break;        }        // 每隔一段时间自动生成一个烟花        if (autoCreateFirework-- == 0)        {            AddFirework(rand() % 500 + 100, rand() % 400 + 100);            autoCreateFirework = 180;        }        MoveFireworkParticle();     // 移动烟花        ClearDisappearFirework();   // 删除消失的烟花        Blur();                     // 全屏模糊处理        // 刷新视图        setcolor(BLACK);        line(0, 0, 800, 0);        line(0, 599, 800, 599);        FlushBatchDraw();        HpSleep(10);    // 延时    }    EndBatchDraw();     // 停止批量绘图    closegraph();       // 关闭绘图环境    return 0;}

先讲添加烟花函数

bool AddFirework(short x, short y)

中的随机生成烟花粒子速度:

// 在圆形范围内随机粒子速度do{    p.speed.x = (rand() % 201) / (200 / fw.radius / fw.radius) - fw.radius;    p.speed.y = (rand() % 201) / (200 / fw.radius / fw.radius) - fw.radius;} while ((p.speed.x * p.speed.x + p.speed.y * p.speed.y) >= fw.radius * fw.radius);

其中最重要的一行就是

while ((p.speed.x * p.speed.x + p.speed.y * p.speed.y) >= fw.radius * fw.radius);

如果木有这一行,随机生成粒子速度,就会导致烟花是方形的,原因很简单,
这里写图片描述
假设所有烟花粒子的x轴速度和y轴速度的上限都是1(负半轴同理),如果有3个烟花粒子

烟花粒子 x轴速度 y轴速度 烟花粒子1 1 1 烟花粒子2 1 0 烟花粒子3 0 1

好,假设这些速度,都是一秒移动的距离,那么就算算一秒钟,这些烟花粒子都移动多远。

烟花粒子 移动距离计算公式 移动距离 烟花粒子1 1²+1²= 2 烟花粒子2 1²+0²= 1 烟花粒子3 0²+1²= 1

很明显,速度不同,而烟花粒子的运动方向就是右上,所以会变成这个样子:
这里写图片描述

很明显,补全另外的方向,就成了一个方形qwq,所以需要限制随机生成的速度,形成圆形。

处理物理效果的部分在

void MoveFireworkParticle()

函数中:

// 移动粒子位置fws[i].particles[j].x -= fws[i].particles[j].speed.x;fws[i].particles[j].y -= fws[i].particles[j].speed.y;// 受重力影响的速度变化fws[i].particles[j].speed.y -= gravity;// 受空气阻力影响的速度变化if (fws[i].particles[j].speed.x > 0)    fws[i].particles[j].speed.x -= air;if (fws[i].particles[j].speed.x < 0)    fws[i].particles[j].speed.x += air;

因为有重力,所以(苹果砸牛顿qwq)y轴速度会不断变小(向负半轴减少),
因为有空气阻力,所以烟花粒子会在烟花刚爆炸的时候速度很快,而后来会不断变慢:

// 受重力影响的速度变化fws[i].particles[j].speed.y -= gravity;// 受空气阻力影响的速度变化if (fws[i].particles[j].speed.x > 0)    fws[i].particles[j].speed.x -= air;if (fws[i].particles[j].speed.x < 0)    fws[i].particles[j].speed.x += air;

核心代码讲完qwq,表问我七夕剧情部分qwq,我剧情做的很渣qwq。


注:源代码去EasyX贴吧下载