C++新年烟花代码(附源码,基于SFML3.0、C++17实现)

🐍 快过年了,祝大家蛇年大吉,希望新的一年能有人陪你一同看烟火!!!

             还不快点赞!!!         

这次的动画不仅包含烟花燃放,还有万家灯火,愿家常在心中!!!

 TypeScript版本在这里:TypeScript新年烟花代码(附源码,趣味编程)

目录

背景介绍

调试环境

技术要点

1. C++17 语言特点及优势

2. SFML 3.0 简介

3. 数学运算与物理模拟

4. 随机数生成器

5. 动画与帧循环

6. 状态管理与模块化

完整代码

源码下载

代码分析

1. 程序初始化与显示设置

2. 建筑物的初始化与绘制

3. 烟花的生成与粒子系统

4. 粒子的更新与绘制

5. 动画循环

6. 总结

SFML 的发展与应用场景

SFML 的发展历程

SFML 的应用场景

SFML 的优势

注意事项

写在后面

版权声明

参考资料


背景介绍

烟花燃放效果是一种极具视觉冲击力的动画效果,常用于庆祝节日、活动开场或游戏特效。使用 C++ 和 SFML 实现烟花效果,不仅可以提升程序的性能,还能通过代码的可维护性和扩展性,为开发者带来更高效的开发体验。本文将详细介绍如何使用 C++ 和 SFML 实现一个绚丽的新年烟花燃放效果,并探讨其在实际项目中的应用场景。

在实现过程中,我们使用了以下关键技术和库:

  • C++17:提供高性能和面向对象编程的支持。

  • SFML 3.0:一个跨平台的多媒体库,支持图形、音频、输入和网络等功能。

  • 随机数生成器:用于生成随机的烟花位置、颜色和粒子运动轨迹。

调试环境

本次代码调试环境为 2023 款 MacBook Pro,搭载苹果M2 Pro芯片,32GB内存,操作系统为macOs Ventura 13.5.2。Mac平台可直接编译运行,其他平台测试运行需确保以下两点

  1.  C++17 及 SFML3.0 开发环境正常配置
  2.  修改编译指令中的 SFML 头文件及库所在路径

技术要点

实现烟花燃放效果需要掌握以下关键技术,它们共同构成了实现这一视觉效果的基础。

1. C++17 语言特点及优势

C++17 是继 C++14 之后的一个重要版本,引入了许多新特性和改进,旨在提高编程效率、简化代码以及增强标准库的功能。它在实现烟花效果中的主要优势包括:

  • 高性能:C++17 提供了高效的内存管理和计算性能,适合实时图形渲染。

  • 面向对象编程:支持类和对象,便于将烟花、粒子等逻辑封装为独立的模块。

  • 标准库改进:例如 std::vectorstd::map 的性能优化,以及新特性如 std::optionalstd::variant

  • 新特性支持:如结构化绑定、条件初始化语句等。

2. SFML 3.0 简介

SFML(Simple and Fast Multimedia Library)是一个跨平台的 C++ 库,旨在简化多媒体应用程序的开发。本次开发基于 SFML 3.0,它引入了许多新特性和改进:

  • C++17 支持:SFML 3.0 已全面更新以支持 C++17。

  • 改进的事件处理 API:提供了更强大的事件处理机制。

  • 音频模块更新:用 miniaudio 替代了 OpenAL。

  • 剪切和模板测试:支持更复杂的图形渲染。

  • 依赖管理改进:通过 CMake 的 FetchContent 动态获取和构建依赖。

3. 数学运算与物理模拟

烟花效果的核心是物理模拟,涉及以下数学和物理知识:

  • 三角函数:通过 sincos 计算粒子的初始速度和方向,模拟烟花爆炸的向外扩散效果。

  • 向量运算:更新粒子的位置和速度,模拟粒子的运动轨迹。

  • 物理公式:模拟重力加速度(vy += 0.004),使粒子在垂直方向上逐渐下落。

4. 随机数生成器

随机性是烟花效果的关键,通过 rand() 实现以下功能:

  • 随机生成粒子的位置、速度和颜色:使每次烟花爆炸的效果都不尽相同。

  • 随机生成建筑物和窗户:为背景添加随机性,增强视觉效果。

  • 随机触发烟花爆炸:每隔一段时间随机生成新的烟花,增加动态效果。

5. 动画与帧循环

动画效果的实现依赖于高效的帧循环,核心技术包括:

  • window.display():SFML 提供的 API,用于更新窗口内容。

  • 帧循环逻辑:在每一帧中更新粒子状态、清除画布并重新绘制,实现动态效果。

6. 状态管理与模块化

虽然烟花效果主要依赖 SFML 和 C++,但通过面向对象编程,我们实现了模块化开发的优势:

  • 状态管理:通过类和对象管理粒子和建筑物的状态,避免不必要的重新计算。

  • 事件处理:通过 SFML 的事件系统(如鼠标点击事件)增强交互性,允许用户通过点击触发烟花爆炸。

通过以上技术的结合,我们能够实现一个高效、可维护且视觉效果绚丽的新年烟花燃放效果。


完整代码

以下是实现新年烟花燃放效果的完整代码:

fireworks.cpp (核心逻辑实现)

// main.cpp
#include <SFML/Graphics.hpp>
#include <vector>
#include <random>
#include <cmath>
#include <memory>
#include <iostream>
#include <sstream>

// 数据结构定义
struct Window {
    float x;
    float y;
    bool isLit;
    Window(float _x, float _y, bool _isLit) : x(_x), y(_y), isLit(_isLit) {}
};

struct Building {
    float x;
    float y;
    float width;
    float height;
    std::vector<Window> windows;
    Building(float _x, float _y, float _w, float _h) : x(_x), y(_y), width(_w), height(_h) {}
};

struct Particle {
    float x;
    float y;
    float vx;
    float vy;
    float alpha;
    sf::Color color;
    float life;
    Particle(float _x, float _y, float _vx, float _vy, sf::Color _color)
        : x(_x), y(_y), vx(_vx), vy(_vy), alpha(1.f), color(_color), life(90.f + static_cast<float>(rand()) / RAND_MAX * 30.f) {}
};

class FireworkCanvas {
private:
    sf::RenderWindow window;
    std::vector<Building> buildings;
    std::vector<Particle> particles;
    sf::Font font;

	// 颜色配置,保持与 TypeScript 版本一致,拥有不同色系
	std::vector<std::vector<sf::Color>> colors = {
		{
   hexToColor("#f0f000"), hexToColor("#0ff000"), hexToColor("#ff4081"), hexToColor("#00e676")},
		{
   hexToColor("#FF3333"), hexToColor("#FF6666"), hexToColor("#FF9999"), hexToColor("#FFCCCC")},
		{
   hexToColor("#FF0033"), hexToColor("#FF3366"), hexToColor("#FF6699"), hexToColor("#FF99CC")},
		{
   hexToColor("#FF0066"), hexToColor("#FF3399"), hexToColor("#FF66CC"), hexToColor("#FF99FF")},
		{
   hexToColor("#FF9933"), hexToColor("#FFCC33"), hexToColor("#FFFF33"), hexToColor("#FFFF66")},
		{
   hexToColor("#FFCC66"), hexToColor("#FF9900"), hexToColor("#FF6600"), hexToColor("#FF3300")},
		{
   hexToColor("#FFFF99"), hexToColor("#FFFFCC"), hexToColor("#FFFF00"), hexToColor("#FFFF33")},
		{
   hexToColor("#33FF33"), hexToColor("#66FF66"), hexToColor("#99FF99"), hexToColor("#CCFFCC")},
		{
   hexToColor("#00FF00"), hexToColor("#33FF66"), hexToColor("#66FF99"), hexToColor("#99FFCC")},
		{
   hexToColor("#3333FF"), hexToColor("#6666FF"), hexToColor("#9999FF"), hexToColor("#CCCCFF")},
		{
   hexToColor("#0000FF"), hexToColor("#3366FF"), hexToColor("#6699FF"), hexToColor("#99CCFF")},
		{
   hexToColor("#9933FF"), hexToColor("#CC33FF"), hexToColor("#FF33FF"), hexToColor("#FF66FF")},
		{
   hexToColor("#6600FF"), hexToColor("#9900FF"), hexToColor("#CC00FF"), hexToColor("#FF00FF")},
		{
   hexToColor("#80FF00"), hexToColor("#00FF80"), hexToColor("#0080FF"), hexToColor("#8000FF")},
		{
   hexToColor("#FF0080"), hexToColor("#FF8000"), hexToColor("#00FFFF"), hexToColor("#FF00FF")}
	};

	// 辅助函数:将十六进制颜色字符串转换为 sf::Color
	sf::Color hexToColor(const std::string& hex) {
		unsigned int color;
		std::istringstream(hex.substr(1)) >> std::hex >> color;
		return sf::Color((color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF);
	}

    void initBuildings() {
        const float screenWidth = static_cast<float>(window.getSize().x);
        const float screenHeight = static_cast<float>(window.getSize().y);
        const int buildingCount = static_cast<int>(screenWidth / 100);

        for (int i = 0; i < buildingCount; i++) {
            float width = 60.f + static_cast<float>(rand()) / RAND_MAX * 40.f;
            float height = 100.f + static_cast<float>(rand()) / RAND_MAX * 200.f;
            float x = i * (width + 20.f);
            float y = screenHeight - height;

            Building building(x, y, width, height);

            // 创建窗户
            int windowRows = static_cast<int>(height / 30);
            int windowCols = static_cast<int>(width / 20);

            for (int row = 0; row < windowRows; row++) {
                for (int col = 0; col < windowCols; col++) {
                    building.windows.emplace_back(
                        col * 20.f + 5.f,
                        row * 30.f + 10.f,
                        static_cast<float>(rand()) / RAND_MAX > 0.5f
                    );
                }
            }

            buildings.push_back(building);
        }
    }

	// 在指定位置创建烟花粒子
    void createParticles(float x, float y) {
        const auto& colorGroup = colors[rand() % colors.size()];
        
        for (int i = 0; i < 100; i++) {
            float angle = (M_PI * 2.f * i) / 50.f;
            float speed = 0.25f + static_cast<float>(rand()) / RAND_MAX * 0.75f;
            
            particles.emplace_back(
                x,
                y,
                std::cos(angle) * speed,
                std::sin(angle) * speed,
                colorGroup[rand() % colorGroup.size()]
            );
        }
    }

    void updateWindows() {
        if (static_cast<float>(rand()) / RAND_MAX > 0.1f) return;

        if (buildings.empty()) return;
        auto& building = buildings[rand() % buildings.size()];
        
        int updateCount = 1 + rand() % 2;
        for (int i = 0; i < updateCount; i++) {
            if (!building.windows.empty()) {
                auto& window = building.windows[rand() % building.windows.size()];
                window.isLit = !window.isLit;
            }
        }
    }

    void drawBuildings() {
        for (const auto& building : buildings) {
            // 绘制建筑物主体
            sf::RectangleShape buildingShape({building.width, building.height});
            buildingShape.setPosition({building.x, building.y});
            buildingShape.setFillColor(sf::Color(26, 26, 26));
            window.draw(buildingShape);

            // 绘制窗户
            for (const auto& win : building.windows) {
                if (win.isLit) {
                    // 绘制窗户光晕
                    sf::CircleShape glow(3.f);
                    glow.setPosition({building.x + win.x - 5.f, building.y + win.y - 5.f});
                    glow.setFillColor(sf::Color(255, 229, 180, 100));
                    window.draw(glow);
                }

                // 绘制窗户本体
                sf::RectangleShape windowShape({
   10.f, 10.f});
                windowShape.setPosition({building.x + win.x, building.y + win.y});
                windowShape.setFillColor(win.isLit ? sf::Color(255, 229, 180) : sf::Color(51, 51, 51));
                window.draw(windowShape);
            }
        }
    }

    void draw2025Text() {
        sf::Text text(font, L"2025 蛇年大吉", 80);
        text.setFillColor(sf::Color(255, 229, 180));
        text.setStyle(sf::Text::Bold);
        
        // 居中显示
        sf::FloatRect textRect = text.getLocalBounds();
        text.setOrigin({textRect.position.x + textRect.size.x/2.0f, textRect.position.y + textRect.size.y/2.0f});
        text.setPosition({window.getSize().x/2.0f, 250.f});

        // 绘制发光效果
        for (int i = 10; i > 0; --i) {
            sf::Text glowText = text;
            glowText.setFillColor(sf::Color(255, 229, 180, 25 * i));
            glowText.move({
   0.f, 0.f});
            window.draw(glowText);
        }

        window.draw(text);
    }

public:
    FireworkCanvas() : window(sf::VideoMode::getFullscreenModes()[0], "Fireworks", sf::State::Fullscreen) {
        window.setFramerateLimit(60);
        
        // 加载字体,确保字体文件与可执行文件在同一目录下(字体需支持中文)
        if (!font.openFromFile("babyfont.ttf")) {
            throw std::runtime_error("Could not load font!");
        }

		// 初始化建筑物
        initBuildings();
    }

    void run() {
        while (window.isOpen()) {
            sf::Event event(sf::Event::Closed);
            while (const auto event = window.pollEvent()) {
            	if (event->is<sf::Event::Closed>() || (event->is<sf::Event::KeyPressed>() && event->getIf<sf::Event::KeyPressed>()->code == sf::Keyboard::Key::Escape)) {
                    window.close();
                }
                else if (const auto* mouseButton = event->getIf<sf::Event::MouseButtonPressed>()) {
                    createParticles(mouseButton->position.x, mouseButton->position.y);
                }
            }

            // 清空屏幕
            window.clear(sf::Color::Black);

            // 绘制建筑物
            drawBuildings();

            // 绘制2025文字
            draw2025Text();

            // 更新窗户状态
            updateWindows();

            // 更新和绘制粒子
            for (auto it = particles.begin(); it != particles.end();) {
                it->x += it->vx;
                it->y += it->vy;
                it->vy += 0.005f;
                it->alpha = it->life / 120.f;
                it->life -= 0.6f;

                if (it->life > 0) {
                    sf::CircleShape particle(2.f);
                    particle.setPosition({it->x, it->y});
                    sf::Color color = it->color;
                    color.a = static_cast<std::uint8_t>(255 * it->alpha);
                    particle.setFillColor(color);
                    window.draw(particle);
                    ++it;
                } else {
                    it = particles.erase(it);
                }
            }

            // 随机生成烟花
            if (static_cast<float>(rand()) / RAND_MAX < 0.03f) {
                float x = static_cast<float>(rand()) / RAND_MAX * window.getSize().x;
                float y = static_cast<float>(rand()) / RAND_MAX * window.getSize().y * 0.3f;
                createParticles(x, y);
            }

            window.display();
        }
    }
};

int main() {
    srand(static_cast<unsigned>(time(nullptr)));
    
    try {
        FireworkCanvas app;
        app.run();
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}

编译脚本 build.sh(for mac系统,windows及linux需自行更换 SFML 头文件及库路径)

#!/bin/bash
g++ -std=c++17 fireworks.cpp -o fireworks -I/opt/homebrew/include -L/opt/homebrew/lib -lsfml-graphics -lsfml-window -lsfml-system

源码下载

您可以通过以下链接访问和下载完整的代码: 源码压缩包链接


代码分析

以下是对代码实现的详细解析,帮助理解新年烟花燃放效果的核心逻辑和实现方式。

1. 程序初始化与显示设置

FireworkCanvas 类中,我们使用 SFML 的 sf::RenderWindow 初始化程序,并设置窗口大小以适应屏幕。同时,我们通过 initBuildings 函数生成建筑物和窗户,为烟花效果提供背景。

FireworkCanvas() : window(sf::VideoMode::getFullscreenModes()[0], "Fireworks", sf::State::Fullscreen) {
    window.setFramerateLimit(60);
    
    // 加载字体,确保字体文件与可执行文件在同一目录下(字体需支持中文)
    if (!font.loadFromFile("babyfont.ttf")) {
        throw std::runtime_error("Could not load font!");
    }

    // 初始化建筑物
    initBuildings();
}

解析:

  • sf::RenderWindow:创建一个全屏窗口,用于绘制烟花效果。

  • font.loadFromFile:加载字体文件,用于绘制新年祝福语,这里的babyfont.ttf可在资源文件获取。

  • initBuildings:动态生成建筑物和窗户,模拟城市夜景。

2. 建筑物的初始化与绘制

建筑物是烟花效果的背景,通过随机生成的矩形和窗户来模拟城市夜景。

void initBuildings() {
    const float screenWidth = static_cast<float>(window.getSize().x);
    const float screenHeight = static_cast<float>(window.getSize().y);
    const int buildingCount = static_cast<int>(screenWidth / 100);

    for (int i = 0; i < buildingCount; i++) {
        float width = 60.f + static_cast<float>(rand()) / RAND_MAX * 40.f;
        float height = 100.f + static_cast<float>(rand()) / RAND_MAX * 200.f;
        float x = i * (width + 20.f);
        float y = screenHeight - height;

        Building building(x, y, width, height);

        // 创建窗户
        int windowRows = static_cast<int>(height / 30);
        int windowCols = static_cast<int>(width / 20);

        for (int row = 0; row < windowRows; row++) {
            for (int col = 0; col < windowCols; col++) {
                building.windows.emplace_back(
                    col * 20.f + 5.f,
                    row * 30.f + 10.f,
                    static_cast<float>(rand()) / RAND_MAX > 0.5f
                );
            }
        }

        buildings.push_back(building);
    }
}

解析:

  • 建筑物生成逻辑:根据屏幕宽度动态生成建筑物,每栋建筑物的宽度和高度随机生成。

  • 窗户生成逻辑:每栋建筑物包含多个窗户,窗户是否点亮通过随机概率决定。

  • buildings:将生成的建筑物存储在 std::vector 中,便于后续绘制。

3. 烟花的生成与粒子系统

烟花效果的核心是粒子系统,每个烟花由多个粒子组成,粒子的运动和衰减模拟了烟花的爆炸效果。

void createParticles(float x, float y) {
    const auto& colorGroup = colors[rand() % colors.size()];
    
    for (int i = 0; i < 100; i++) {
        float angle = (M_PI * 2.f * i) / 50.f;
        float speed = 0.25f + static_cast<float>(rand()) / RAND_MAX * 0.75f;
        
        particles.emplace_back(
            x,
            y,
            std::cos(angle) * speed,
            std::sin(angle) * speed,
            colorGroup[rand() % colorGroup.size()]
        );
    }
}

解析:

  • 粒子初始化:每个烟花生成 100 个粒子,粒子的初始位置为烟花的中心。

  • 速度和方向:通过三角函数计算粒子的初始速度和方向,模拟烟花爆炸的向外扩散效果。

  • 颜色和生命周期:粒子的颜色从预定义的色彩数组中随机选择,生命周期决定了粒子的存活时间。

4. 粒子的更新与绘制

粒子的更新和绘制逻辑在 run 函数中实现,通过物理公式模拟粒子的运动和衰减。

for (auto it = particles.begin(); it != particles.end();) {
    it->x += it->vx;
    it->y += it->vy;
    it->vy += 0.005f;
    it->alpha = it->life / 120.f;
    it->life -= 0.6f;

    if (it->life > 0) {
        sf::CircleShape particle(2.f);
        particle.setPosition({it->x, it->y});
        sf::Color color = it->color;
        color.a = static_cast<std::uint8_t>(255 * it->alpha);
        particle.setFillColor(color);
        window.draw(particle);
        ++it;
    } else {
        it = particles.erase(it);
    }
}

解析:

  • 物理模拟:粒子的水平速度保持不变,垂直速度通过重力加速度逐渐增加,模拟自然下落效果。

  • 透明度和生命周期:粒子的透明度随生命周期减少,模拟烟花的逐渐消散。

  • 绘制:使用 sf::CircleShape 绘制圆形粒子,并通过 setFillColor 设置颜色和透明度。

5. 动画循环

动画循环通过 SFML 的 window.display() 实现,每次循环中清除画布、更新粒子状态并重新绘制。

void run() {
    while (window.isOpen()) {
        sf::Event event(sf::Event::Closed);
        while (const auto event = window.pollEvent()) {
            if (event->is<sf::Event::Closed>() || (event->is<sf::Event::KeyPressed>() && event->getIf<sf::Event::KeyPressed>()->code == sf::Keyboard::Key::Escape)) {
                window.close();
            }
            else if (const auto* mouseButton = event->getIf<sf::Event::MouseButtonPressed>()) {
                createParticles(mouseButton->position.x, mouseButton->position.y);
            }
        }

        // 清空屏幕
        window.clear(sf::Color::Black);

        // 绘制建筑物
        drawBuildings();

        // 绘制2025文字
        draw2025Text();

        // 更新窗户状态
        updateWindows();

        // 更新和绘制粒子
        // ... 粒子更新代码

        // 随机生成烟花
        if (static_cast<float>(rand()) / RAND_MAX < 0.03f) {
            float x = static_cast<float>(rand()) / RAND_MAX * window.getSize().x;
            float y = static_cast<float>(rand()) / RAND_MAX * window.getSize().y * 0.3f;
            createParticles(x, y);
        }

        window.display();
    }
}

解析:

  • 清除画布:使用 window.clear 清空屏幕,保留部分残影,增强视觉效果。

  • 随机生成烟花:每隔一段时间随机生成新的烟花,增加动态效果。

  • 帧循环:通过 window.display() 实现持续的动画效果。

6. 总结

通过 C++17 和 SFML 3.0,我们实现了一个绚丽的新年烟花燃放效果。核心逻辑包括:

  1. 使用 SFML 绘制建筑物和烟花。

  2. 通过粒子系统模拟烟花的爆炸和衰减。

  3. 使用物理公式和随机数生成器实现动态效果。

  4. 利用 SFML 的帧循环实现流畅的动画效果。

这种实现方式不仅展示了 C++17 的高性能和代码结构化优势,还通过面向对象编程提升了代码的可维护性。


SFML 的发展与应用场景

SFML(Simple and Fast Multimedia Library)自发布以来,已经成为多媒体应用开发中的一个重要工具。它提供了简单易用的 API,支持图形绘制、音频处理、输入处理和网络通信等功能。随着版本的不断更新,SFML 的功能和性能也在不断提升。

SFML 的发展历程

  • 2007 年:SFML 1.0 发布,最初支持 Windows 和 Linux 平台。

  • 2012 年:SFML 2.0 发布,引入了对 OpenGL 的支持,并改进了音频模块。

  • 2023 年:SFML 3.0 发布,引入了多项重大改进,包括对 C++17 的全面支持、改进的事件处理 API、音频模块的更新(使用 miniaudio 替代 OpenAL)以及对剪切和模板测试的支持。

SFML 的应用场景

  1. 游戏开发:SFML 提供了丰富的图形和音频功能,适合开发 2D 游戏。

  2. 多媒体应用:支持音频播放和图形渲染,可用于开发音乐播放器、视频播放器等。

  3. 教育和学习:其简单易用的 API 使其成为学习 C++ 和多媒体编程的理想工具。

  4. 科学可视化:可以用于绘制图表、模拟物理现象等。

SFML 的优势

  • 跨平台:支持 Windows、Linux 和 macOS。

  • 高性能:基于 OpenGL 和 OpenAL,提供高效的图形和音频处理。

  • 易于上手:提供了简单直观的 API,适合初学者和专业开发者。


注意事项

  1. 字体文件:确保字体文件(如 babyfont.ttf)与可执行文件在同一目录下,并支持中文显示。

  2. 依赖库:确保安装了 SFML 3.0 库,并正确配置了编译环境。

  3. C++17 支持:本次开发基于 C++17,确保编译器支持 C++17 标准。


写在后面

感谢您的阅读!如果您在实现过程中有任何问题或建议,欢迎随时反馈。开发这个烟花效果的过程充满了乐趣,希望您也能享受其中。如果您喜欢这个效果,不妨分享到您的项目中!


版权声明

本文遵循 Creative Commons Attribution-ShareAlike 4.0 International License 协议。您可以自由使用和修改代码,但需注明原作者和出处。

评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值