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

本程序设计在 WSL(Windows Subsystem for Linux)的 Fedora 系统上运行,需要以下环境:
在 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
安装完成后,验证关键组件:
# 验证 g++ 版本
g++ --version
# 验证 SFML 是否安装成功(检查头文件路径)
ls /usr/include/SFML/
程序采用面向对象设计,主要包含以下类:
Particle:粒子结构体,代表单个烟花粒子Firework:烟花结构体,包含多个粒子TextParticle:文字粒子结构体FireworksDisplay:主显示类,管理所有烟花和界面struct Particle {
sf::Vector2f position; // 粒子位置
sf::Vector2f velocity; // 粒子速度
sf::Color color; // 粒子颜色
float lifetime; // 当前寿命
float maxLifetime; // 最大寿命
float size; // 粒子大小
bool isTrail; // 是否为尾迹粒子
};
功能说明:
isTrail标志区分爆炸粒子和尾迹粒子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)采用大量细小粒子散射算法:
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);
}
}
算法特点:
基于心形参数方程创建爱心形状:
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);
// ... 其他属性设置
}
}
根据链接内容,采用显式 UTF-8 转换解决中文乱码:
// 使用 sf::String::fromUtf8() 进行显式转换
std::string titleUtf8 = "2026 新春快乐 - 烟花庆祝";
titleText.setString(sf::String::fromUtf8(titleUtf8.begin(), titleUtf8.end()));
关键点:
sf::String::fromUtf8() 转换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;
}
// 尾迹粒子生成
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;
}
mkdir fireworks2026
cd fireworks2026
# 将完整代码保存为 fireworks.cpp
nano fireworks.cpp
# 粘贴代码,按 Ctrl+X,然后 Y,回车保存
# 基本编译命令
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 新年烟花程序..."
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
如果中文显示异常,配置字体:
# 方案 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");
# 编译后直接运行
./fireworks
| 按键 | 功能 | 效果描述 |
|---|---|---|
| 空格键 | 发射更多烟花 | 随机位置发射 5 个不同类型烟花 |
| 鼠标点击 | 指定位置爆炸 | 在点击位置立即爆炸烟花 |
| 回车键 | 显示祝福语 | 显示彩色祝福文字动画 |
| R 键 | 重置烟花 | 重新开始开场表演 |
| ESC 键 | 退出程序 | 安全退出烟花程序 |
按回车键触发:
文字内容:
新的一年,愿你 马不停蹄奔赴山海,
一马当先奔向热爱!
// 在构造函数中调整帧率
window.setFramerateLimit(60); // 默认 60FPS
// 如果需要更高帧率(性能允许)
window.setFramerateLimit(120);
// 调整普通烟花粒子数量
particleCount = 2000 + static_cast<int>(1000 * fw.sizeFactor);
// 如果需要降低性能消耗,减少粒子数量
particleCount = 1000 + static_cast<int>(500 * fw.sizeFactor);
// 控制最大烟花数量
if (fireworks.size() < 25) { // 当前限制 25 个
// 创建新烟花
}
fatal error: SFML/Graphics.hpp: No such file or directory
解决方案:
# 确认 SFML 安装
sudo dnf install SFML-devel
# 检查头文件位置
find /usr -name "Graphics.hpp" 2>/dev/null
undefined reference to `sf::...
解决方案:
# 确保链接所有必要的 SFML 库
g++ ... -lsfml-graphics -lsfml-window -lsfml-system -lsfml-audio
Failed to open window
解决方案:
解决方案:
解决方案:
# 在 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
// 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;
// ... 其他类型
}
// 修改 setupText() 中的祝福文字
std::string blessingLines[3] = {
"你的祝福语第一行",
"你的祝福语第二行",
"你的祝福语第三行"
};
// 修改颜色
blessingText[0].setFillColor(自定义颜色);
// 1. 添加音频头文件
#include <SFML/Audio.hpp>
// 2. 添加音乐对象
sf::Music backgroundMusic;
// 3. 加载和播放音乐
if (backgroundMusic.openFromFile("music.ogg")) {
backgroundMusic.setLoop(true);
backgroundMusic.play();
}
创建 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
# 赋予执行权限
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;
}

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online