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

C++ 实现 2026 新年烟花特效程序

介绍使用 C++ 和 SFML 库开发 2026 新年烟花庆祝程序的完整过程。涵盖环境配置(WSL/Fedora)、核心数据结构设计(粒子、烟花)、物理模拟算法(重力、阻力)、多种烟花特效实现(普通、心形、螺旋等)以及中文显示解决方案。包含编译运行指南、故障排除及扩展定制方法,适合图形编程爱好者学习参考。

暖阳发布于 2026/3/28更新于 2026/9/1068 浏览
C++ 实现 2026 新年烟花特效程序

2026 新年烟花庆祝程序

第一部分:环境准备

1.1 系统要求

本程序设计在 WSL(Windows Subsystem for Linux)的 Fedora 系统上运行,需要以下环境:

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

1.2 安装依赖

在 Fedora 终端中执行以下命令:

# 1. 更新系统包管理器
sudo dnf update
# 2. 安装 C++ 编译器和构建工具
sudo dnf install gcc-c++ make cmake
# 3. 安装 SFML 图形库(注意:Fedora 中 SFML 包名可能有差异)
sudo dnf install SFML SFML-devel
# 如果上述 SFML 安装失败,尝试以下命令:
sudo dnf install sfml sfml-devel
# 4. 安装中文字体(可选,但推荐)
sudo dnf install wqy-microhei-fonts wqy-zenhei-fonts

1.3 验证安装

安装完成后,验证关键组件:

# 验证 g++ 版本
g++ --version
# 验证 SFML 是否安装成功(检查头文件路径)
ls /usr/include/SFML/

第二部分:代码结构详解

2.1 整体架构

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

  • Particle:粒子结构体,代表单个烟花粒子
  • Firework:烟花结构体,包含多个粒子
  • TextParticle:文字粒子结构体
  • FireworksDisplay:主显示类,管理所有烟花和界面

2.2 核心数据结构

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

功能说明:

  • 每个粒子有独立的物理属性(位置、速度)
  • 寿命系统控制粒子显示时间
  • isTrail标志区分爆炸粒子和尾迹粒子
2.2.2 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:大量细小粒子散射(普通烟花)
  • 类型 1:心形烟花
  • 类型 2:螺旋形烟花
  • 类型 3:星形烟花
  • 类型 4:多重圆环烟花
  • 类型 5:文字效果烟花

2.3 核心算法解析

2.3.1 烟花爆炸算法

普通烟花(类型 0)采用大量细小粒子散射算法:

void createTinyScatteringExplosion(Firework& fw, int particleCount) {
    for (int i = 0; i < particleCount; ++i) {
        // 1. 随机角度和速度
        float angle = dist(rng) * 2.0f * M_PI;
        float speed = dist(rng) * 40.0f + 20.0f;
        // 2. 设置粒子速度和位置
        Particle p;
        p.position = fw.position;
        p.velocity = sf::Vector2f(cos(angle) * speed, sin(angle) * speed);
        // 3. 随机颜色选择
        int colorType = static_cast<int>(dist(rng) * 6);
        // ... 颜色设置代码
        // 4. 设置粒子属性
        p.lifetime = dist(rng) * 1.2f + 0.8f;
        p.size = (dist(rng) * 0.8f + 0.3f) * fw.sizeFactor * 0.1f;
        fw.particles.push_back(p);
    }
}

算法特点:

  • 粒子数量:2000-3000 个
  • 粒子大小:非常细小(原始大小的 10%)
  • 颜色多样:6 种随机颜色
  • 散射方向:360 度全向散射
2.3.2 心形烟花算法

基于心形参数方程创建爱心形状:

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 * speed, y * speed);
        // ... 其他属性设置
    }
}
2.3.3 UTF-8 中文显示解决方案

根据链接内容,采用显式 UTF-8 转换解决中文乱码:

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

关键点:

  • 所有中文文本必须通过 sf::String::fromUtf8() 转换
  • 确保使用支持中文的字体(如微软雅黑)
  • 字体文件路径需要正确配置

2.4 物理模拟系统

2.4.1 运动更新
void update(float deltaTime) {
    // 更新烟花位置
    fw.position += fw.velocity * deltaTime;
    // 更新粒子位置
    it->position += it->velocity * deltaTime;
    // 重力影响
    it->velocity.y += 9.8f * deltaTime * 0.1f;
    // 空气阻力
    it->velocity *= 0.98f;
    // 寿命衰减
    it->lifetime -= deltaTime;
}
2.4.2 尾迹效果
// 尾迹粒子生成
if (fw.timer > 0.005f) {
    for (int i = 0; i < 5; ++i) {
        Particle trail;
        trail.position = fw.position;
        trail.velocity = fw.velocity * 0.1f + 随机偏移;
        trail.color = 稍亮的颜色;
        trail.isTrail = true;
        fw.particles.push_back(trail);
    }
    fw.timer = 0.0f;
}

第三部分:编译与运行

3.1 创建项目文件

  1. 在 WSL 中创建项目目录:
mkdir fireworks2026
cd fireworks2026
  1. 创建源文件:
# 将完整代码保存为 fireworks.cpp
nano fireworks.cpp
# 粘贴代码,按 Ctrl+X,然后 Y,回车保存

3.2 编译命令

# 基本编译命令
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

3.3 编译脚本

创建 compile.sh 脚本:

#!/bin/bash
echo "编译 2026 新年烟花程序..."
echo ""
# 检查依赖
echo "检查 SFML 库..."
if [ ! -f /usr/include/SFML/Graphics.hpp ]; then
    echo "错误:SFML 未安装,请先运行:sudo dnf install SFML SFML-devel"
    exit 1
fi
# 编译
echo "开始编译..."
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

3.4 字体配置

如果中文显示异常,配置字体:

# 方案 1:从 Windows 复制字体
cp /mnt/c/Windows/Fonts/msyh.ttc .
# 方案 2:创建字体链接
sudo ln -s /mnt/c/Windows/Fonts/msyh.ttc /usr/share/fonts/msyh.ttc
# 方案 3:修改代码中的字体路径
# 在 loadFont() 函数中添加当前目录字体路径
fontPaths.push_back("./msyh.ttc");

第四部分:程序操作指南

4.1 启动程序

# 编译后直接运行
./fireworks

4.2 操作快捷键

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

4.3 视觉效果说明

4.3.1 开场效果
  • 程序启动时自动发射两个超大爱心烟花
  • 爱心烟花位于屏幕中间偏左和偏右位置
  • 3 秒后开始随机生成其他烟花
4.3.2 烟花类型
  1. 普通烟花:大量细小粒子散射,2000-3000 个微小粒子
  2. 爱心烟花:红色心形图案,适当缩小显示
  3. 螺旋烟花:彩虹色螺旋图案
  4. 星形烟花:五角星形状,金色到橙色渐变
  5. 多重圆环:多个同心圆环,不同颜色
  6. 文字烟花:金色粒子散射效果
4.3.3 祝福语效果

按回车键触发:

  • 显示三行彩色祝福文字
  • 文字以粒子动画形式出现
  • 持续 8 秒后自动消失

文字内容:

新的一年,愿你 马不停蹄奔赴山海,
一马当先奔向热爱!

4.4 性能优化提示

4.4.1 帧率调整
// 在构造函数中调整帧率
window.setFramerateLimit(60); // 默认 60FPS
// 如果需要更高帧率(性能允许)
window.setFramerateLimit(120);
4.4.2 粒子数量控制
// 调整普通烟花粒子数量
particleCount = 2000 + static_cast<int>(1000 * fw.sizeFactor);
// 如果需要降低性能消耗,减少粒子数量
particleCount = 1000 + static_cast<int>(500 * fw.sizeFactor);
4.4.3 同时显示烟花数量
// 控制最大烟花数量
if (fireworks.size() < 25) { // 当前限制 25 个
    // 创建新烟花
}

第五部分:故障排除

5.1 常见编译错误

错误 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

5.2 运行时问题

问题 1:窗口无法打开
Failed to open window

解决方案:

  • 确保在图形环境下运行
  • WSL 需要 X11 转发支持
  • 安装 X11 服务器(如 VcXsrv)
问题 2:中文显示为方框

解决方案:

  1. 确认字体文件存在
  2. 使用正确的 UTF-8 转换
  3. 修改字体加载路径
问题 3:程序运行缓慢

解决方案:

  1. 减少粒子数量
  2. 降低帧率限制
  3. 减少同时显示的烟花数量

5.3 WSL 特定配置

X11 转发配置
# 在 Windows 端安装 VcXsrv
# 启动 XLaunch,选择 "Disable access control"
# 在 WSL 中设置 DISPLAY 变量
export DISPLAY=$(grep -m 1 nameserver /etc/resolv.conf | awk '{print $2}'):0
# 测试 X11 转发
xeyes # 应该能看到眼睛跟随鼠标
音频支持(可选)
# 安装脉冲音频支持
sudo dnf install pulseaudio-utils
# Windows 端安装 PulseAudio

第六部分:扩展与定制

6.1 自定义烟花效果

添加新烟花类型
// 1. 在 Firework 结构体中增加类型
// 2. 在 explodeFirework() 中添加新类型处理
case 6:
    particleCount = 自定义数量;
    createCustomExplosion(fw, particleCount);
    break;
// 3. 实现新的爆炸函数
void createCustomExplosion(Firework& fw, int particleCount) {
    // 自定义爆炸算法
}
修改颜色方案
// 在 createFirework() 中修改颜色分配
switch (type) {
case 0: fw.color = sf::Color(自定义 RGB); break;
// ... 其他类型
}

6.2 自定义祝福文字

// 修改 setupText() 中的祝福文字
std::string blessingLines[3] = {
    "你的祝福语第一行",
    "你的祝福语第二行",
    "你的祝福语第三行"
};
// 修改颜色
blessingText[0].setFillColor(自定义颜色);

6.3 添加背景音乐

// 1. 添加音频头文件
#include <SFML/Audio.hpp>
// 2. 添加音乐对象
sf::Music backgroundMusic;
// 3. 加载和播放音乐
if (backgroundMusic.openFromFile("music.ogg")) {
    backgroundMusic.setLoop(true);
    backgroundMusic.play();
}

第七部分:完整编译运行示例

7.1 一键安装脚本

创建 install_and_run.sh:

#!/bin/bash
echo "=== 2026 新年烟花程序安装脚本 ==="
echo ""
# 检查 WSL 版本
echo "检查 WSL 版本..."
if ! grep -q "WSL2" /proc/version 2>/dev/null; then
    echo "警告:推荐使用 WSL2 以获得更好性能"
fi
# 安装依赖
echo "安装系统依赖..."
sudo dnf install -y gcc-c++ make SFML SFML-devel
# 下载中文字体
echo "配置中文字体..."
if [ -f "/mnt/c/Windows/Fonts/msyh.ttc" ]; then
    cp "/mnt/c/Windows/Fonts/msyh.ttc" .
    echo "微软雅黑字体已复制"
else
    echo "使用系统默认字体"
fi
# 编译程序
echo "编译烟花程序..."
g++ -std=c++11 -O3 -o fireworks fireworks.cpp \
    -lsfml-graphics -lsfml-window -lsfml-system
if [ $? -eq 0 ]; then
    echo ""
    echo "=== 编译成功! ==="
    echo ""
    echo "运行程序:./fireworks"
    echo ""
    echo "操作说明:"
    echo " 空格键 - 发射更多烟花"
    echo " 鼠标点击 - 指定位置爆炸"
    echo " 回车键 - 显示祝福语"
    echo " R 键 - 重置烟花"
    echo " ESC 键 - 退出程序"
    echo ""
else
    echo "编译失败,请检查错误信息"
fi

7.2 测试运行

# 赋予执行权限
chmod +x install_and_run.sh
# 运行安装脚本
./install_and_run.sh
# 如果一切正常,运行程序
./fireworks

最后附上完整源代码

#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;
        // 方案二:显式 UTF-8 转换 - 根据链接内容
        // 设置标题文本
        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. 2026 新年烟花庆祝程序
  2. 第一部分:环境准备
  3. 1.1 系统要求
  4. 1.2 安装依赖
  5. 1. 更新系统包管理器
  6. 2. 安装 C++ 编译器和构建工具
  7. 3. 安装 SFML 图形库(注意:Fedora 中 SFML 包名可能有差异)
  8. 如果上述 SFML 安装失败,尝试以下命令:
  9. 4. 安装中文字体(可选,但推荐)
  10. 1.3 验证安装
  11. 验证 g++ 版本
  12. 验证 SFML 是否安装成功(检查头文件路径)
  13. 第二部分:代码结构详解
  14. 2.1 整体架构
  15. 2.2 核心数据结构
  16. 2.2.1 Particle 结构体
  17. 2.2.2 Firework 结构体
  18. 2.3 核心算法解析
  19. 2.3.1 烟花爆炸算法
  20. 2.3.2 心形烟花算法
  21. 2.3.3 UTF-8 中文显示解决方案
  22. 2.4 物理模拟系统
  23. 2.4.1 运动更新
  24. 2.4.2 尾迹效果
  25. 第三部分:编译与运行
  26. 3.1 创建项目文件
  27. 将完整代码保存为 fireworks.cpp
  28. 粘贴代码,按 Ctrl+X,然后 Y,回车保存
  29. 3.2 编译命令
  30. 基本编译命令
  31. 如果遇到链接错误,尝试完整路径
  32. 3.3 编译脚本
  33. 检查依赖
  34. 编译
  35. 检查编译结果
  36. 3.4 字体配置
  37. 方案 1:从 Windows 复制字体
  38. 方案 2:创建字体链接
  39. 方案 3:修改代码中的字体路径
  40. 在 loadFont() 函数中添加当前目录字体路径
  41. 第四部分:程序操作指南
  42. 4.1 启动程序
  43. 编译后直接运行
  44. 4.2 操作快捷键
  45. 4.3 视觉效果说明
  46. 4.3.1 开场效果
  47. 4.3.2 烟花类型
  48. 4.3.3 祝福语效果
  49. 4.4 性能优化提示
  50. 4.4.1 帧率调整
  51. 4.4.2 粒子数量控制
  52. 4.4.3 同时显示烟花数量
  53. 第五部分:故障排除
  54. 5.1 常见编译错误
  55. 错误 1:SFML 库找不到
  56. 确认 SFML 安装
  57. 检查头文件位置
  58. 错误 2:链接错误
  59. 确保链接所有必要的 SFML 库
  60. 5.2 运行时问题
  61. 问题 1:窗口无法打开
  62. 问题 2:中文显示为方框
  63. 问题 3:程序运行缓慢
  64. 5.3 WSL 特定配置
  65. X11 转发配置
  66. 在 Windows 端安装 VcXsrv
  67. 启动 XLaunch,选择 "Disable access control"
  68. 在 WSL 中设置 DISPLAY 变量
  69. 测试 X11 转发
  70. 音频支持(可选)
  71. 安装脉冲音频支持
  72. Windows 端安装 PulseAudio
  73. 第六部分:扩展与定制
  74. 6.1 自定义烟花效果
  75. 添加新烟花类型
  76. 修改颜色方案
  77. 6.2 自定义祝福文字
  78. 6.3 添加背景音乐
  79. 第七部分:完整编译运行示例
  80. 7.1 一键安装脚本
  81. 检查 WSL 版本
  82. 安装依赖
  83. 下载中文字体
  84. 编译程序
  85. 7.2 测试运行
  86. 赋予执行权限
  87. 运行安装脚本
  88. 如果一切正常,运行程序
  89. 最后附上完整源代码

更多推荐文章

查看全部
  • 开源版 Coze 工作流创建与使用指南
  • 基于 Spring AI + DeepSeek 构建企业级 AI Agent 服务与底层原理
  • DeepSeek-R1 开源大模型推理优化实战方案
  • Python 依赖注入(DI)三种实现方式与可测试性实践
  • WebODM 免费开源无人机影像处理全流程指南
  • 基于 AI 辅助的在线考试系统全流程开发实战
  • HarmonyOS6 RcInput 组件核心架构与类型系统设计
  • Qwen3Guard-Gen-WEB 全球多语言内容合规部署实测
  • 基于 Pandas 与 Pyecharts 的全国星巴克门店数据可视化分析
  • Windows 系统下 VSCode 配置 C/C++ 开发环境实战
  • Java Web 开发基础与 Servlet 核心技术
  • RAG 知识库实战:基于 LangChain 与 Milvus 构建本地检索系统
  • 多款实用 Python 脚本实现工作自动化
  • Patch Position Embedding (PPE) 在医疗 AI 中的应用分析
  • 国内环境部署 OpenClaw 个人 AI 助手搭建指南
  • C++ 就业导向的系统学习路径指南
  • 二叉树入门:概念、存储与 C 语言实现
  • 数据结构:队列的完整实现
  • 哈希表详解:从哈希函数到冲突解决的 C++ 实现
  • 渐进式 AIGC 聚合系统:多模态生成与 Agent 工作流私有化部署方案

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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