基于SFML3.0的C++17新年烟花代码(附源码,蛇年大吉)

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

             还不快点赞!!!         

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

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

背景介绍

烟花燃放效果是一种极具视觉冲击力的动画效果,常用于庆祝节日、活动开场或游戏特效。使用 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 协议。您可以自由使用和修改代码,但需注明原作者和出处。


参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值