跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

基于 C++ 与 SFML 的 2026 新春烟花模拟程序

基于 C++ 与 SFML 的 2026 新春烟花模拟程序,涵盖粒子系统架构、物理运动算法及 WSL 环境部署。解决中文编码与字体加载问题,提供多种烟花特效类型与交互功能,适合图形编程入门与进阶参考。

JavaCoder发布于 2026/3/25更新于 2026/10/1586 浏览
基于 C++ 与 SFML 的 2026 新春烟花模拟程序

基于 C++ 与 SFML 的 2026 新春烟花模拟程序

效果预览

程序运行后将呈现动态烟花特效,包含多种粒子形态、物理重力模拟及中文祝福语显示。

环境准备

系统要求

本程序在 WSL(Windows Subsystem for Linux)的 Fedora 系统上开发,需满足以下环境:

  • WSL 2.0 或更高版本
  • Fedora Linux 发行版
  • C++ 编译器(g++)
  • SFML 图形库

安装依赖

在 Fedora 终端中执行以下命令更新并安装必要组件:

# 1. 更新系统包管理器
sudo dnf update

# 2. 安装 C++ 编译器和构建工具
sudo dnf install gcc-c++ make cmake

# 3. 安装 SFML 图形库(注意:Fedora 中 SFML 包名可能有差异)
sudo dnf install SFML SFML-devel
# 如果上述安装失败,尝试以下命令:
sudo dnf install sfml sfml-devel

# 4. 安装中文字体(可选,但推荐)
sudo dnf install wqy-microhei-fonts wqy-zenhei-fonts

验证安装

安装完成后,建议验证关键组件是否就绪:

# 验证 g++ 版本
g++ --version

# 验证 SFML 是否安装成功(检查头文件路径)
ls /usr/include/SFML/

核心逻辑解析

整体架构

程序采用面向对象设计,主要包含以下结构体与类:

  • Particle:粒子结构体,代表单个烟花粒子
  • Firework:烟花结构体,包含多个粒子集合
  • TextParticle:文字粒子结构体,用于特殊动画
  • FireworksDisplay:主显示类,管理所有烟花和界面交互

数据结构详解

Particle 结构体

每个粒子拥有独立的物理属性,通过寿命系统控制显示时间。

struct Particle {
    sf::Vector2f position;      // 粒子位置
    sf::Vector2f velocity;      // 粒子速度
    sf::Color color;            // 粒子颜色
    float lifetime;             // 当前寿命
    float maxLifetime;          
     size;                 
     isTrail;               
};
// 最大寿命
float
// 粒子大小
bool
// 是否为尾迹粒子
Firework 结构体

定义烟花的整体行为模式,支持多种类型(普通、心形、螺旋等)。

struct Firework {
    sf::Vector2f position;      // 烟花当前位置
    sf::Vector2f velocity;      // 烟花上升速度
    sf::Color color;            // 烟花颜色
    float timer;                // 计时器
    float explosionHeight;      // 爆炸高度
    bool exploded;              // 是否已爆炸
    std::vector<Particle> particles; // 粒子集合
    int type;                   // 烟花类型(0-5)
    float sizeFactor;           // 大小因子
    int pattern;                // 运动模式
    float speedMultiplier;      // 速度乘数
};

核心算法

粒子散射算法

普通烟花(类型 0)采用大量细小粒子全向散射,模拟真实爆炸效果。粒子数量通常在 2000-3000 个之间,确保视觉密度。

void createTinyScatteringExplosion(Firework& fw, int particleCount) {
    for (int i = 0; i < particleCount; ++i) {
        // 随机角度和速度
        float angle = dist(rng) * 2.0f * M_PI;
        float speed = dist(rng) * 40.0f + 20.0f;

        // 设置粒子速度和位置
        Particle p;
        p.position = fw.position;
        p.velocity = sf::Vector2f(cos(angle) * speed, sin(angle) * speed);

        // 随机颜色选择
        int colorType = static_cast<int>(dist(rng) * 6);
        switch(colorType) {
            case 0: p.color = sf::Color(255,255,255); break;
            case 1: p.color = sf::Color(255,200,100); break;
            case 2: p.color = sf::Color(100,255,100); break;
            case 3: p.color = sf::Color(100,100,255); break;
            case 4: p.color = sf::Color(255,100,255); break;
            default: p.color = sf::Color(255,150,50); break;
        }

        // 设置粒子属性
        p.lifetime = dist(rng) * 1.2f + 0.8f;
        p.maxLifetime = p.lifetime;
        p.size = (dist(rng) * 0.8f + 0.3f) * fw.sizeFactor * 0.1f;
        p.isTrail = false;
        fw.particles.push_back(p);
    }
}
心形烟花算法

基于心形参数方程创建爱心形状,利用三角函数计算粒子初始速度方向。

void createHeartExplosion(Firework& fw, int particleCount) {
    for (int i = 0; i < particleCount; ++i) {
        // 心形参数方程
        float t = dist(rng) * 2.0f * M_PI;
        float x = 16 * pow(sin(t), 3);
        float y = 13 * cos(t) - 5 * cos(2*t) - 2 * cos(3*t) - cos(4*t);

        // 缩放和随机化
        float scale = (dist(rng) * 0.8f + 0.2f) * fw.sizeFactor;
        x *= scale * 1.5f;
        y *= -scale * 1.5f; // 反转 Y 轴

        // 设置粒子
        Particle p;
        p.position = fw.position;
        p.velocity = sf::Vector2f(x * 6.0f, y * 6.0f);
        // ... 其他属性设置
        fw.particles.push_back(p);
    }
}
UTF-8 中文显示解决方案

为避免乱码,所有中文文本必须通过 sf::String::fromUtf8() 进行显式转换,并确保加载了支持中文的字体。

// 使用 sf::String::fromUtf8() 进行显式转换
std::string titleUtf8 = "2026 新春快乐 - 烟花庆祝";
titleText.setString(sf::String::fromUtf8(titleUtf8.begin(), titleUtf8.end()));

物理模拟系统

运动更新

每一帧根据时间增量更新位置和速度,应用重力和空气阻力。

void update(float deltaTime) {
    // 更新粒子位置
    it->position += it->velocity * deltaTime;
    
    // 重力影响
    it->velocity.y += 9.8f * deltaTime * 0.1f;
    
    // 空气阻力
    it->velocity *= 0.98f;
    
    // 寿命衰减
    it->lifetime -= deltaTime;
}

编译与运行

创建项目文件

在 WSL 中创建项目目录并编写源代码:

mkdir fireworks2026
cd fireworks2026
nano fireworks.cpp

编译命令

使用 g++ 进行编译,链接必要的 SFML 库:

g++ -std=c++11 -O2 -o fireworks fireworks.cpp \
    -lsfml-graphics -lsfml-window -lsfml-system

如果遇到链接错误,可尝试指定完整路径:

g++ -std=c++11 -O2 -o fireworks fireworks.cpp \
    -I/usr/include/SFML \
    -L/usr/lib64 \
    -lsfml-graphics -lsfml-window -lsfml-system

自动化脚本

为了简化流程,可以创建 compile.sh 脚本自动检查依赖并编译:

#!/bin/bash
echo "编译 2026 新年烟花程序..."

# 检查依赖
if [ ! -f /usr/include/SFML/Graphics.hpp ]; then
    echo "错误:SFML 未安装,请先运行:sudo dnf install SFML SFML-devel"
    exit 1
fi

# 编译
g++ -std=c++11 -O3 -o fireworks fireworks.cpp \
    -lsfml-graphics -lsfml-window -lsfml-system

if [ $? -eq 0 ]; then
    echo "编译成功!"
    echo "运行命令:./fireworks"
else
    echo "编译失败,请检查错误信息"
fi

赋予执行权限后运行:

chmod +x compile.sh
./compile.sh

字体配置

如果中文显示异常,需手动配置字体路径。例如从 Windows 复制字体到 WSL:

# 方案 1:从 Windows 复制字体
cp /mnt/c/Windows/Fonts/msyh.ttc .

# 方案 2:修改代码中的字体路径
# 在 loadFont() 函数中添加当前目录字体路径
fontPaths.push_back("./msyh.ttc");

操作指南

启动程序

编译成功后直接运行:

./fireworks

快捷键说明

按键功能效果描述
空格键发射更多烟花随机位置发射 5 个不同类型烟花
鼠标点击指定位置爆炸在点击位置立即爆炸烟花
回车键显示祝福语显示彩色祝福文字动画
R 键重置烟花重新开始开场表演
ESC 键退出程序安全退出烟花程序

视觉效果说明

  • 开场效果:程序启动时自动发射两个超大爱心烟花。
  • 烟花类型:包括普通散射、爱心、螺旋、星形、多重圆环及文字效果。
  • 祝福语:按回车键触发三行彩色祝福文字动画,持续 8 秒后消失。

故障排除

常见编译错误

错误 1:SFML 库找不到

fatal error: SFML/Graphics.hpp: No such file or directory

解决方案:确认 SFML 安装并检查头文件位置。

sudo dnf install SFML-devel
find /usr -name "Graphics.hpp" 2>/dev/null

错误 2:链接错误

undefined reference to `sf::...

解决方案:确保链接所有必要的 SFML 库。

g++ ... -lsfml-graphics -lsfml-window -lsfml-system -lsfml-audio

运行时问题

窗口无法打开:确保在图形环境下运行,WSL 需要 X11 转发支持(如 VcXsrv)。

中文显示为方框:确认字体文件存在,使用正确的 UTF-8 转换,修改字体加载路径。

程序运行缓慢:减少粒子数量,降低帧率限制,减少同时显示的烟花数量。

扩展与定制

添加新烟花类型

在 explodeFirework() 函数中添加新的 case 分支,并实现对应的爆炸函数。

自定义祝福文字

修改 setupText() 中的 blessingLines 数组内容即可。

添加背景音乐

引入 SFML/Audio.hpp,加载 .ogg 或 .mp3 文件并在构造函数中播放。

完整源码

#include <SFML/Graphics.hpp>
#include <vector>
#include <cmath>
#include <random>
#include <chrono>

// 粒子结构体
struct Particle {
    sf::Vector2f position;
    sf::Vector2f velocity;
    sf::Color color;
    float lifetime;
    float maxLifetime;
    float size;
    bool isTrail;
};

// 烟花结构体
struct Firework {
    sf::Vector2f position;
    sf::Vector2f velocity;
    sf::Color color;
    float timer;
    float explosionHeight;
    bool exploded;
    std::vector<Particle> particles;
    int type;
    float sizeFactor;
    int pattern;
    float speedMultiplier;
};

// 文字粒子效果
struct TextParticle {
    sf::Vector2f position;
    sf::Vector2f targetPosition;
    sf::Vector2f velocity;
    sf::Color color;
    float lifetime;
    float maxLifetime;
    float size;
};

class FireworksDisplay {
private:
    sf::RenderWindow window;
    sf::Font font;
    sf::Text titleText;
    sf::Text infoText;
    sf::Text blessingText[3];
    sf::Clock clock;
    std::vector<Firework> fireworks;
    std::vector<TextParticle> blessingParticles;
    std::mt19937 rng;
    std::uniform_real_distribution<float> dist;
    std::uniform_real_distribution<float> sizeDist;
    std::uniform_real_distribution<float> speedDist;
    bool showBlessing;
    float blessingTimer;
    bool fontLoaded;
    float timeSinceStart;
    bool initialShowDone;

public:
    FireworksDisplay()
        : window(sf::VideoMode(1200, 800), "2026 新春快乐 - 烟花庆祝"),
          rng(std::chrono::steady_clock::now().time_since_epoch().count()),
          dist(0.0f, 1.0f),
          sizeDist(0.8f, 2.0f),
          speedDist(0.2f, 3.0f),
          showBlessing(false),
          blessingTimer(0.0f),
          fontLoaded(false),
          timeSinceStart(0.0f),
          initialShowDone(false)
    {
        window.setVerticalSyncEnabled(false);
        window.setFramerateLimit(60);
        loadFont();
        initializeOpeningFireworks();
        setupText();
    }

    void loadFont() {
        // 尝试多种字体路径
        std::vector<std::string> fontPaths = {
            "/mnt/c/Windows/Fonts/msyh.ttc",
            "/mnt/c/Windows/Fonts/msyhbd.ttc",
            "/mnt/c/Windows/Fonts/simhei.ttf",
            "/mnt/c/Windows/Fonts/simkai.ttf",
            "/mnt/c/Windows/Fonts/simsun.ttc",
            "msyh.ttc",
            "simhei.ttf"
        };
        for (const auto& path : fontPaths) {
            if (font.loadFromFile(path)) {
                fontLoaded = true;
                break;
            }
        }
    }

    void setupText() {
        if (!fontLoaded) return;

        // 设置标题文本
        std::string titleUtf8 = "2026 新春快乐 - 烟花庆祝";
        titleText.setFont(font);
        titleText.setString(sf::String::fromUtf8(titleUtf8.begin(), titleUtf8.end()));
        titleText.setCharacterSize(48);
        titleText.setFillColor(sf::Color::Yellow);
        titleText.setStyle(sf::Text::Bold);
        titleText.setOutlineColor(sf::Color::Red);
        titleText.setOutlineThickness(2);
        sf::FloatRect titleBounds = titleText.getLocalBounds();
        titleText.setOrigin(titleBounds.left + titleBounds.width / 2.0f,
                            titleBounds.top + titleBounds.height / 2.0f);
        titleText.setPosition(window.getSize().x / 2.0f, 40);

        // 设置操作说明文本
        std::string infoUtf8 = "空格键:更多烟花 | 鼠标点击:指定位置爆炸 | 回车键:显示祝福 | ESC:退出";
        infoText.setFont(font);
        infoText.setString(sf::String::fromUtf8(infoUtf8.begin(), infoUtf8.end()));
        infoText.setCharacterSize(20);
        infoText.setFillColor(sf::Color(200, 200, 255));
        infoText.setPosition(10, window.getSize().y - 30);

        // 设置祝福文本
        std::string blessingLines[3] = {"新的一年,愿你", "马不停蹄奔赴山海,", "一马当先奔向热爱!"};
        for (int i = 0; i < 3; ++i) {
            blessingText[i].setFont(font);
            blessingText[i].setString(sf::String::fromUtf8(blessingLines[i].begin(), blessingLines[i].end()));
            blessingText[i].setCharacterSize(48);
            blessingText[i].setStyle(sf::Text::Bold);
            blessingText[i].setOutlineThickness(2);
            switch (i) {
                case 0: blessingText[i].setFillColor(sf::Color::Red); blessingText[i].setOutlineColor(sf::Color::Yellow); break;
                case 1: blessingText[i].setFillColor(sf::Color::Green); blessingText[i].setOutlineColor(sf::Color::White); break;
                case 2: blessingText[i].setFillColor(sf::Color::Blue); blessingText[i].setOutlineColor(sf::Color::Yellow); break;
            }
            sf::FloatRect bounds = blessingText[i].getLocalBounds();
            blessingText[i].setOrigin(bounds.left + bounds.width / 2.0f, bounds.top + bounds.height / 2.0f);
            blessingText[i].setPosition(window.getSize().x / 2.0f, 200 + i * 60);
        }
    }

    void initializeOpeningFireworks() {
        fireworks.clear();
        blessingParticles.clear();
        createFirework(window.getSize().x * 0.4f, window.getSize().y, 1, 0, 3.5f, speedDist(rng));
        createFirework(window.getSize().x * 0.6f, window.getSize().y, 1, 0, 3.5f, speedDist(rng));
    }

    void createFirework(float x, float y, int type, int pattern, float customSizeFactor = -1.0f, float speedMultiplier = 1.0f) {
        Firework fw;
        fw.position = sf::Vector2f(x, y);
        fw.pattern = pattern;
        fw.speedMultiplier = speedMultiplier;
        float baseSpeed = 100.0f;
        switch (pattern) {
            case 0: fw.velocity = sf::Vector2f((dist(rng) - 0.5f) * 10.0f, -(dist(rng) * 20.0f + baseSpeed) * speedMultiplier); break;
            case 1: fw.velocity = sf::Vector2f(-(dist(rng) * 10.0f + 5.0f), -(dist(rng) * 20.0f + baseSpeed) * speedMultiplier); break;
            case 2: fw.velocity = sf::Vector2f(dist(rng) * 10.0f + 5.0f, -(dist(rng) * 20.0f + baseSpeed) * speedMultiplier); break;
        }
        fw.explosionHeight = window.getSize().y * (0.05f + dist(rng) * 0.3f);
        fw.timer = 0.0f;
        fw.exploded = false;
        fw.type = type;
        if (customSizeFactor > 0) fw.sizeFactor = customSizeFactor;
        else fw.sizeFactor = sizeDist(rng);

        switch (type) {
            case 0: fw.color = sf::Color(255, 255, 255); break;
            case 1: fw.color = sf::Color(255, 50, 50); break;
            case 2: fw.color = sf::Color(0, 255, 100); break;
            case 3: fw.color = sf::Color(100, 100, 255); break;
            case 4: fw.color = sf::Color(255, 100, 255); break;
            case 5: fw.color = sf::Color(100, 255, 255); break;
        }
        fireworks.push_back(fw);
    }

    void explodeFirework(Firework& fw) {
        int particleCount = 0;
        switch (fw.type) {
            case 0: particleCount = 2000 + static_cast<int>(1000 * fw.sizeFactor); createTinyScatteringExplosion(fw, particleCount); break;
            case 1: particleCount = 400 + static_cast<int>(300 * fw.sizeFactor); createHeartExplosion(fw, particleCount); break;
            case 2: particleCount = 600 + static_cast<int>(600 * fw.sizeFactor); createSpiralExplosion(fw, particleCount); break;
            case 3: particleCount = 700 + static_cast<int>(600 * fw.sizeFactor); createStarExplosion(fw, particleCount); break;
            case 4: particleCount = 800 + static_cast<int>(800 * fw.sizeFactor); createMultiRingExplosion(fw, particleCount); break;
            case 5: particleCount = 600 + static_cast<int>(600 * fw.sizeFactor); createTextExplosion(fw, particleCount); break;
        }
    }

    void createTinyScatteringExplosion(Firework& fw, int particleCount) {
        for (int i = 0; i < particleCount; ++i) {
            float angle = dist(rng) * 2.0f * M_PI;
            float speed = dist(rng) * 40.0f + 20.0f;
            Particle p;
            p.position = fw.position;
            p.velocity = sf::Vector2f(cos(angle) * speed, sin(angle) * speed);
            int colorType = static_cast<int>(dist(rng) * 6);
            switch (colorType) {
                case 0: p.color = sf::Color(255, 255, 255); break;
                case 1: p.color = sf::Color(255, 200, 100); break;
                case 2: p.color = sf::Color(100, 255, 100); break;
                case 3: p.color = sf::Color(100, 100, 255); break;
                case 4: p.color = sf::Color(255, 100, 255); break;
                default: p.color = sf::Color(255, 150, 50); break;
            }
            p.lifetime = dist(rng) * 1.2f + 0.8f;
            p.maxLifetime = p.lifetime;
            p.size = (dist(rng) * 0.8f + 0.3f) * fw.sizeFactor * 0.1f;
            p.isTrail = false;
            fw.particles.push_back(p);
        }
        createTrailParticles(fw, 200 + static_cast<int>(200 * fw.sizeFactor));
    }

    void createHeartExplosion(Firework& fw, int particleCount) {
        float sizeMultiplier = 0.8f;
        for (int i = 0; i < particleCount; ++i) {
            float t = dist(rng) * 2.0f * M_PI;
            float x = 16 * pow(sin(t), 3);
            float y = 13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t);
            float scale = (dist(rng) * 0.8f + 0.2f) * fw.sizeFactor;
            x *= scale * 1.5f;
            y *= -scale * 1.5f;
            float speed = dist(rng) * 6.0f + 3.0f;
            Particle p;
            p.position = fw.position;
            p.velocity = sf::Vector2f(x * speed, y * speed);
            float colorFactor = dist(rng);
            p.color = sf::Color(255, 50 + static_cast<int>(205 * colorFactor), 50 + static_cast<int>(130 * colorFactor));
            p.lifetime = dist(rng) * 2.0f + 1.0f;
            p.maxLifetime = p.lifetime;
            p.size = (dist(rng) * 2.0f + 1.0f) * fw.sizeFactor * sizeMultiplier;
            p.isTrail = false;
            fw.particles.push_back(p);
        }
        createTrailParticles(fw, 80 + static_cast<int>(80 * fw.sizeFactor));
    }

    void createSpiralExplosion(Firework& fw, int particleCount) {
        for (int i = 0; i < particleCount; ++i) {
            float angle = dist(rng) * 10.0f * M_PI;
            float radius = angle * 0.1f;
            float speed = dist(rng) * 15.0f + 7.0f;
            Particle p;
            p.position = fw.position;
            p.velocity = sf::Vector2f(cos(angle) * radius * speed, sin(angle) * radius * speed);
            p.color = hsvToRgb(i % 360, 1.0f, 1.0f);
            p.lifetime = dist(rng) * 2.0f + 1.0f;
            p.maxLifetime = p.lifetime;
            p.size = (dist(rng) * 2.5f + 1.0f) * fw.sizeFactor;
            p.isTrail = false;
            fw.particles.push_back(p);
        }
        createTrailParticles(fw, 100 + static_cast<int>(100 * fw.sizeFactor));
    }

    void createStarExplosion(Firework& fw, int particleCount) {
        int points = 5 + static_cast<int>(dist(rng) * 3);
        for (int i = 0; i < particleCount; ++i) {
            float angle = (static_cast<float>(i % points) / points) * 2.0f * M_PI;
            float radius = (1.0f + 0.3f * sin(points * angle)) * 8.0f * fw.sizeFactor;
            float speed = dist(rng) * 18.0f + 9.0f;
            Particle p;
            p.position = fw.position;
            p.velocity = sf::Vector2f(cos(angle) * radius * speed, sin(angle) * radius * speed);
            p.color = sf::Color(255, 140 + static_cast<int>(115 * dist(rng)), 50);
            p.lifetime = dist(rng) * 2.0f + 1.0f;
            p.maxLifetime = p.lifetime;
            p.size = (dist(rng) * 3.0f + 1.5f) * fw.sizeFactor;
            p.isTrail = false;
            fw.particles.push_back(p);
        }
        createTrailParticles(fw, 120 + static_cast<int>(120 * fw.sizeFactor));
    }

    void createMultiRingExplosion(Firework& fw, int particleCount) {
        int rings = 3 + static_cast<int>(fw.sizeFactor);
        int particlesPerRing = particleCount / rings;
        for (int ring = 0; ring < rings; ++ring) {
            float ringRadius = (ring + 1) * 3.0f * fw.sizeFactor;
            for (int i = 0; i < particlesPerRing; ++i) {
                float angle = (static_cast<float>(i) / particlesPerRing) * 2.0f * M_PI;
                float speed = dist(rng) * 12.0f + 6.0f;
                Particle p;
                p.position = fw.position;
                p.velocity = sf::Vector2f(cos(angle) * ringRadius * speed, sin(angle) * ringRadius * speed);
                p.color = hsvToRgb((ring * 60 + i * 2) % 360, 1.0f, 1.0f);
                p.lifetime = dist(rng) * 2.0f + 1.0f;
                p.maxLifetime = p.lifetime;
                p.size = (dist(rng) * 2.5f + 1.0f) * fw.sizeFactor;
                p.isTrail = false;
                fw.particles.push_back(p);
            }
        }
        createTrailParticles(fw, 150 + static_cast<int>(150 * fw.sizeFactor));
    }

    void createTextExplosion(Firework& fw, int particleCount) {
        for (int i = 0; i < particleCount; ++i) {
            float angle = dist(rng) * 2.0f * M_PI;
            float speed = dist(rng) * 36.0f + 18.0f;
            Particle p;
            p.position = fw.position;
            p.velocity = sf::Vector2f(cos(angle) * speed, sin(angle) * speed);
            p.color = sf::Color(255, 215, 0);
            p.lifetime = dist(rng) * 2.5f + 1.5f;
            p.maxLifetime = p.lifetime;
            p.size = (dist(rng) * 4.0f + 2.0f) * fw.sizeFactor;
            p.isTrail = false;
            fw.particles.push_back(p);
        }
        createTrailParticles(fw, 180 + static_cast<int>(180 * fw.sizeFactor));
    }

    void createTrailParticles(Firework& fw, int count) {
        for (int i = 0; i < count; ++i) {
            Particle p;
            p.position = fw.position;
            float angle = (dist(rng) - 0.5f) * M_PI * 0.5f;
            float speed = dist(rng) * 25.0f + 12.0f;
            p.velocity = sf::Vector2f(sin(angle) * speed, -cos(angle) * speed);
            p.color = sf::Color(std::min(255, fw.color.r + 50), std::min(255, fw.color.g + 50), std::min(255, fw.color.b + 50), 200);
            p.lifetime = dist(rng) * 0.4f + 0.2f;
            p.maxLifetime = p.lifetime;
            p.size = (dist(rng) * 2.0f + 1.0f) * fw.sizeFactor * 0.5f;
            p.isTrail = true;
            fw.particles.push_back(p);
        }
    }

    void createBlessingTextEffect() {
        blessingParticles.clear();
        for (int i = 0; i < 1500; ++i) {
            TextParticle tp;
            tp.position = sf::Vector2f(dist(rng) * window.getSize().x, window.getSize().y + dist(rng) * 100.0f);
            int row = i % 3;
            int col = (i / 3) % 50;
            tp.targetPosition = sf::Vector2f(
                window.getSize().x * 0.3f + col * 15.0f + (dist(rng) - 0.5f) * 20.0f,
                200 + row * 60 + (dist(rng) - 0.5f) * 30.0f
            );
            sf::Vector2f direction = tp.targetPosition - tp.position;
            float distance = sqrt(direction.x * direction.x + direction.y * direction.y);
            float speed = 8.0f + dist(rng) * 6.0f;
            tp.velocity = (direction / distance) * speed;
            int hue = (i * 10) % 360;
            tp.color = hsvToRgb(hue, 1.0f, 1.0f);
            tp.lifetime = 8.0f;
            tp.maxLifetime = 8.0f;
            tp.size = dist(rng) * 4.0f + 2.0f;
            blessingParticles.push_back(tp);
        }
        showBlessing = true;
        blessingTimer = 8.0f;
    }

    sf::Color hsvToRgb(int h, float s, float v) {
        float c = v * s;
        float x = c * (1 - abs(fmod(h / 60.0f, 2) - 1));
        float m = v - c;
        float r, g, b;
        if (h >= 0 && h < 60) { r = c; g = x; b = 0; }
        else if (h >= 60 && h < 120) { r = x; g = c; b = 0; }
        else if (h >= 120 && h < 180) { r = 0; g = c; b = x; }
        else if (h >= 180 && h < 240) { r = 0; g = x; b = c; }
        else if (h >= 240 && h < 300) { r = x; g = 0; b = c; }
        else { r = c; g = 0; b = x; }
        return sf::Color(static_cast<int>((r + m) * 255), static_cast<int>((g + m) * 255), static_cast<int>((b + m) * 255));
    }

    void update(float deltaTime) {
        timeSinceStart += deltaTime;
        if (!initialShowDone && timeSinceStart > 3.0f) initialShowDone = true;

        if (initialShowDone && !showBlessing && dist(rng) < 0.08f && fireworks.size() < 25) {
            int pattern = static_cast<int>(dist(rng) * 3);
            int type = static_cast<int>(dist(rng) * 6);
            float speedMultiplier = speedDist(rng);
            createFirework(dist(rng) * window.getSize().x, window.getSize().y, type, pattern, -1.0f, speedMultiplier);
        }

        if (showBlessing) {
            blessingTimer -= deltaTime;
            if (blessingTimer <= 0.0f) {
                showBlessing = false;
                blessingParticles.clear();
            }
        }

        for (auto& fw : fireworks) {
            if (!fw.exploded) {
                fw.position += fw.velocity * deltaTime;
                fw.timer += deltaTime;
                if (fw.timer > 0.005f) {
                    for (int i = 0; i < 5; ++i) {
                        Particle trail;
                        trail.position = fw.position;
                        trail.velocity = sf::Vector2f(fw.velocity.x * 0.1f + (dist(rng) - 0.5f) * 6.0f, fw.velocity.y * 0.1f + (dist(rng) - 0.5f) * 6.0f);
                        trail.color = sf::Color(std::min(255, fw.color.r + 80), std::min(255, fw.color.g + 80), std::min(255, fw.color.b + 80), 240);
                        trail.lifetime = 0.2f + dist(rng) * 0.2f;
                        trail.maxLifetime = trail.lifetime;
                        trail.size = (dist(rng) * 1.5f + 0.5f) * fw.sizeFactor * 0.6f;
                        trail.isTrail = true;
                        fw.particles.push_back(trail);
                    }
                    fw.timer = 0.0f;
                }
                if (fw.position.y <= fw.explosionHeight || fw.velocity.y >= 0) {
                    fw.exploded = true;
                    explodeFirework(fw);
                }
            }

            for (auto it = fw.particles.begin(); it != fw.particles.end();) {
                it->position += it->velocity * deltaTime;
                if (it->isTrail) it->velocity.y += 9.8f * deltaTime * 0.02f;
                else it->velocity.y += 9.8f * deltaTime * 0.1f;
                it->velocity *= 0.98f;
                it->lifetime -= deltaTime;
                if (it->lifetime <= 0.0f) it = fw.particles.erase(it);
                else ++it;
            }
        }

        for (auto& tp : blessingParticles) {
            sf::Vector2f direction = tp.targetPosition - tp.position;
            float distance = sqrt(direction.x * direction.x + direction.y * direction.y);
            if (distance > 1.0f) {
                float speed = std::min(12.0f, distance * 0.2f);
                tp.velocity = (direction / distance) * speed;
                tp.position += tp.velocity * deltaTime;
            } else {
                tp.velocity.x += (dist(rng) - 0.5f) * 3.0f * deltaTime;
                tp.velocity.y += (dist(rng) - 0.5f) * 3.0f * deltaTime;
                tp.position += tp.velocity * deltaTime;
                if (abs(tp.position.x - tp.targetPosition.x) > 10.0f) tp.velocity.x = -tp.velocity.x * 0.1f;
                if (abs(tp.position.y - tp.targetPosition.y) > 10.0f) tp.velocity.y = -tp.velocity.y * 0.1f;
            }
            tp.lifetime -= deltaTime * 0.02f;
        }

        fireworks.erase(std::remove_if(fireworks.begin(), fireworks.end(), [](const Firework& fw) { return fw.exploded && fw.particles.empty(); }), fireworks.end());
    }

    void draw() {
        window.clear(sf::Color(5, 5, 25));
        for (const auto& fw : fireworks) {
            for (const auto& particle : fw.particles) {
                float alpha = (particle.lifetime / particle.maxLifetime) * 255;
                sf::Color particleColor = particle.color;
                particleColor.a = static_cast<sf::Uint8>(alpha);
                sf::CircleShape shape(particle.size);
                shape.setFillColor(particleColor);
                shape.setPosition(particle.position);
                shape.setOrigin(particle.size, particle.size);
                if (particle.isTrail) {
                    sf::CircleShape glow(particle.size * 2.5f);
                    glow.setFillColor(sf::Color(particleColor.r, particleColor.g, particleColor.b, particleColor.a / 3));
                    glow.setPosition(particle.position);
                    glow.setOrigin(particle.size * 2.5f, particle.size * 2.5f);
                    window.draw(glow);
                }
                window.draw(shape);
            }
            if (!fw.exploded) {
                sf::CircleShape head(4.0f * fw.sizeFactor);
                head.setFillColor(fw.color);
                head.setPosition(fw.position);
                head.setOrigin(4.0f * fw.sizeFactor, 4.0f * fw.sizeFactor);
                window.draw(head);
                sf::CircleShape glow(6.0f * fw.sizeFactor);
                glow.setFillColor(sf::Color(fw.color.r, fw.color.g, fw.color.b, 180));
                glow.setPosition(fw.position);
                glow.setOrigin(6.0f * fw.sizeFactor, 6.0f * fw.sizeFactor);
                window.draw(glow);
            }
        }
        for (const auto& tp : blessingParticles) {
            float alpha = (tp.lifetime / tp.maxLifetime) * 255;
            sf::Color particleColor = tp.color;
            particleColor.a = static_cast<sf::Uint8>(alpha);
            sf::CircleShape shape(tp.size);
            shape.setFillColor(particleColor);
            shape.setPosition(tp.position);
            shape.setOrigin(tp.size, tp.size);
            sf::CircleShape glow(tp.size * 1.8f);
            glow.setFillColor(sf::Color(particleColor.r, particleColor.g, particleColor.b, particleColor.a / 2));
            glow.setPosition(tp.position);
            glow.setOrigin(tp.size * 1.8f, tp.size * 1.8f);
            window.draw(glow);
            window.draw(shape);
        }
        if (fontLoaded) {
            window.draw(titleText);
            window.draw(infoText);
            if (showBlessing) {
                for (int i = 0; i < 3; ++i) window.draw(blessingText[i]);
            }
        }
        window.display();
    }

    void run() {
        sf::Clock deltaClock;
        while (window.isOpen()) {
            sf::Event event;
            while (window.pollEvent(event)) {
                if (event.type == sf::Event::Closed) window.close();
                if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Space) {
                    for (int i = 0; i < 5; ++i) {
                        int pattern = static_cast<int>(dist(rng) * 3);
                        int type = static_cast<int>(dist(rng) * 6);
                        float speedMultiplier = speedDist(rng);
                        createFirework(dist(rng) * window.getSize().x, window.getSize().y, type, pattern, -1.0f, speedMultiplier);
                    }
                }
                if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Enter) createBlessingTextEffect();
                if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) window.close();
                if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::R) {
                    initializeOpeningFireworks();
                    timeSinceStart = 0.0f;
                    initialShowDone = false;
                    showBlessing = false;
                    blessingParticles.clear();
                }
                if (event.type == sf::Event::MouseButtonPressed) {
                    int type = static_cast<int>(dist(rng) * 6);
                    int pattern = static_cast<int>(dist(rng) * 3);
                    float speedMultiplier = speedDist(rng);
                    createFirework(event.mouseButton.x, event.mouseButton.y, type, pattern, -1.0f, speedMultiplier);
                }
            }
            float deltaTime = deltaClock.restart().asSeconds();
            update(deltaTime);
            draw();
        }
    }
};

int main() {
    FireworksDisplay display;
    display.run();
    return 0;
}

目录

  1. 基于 C++ 与 SFML 的 2026 新春烟花模拟程序
  2. 效果预览
  3. 环境准备
  4. 系统要求
  5. 安装依赖
  6. 1. 更新系统包管理器
  7. 2. 安装 C++ 编译器和构建工具
  8. 3. 安装 SFML 图形库(注意:Fedora 中 SFML 包名可能有差异)
  9. 如果上述安装失败,尝试以下命令:
  10. 4. 安装中文字体(可选,但推荐)
  11. 验证安装
  12. 验证 g++ 版本
  13. 验证 SFML 是否安装成功(检查头文件路径)
  14. 核心逻辑解析
  15. 整体架构
  16. 数据结构详解
  17. Particle 结构体
  18. Firework 结构体
  19. 核心算法
  20. 粒子散射算法
  21. 心形烟花算法
  22. UTF-8 中文显示解决方案
  23. 物理模拟系统
  24. 运动更新
  25. 编译与运行
  26. 创建项目文件
  27. 编译命令
  28. 自动化脚本
  29. 检查依赖
  30. 编译
  31. 字体配置
  32. 方案 1:从 Windows 复制字体
  33. 方案 2:修改代码中的字体路径
  34. 在 loadFont() 函数中添加当前目录字体路径
  35. 操作指南
  36. 启动程序
  37. 快捷键说明
  38. 视觉效果说明
  39. 故障排除
  40. 常见编译错误
  41. 运行时问题
  42. 扩展与定制
  43. 添加新烟花类型
  44. 自定义祝福文字
  45. 添加背景音乐
  46. 完整源码

更多推荐文章

查看全部
  • 飞算 JavaAI 代码审查落地实践与关键细节
  • CLEAN 算法仿真:雷达信号杂波抑制
  • System Verilog 从基础到高级验证实战指南
  • 扫频信号原理与应用
  • 使用 Trae IDE 将 Figma 设计稿转化为前端代码
  • Web 可访问性最佳实践:构建人人可用的前端界面
  • 练习开发Skill——网页内容抓取Skill(website-content-fetch)
  • VibeVoice Pro 多终端 WebSocket 接入实战:Web/Android/iOS
  • VSCode Copilot 登录失败排查指南:常见问题与解决方案
  • Tushare、AkShare、BaoStock 三大数据源配置与对比指南
  • 基于腾讯云HAI和DeepSeek快速设计个人网页
  • Ubuntu 虚拟机部署 OpenClaw 个人 AI 助手指南
  • Go2RTC 视频直播推流服务支持 H265
  • 大型语言模型(LLMs)关键技术指南
  • JadeAI:开源 AI 简历生成器,50 套模板与 Docker 一键部署
  • GitHub 2026 年 AI 项目热度分析报告
  • VS Code C++ 无法跳转函数定义修复指南
  • VLM Unlearning 核心方法综述:从对抗遗忘到神经元编辑
  • 网络安全入门指南:技术方向、学习路线与方法论
  • 微信视频号视频加密逆向:WebAssembly 解密记录

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online