🐍 快过年了,祝大家蛇年大吉,希望新的一年能有人陪你一同看烟火!!!
还不快点赞!!!
这次的动画不仅包含烟花燃放,还有万家灯火,愿家常在心中!!!
TypeScript版本在这里:TypeScript新年烟花代码(附源码,趣味编程)
背景介绍
烟花燃放效果是一种极具视觉冲击力的动画效果,常用于庆祝节日、活动开场或游戏特效。使用 C++ 和 SFML 实现烟花效果,不仅可以提升程序的性能,还能通过代码的可维护性和扩展性,为开发者带来更高效的开发体验。本文将详细介绍如何使用 C++ 和 SFML 实现一个绚丽的新年烟花燃放效果,并探讨其在实际项目中的应用场景。
在实现过程中,我们使用了以下关键技术和库:
-
C++17:提供高性能和面向对象编程的支持。
-
SFML 3.0:一个跨平台的多媒体库,支持图形、音频、输入和网络等功能。
-
随机数生成器:用于生成随机的烟花位置、颜色和粒子运动轨迹。
调试环境
本次代码调试环境为 2023 款 MacBook Pro,搭载苹果M2 Pro芯片,32GB内存,操作系统为macOs Ventura 13.5.2。Mac平台可直接编译运行,其他平台测试运行需确保以下两点:
- C++17 及 SFML3.0 开发环境正常配置
- 修改编译指令中的 SFML 头文件及库所在路径
技术要点
实现烟花燃放效果需要掌握以下关键技术,它们共同构成了实现这一视觉效果的基础。
1. C++17 语言特点及优势
C++17 是继 C++14 之后的一个重要版本,引入了许多新特性和改进,旨在提高编程效率、简化代码以及增强标准库的功能。它在实现烟花效果中的主要优势包括:
-
高性能:C++17 提供了高效的内存管理和计算性能,适合实时图形渲染。
-
面向对象编程:支持类和对象,便于将烟花、粒子等逻辑封装为独立的模块。
-
标准库改进:例如
std::vector
和std::map
的性能优化,以及新特性如std::optional
和std::variant
。 -
新特性支持:如结构化绑定、条件初始化语句等。
2. SFML 3.0 简介
SFML(Simple and Fast Multimedia Library)是一个跨平台的 C++ 库,旨在简化多媒体应用程序的开发。本次开发基于 SFML 3.0,它引入了许多新特性和改进:
-
C++17 支持:SFML 3.0 已全面更新以支持 C++17。
-
改进的事件处理 API:提供了更强大的事件处理机制。
-
音频模块更新:用 miniaudio 替代了 OpenAL。
-
剪切和模板测试:支持更复杂的图形渲染。
-
依赖管理改进:通过 CMake 的
FetchContent
动态获取和构建依赖。
3. 数学运算与物理模拟
烟花效果的核心是物理模拟,涉及以下数学和物理知识:
-
三角函数:通过
sin
和cos
计算粒子的初始速度和方向,模拟烟花爆炸的向外扩散效果。 -
向量运算:更新粒子的位置和速度,模拟粒子的运动轨迹。
-
物理公式:模拟重力加速度(
vy += 0.004
),使粒子在垂直方向上逐渐下落。
4. 随机数生成器
随机性是烟花效果的关键,通过 rand()
实现以下功能:
-
随机生成粒子的位置、速度和颜色:使每次烟花爆炸的效果都不尽相同。
-
随机生成建筑物和窗户:为背景添加随机性,增强视觉效果。
-
随机触发烟花爆炸:每隔一段时间随机生成新的烟花,增加动态效果。
5. 动画与帧循环
动画效果的实现依赖于高效的帧循环,核心技术包括:
-
window.display()
:SFML 提供的 API,用于更新窗口内容。 -
帧循环逻辑:在每一帧中更新粒子状态、清除画布并重新绘制,实现动态效果。
6. 状态管理与模块化
虽然烟花效果主要依赖 SFML 和 C++,但通过面向对象编程,我们实现了模块化开发的优势:
-
状态管理:通过类和对象管理粒子和建筑物的状态,避免不必要的重新计算。
-
事件处理:通过 SFML 的事件系统(如鼠标点击事件)增强交互性,允许用户通过点击触发烟花爆炸。
通过以上技术的结合,我们能够实现一个高效、可维护且视觉效果绚丽的新年烟花燃放效果。
完整代码
以下是实现新年烟花燃放效果的完整代码:
fireworks.cpp (核心逻辑实现)
// main.cpp
#include <SFML/Graphics.hpp>
#include <vector>
#include <random>
#include <cmath>
#include <memory>
#include <iostream>
#include <sstream>
// 数据结构定义
struct Window {
float x;
float y;
bool isLit;
Window(float _x, float _y, bool _isLit) : x(_x), y(_y), isLit(_isLit) {}
};
struct Building {
float x;
float y;
float width;
float height;
std::vector<Window> windows;
Building(float _x, float _y, float _w, float _h) : x(_x), y(_y), width(_w), height(_h) {}
};
struct Particle {
float x;
float y;
float vx;
float vy;
float alpha;
sf::Color color;
float life;
Particle(float _x, float _y, float _vx, float _vy, sf::Color _color)
: x(_x), y(_y), vx(_vx), vy(_vy), alpha(1.f), color(_color), life(90.f + static_cast<float>(rand()) / RAND_MAX * 30.f) {}
};
class FireworkCanvas {
private:
sf::RenderWindow window;
std::vector<Building> buildings;
std::vector<Particle> particles;
sf::Font font;
// 颜色配置,保持与 TypeScript 版本一致,拥有不同色系
std::vector<std::vector<sf::Color>> colors = {
{hexToColor("#f0f000"), hexToColor("#0ff000"), hexToColor("#ff4081"), hexToColor("#00e676")},
{hexToColor("#FF3333"), hexToColor("#FF6666"), hexToColor("#FF9999"), hexToColor("#FFCCCC")},
{hexToColor("#FF0033"), hexToColor("#FF3366"), hexToColor("#FF6699"), hexToColor("#FF99CC")},
{hexToColor("#FF0066"), hexToColor("#FF3399"), hexToColor("#FF66CC"), hexToColor("#FF99FF")},
{hexToColor("#FF9933"), hexToColor("#FFCC33"), hexToColor("#FFFF33"), hexToColor("#FFFF66")},
{hexToColor("#FFCC66"), hexToColor("#FF9900"), hexToColor("#FF6600"), hexToColor("#FF3300")},
{hexToColor("#FFFF99"), hexToColor("#FFFFCC"), hexToColor("#FFFF00"), hexToColor("#FFFF33")},
{hexToColor("#33FF33"), hexToColor("#66FF66"), hexToColor("#99FF99"), hexToColor("#CCFFCC")},
{hexToColor("#00FF00"), hexToColor("#33FF66"), hexToColor("#66FF99"), hexToColor("#99FFCC")},
{hexToColor("#3333FF"), hexToColor("#6666FF"), hexToColor("#9999FF"), hexToColor("#CCCCFF")},
{hexToColor("#0000FF"), hexToColor("#3366FF"), hexToColor("#6699FF"), hexToColor("#99CCFF")},
{hexToColor("#9933FF"), hexToColor("#CC33FF"), hexToColor("#FF33FF"), hexToColor("#FF66FF")},
{hexToColor("#6600FF"), hexToColor("#9900FF"), hexToColor("#CC00FF"), hexToColor("#FF00FF")},
{hexToColor("#80FF00"), hexToColor("#00FF80"), hexToColor("#0080FF"), hexToColor("#8000FF")},
{hexToColor("#FF0080"), hexToColor("#FF8000"), hexToColor("#00FFFF"), hexToColor("#FF00FF")}
};
// 辅助函数:将十六进制颜色字符串转换为 sf::Color
sf::Color hexToColor(const std::string& hex) {
unsigned int color;
std::istringstream(hex.substr(1)) >> std::hex >> color;
return sf::Color((color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF);
}
void initBuildings() {
const float screenWidth = static_cast<float>(window.getSize().x);
const float screenHeight = static_cast<float>(window.getSize().y);
const int buildingCount = static_cast<int>(screenWidth / 100);
for (int i = 0; i < buildingCount; i++) {
float width = 60.f + static_cast<float>(rand()) / RAND_MAX * 40.f;
float height = 100.f + static_cast<float>(rand()) / RAND_MAX * 200.f;
float x = i * (width + 20.f);
float y = screenHeight - height;
Building building(x, y, width, height);
// 创建窗户
int windowRows = static_cast<int>(height / 30);
int windowCols = static_cast<int>(width / 20);
for (int row = 0; row < windowRows; row++) {
for (int col = 0; col < windowCols; col++) {
building.windows.emplace_back(
col * 20.f + 5.f,
row * 30.f + 10.f,
static_cast<float>(rand()) / RAND_MAX > 0.5f
);
}
}
buildings.push_back(building);
}
}
// 在指定位置创建烟花粒子
void createParticles(float x, float y) {
const auto& colorGroup = colors[rand() % colors.size()];
for (int i = 0; i < 100; i++) {
float angle = (M_PI * 2.f * i) / 50.f;
float speed = 0.25f + static_cast<float>(rand()) / RAND_MAX * 0.75f;
particles.emplace_back(
x,
y,
std::cos(angle) * speed,
std::sin(angle) * speed,
colorGroup[rand() % colorGroup.size()]
);
}
}
void updateWindows() {
if (static_cast<float>(rand()) / RAND_MAX > 0.1f) return;
if (buildings.empty()) return;
auto& building = buildings[rand() % buildings.size()];
int updateCount = 1 + rand() % 2;
for (int i = 0; i < updateCount; i++) {
if (!building.windows.empty()) {
auto& window = building.windows[rand() % building.windows.size()];
window.isLit = !window.isLit;
}
}
}
void drawBuildings() {
for (const auto& building : buildings) {
// 绘制建筑物主体
sf::RectangleShape buildingShape({building.width, building.height});
buildingShape.setPosition({building.x, building.y});
buildingShape.setFillColor(sf::Color(26, 26, 26));
window.draw(buildingShape);
// 绘制窗户
for (const auto& win : building.windows) {
if (win.isLit) {
// 绘制窗户光晕
sf::CircleShape glow(3.f);
glow.setPosition({building.x + win.x - 5.f, building.y + win.y - 5.f});
glow.setFillColor(sf::Color(255, 229, 180, 100));
window.draw(glow);
}
// 绘制窗户本体
sf::RectangleShape windowShape({10.f, 10.f});
windowShape.setPosition({building.x + win.x, building.y + win.y});
windowShape.setFillColor(win.isLit ? sf::Color(255, 229, 180) : sf::Color(51, 51, 51));
window.draw(windowShape);
}
}
}
void draw2025Text() {
sf::Text text(font, L"2025 蛇年大吉", 80);
text.setFillColor(sf::Color(255, 229, 180));
text.setStyle(sf::Text::Bold);
// 居中显示
sf::FloatRect textRect = text.getLocalBounds();
text.setOrigin({textRect.position.x + textRect.size.x/2.0f, textRect.position.y + textRect.size.y/2.0f});
text.setPosition({window.getSize().x/2.0f, 250.f});
// 绘制发光效果
for (int i = 10; i > 0; --i) {
sf::Text glowText = text;
glowText.setFillColor(sf::Color(255, 229, 180, 25 * i));
glowText.move({0.f, 0.f});
window.draw(glowText);
}
window.draw(text);
}
public:
FireworkCanvas() : window(sf::VideoMode::getFullscreenModes()[0], "Fireworks", sf::State::Fullscreen) {
window.setFramerateLimit(60);
// 加载字体,确保字体文件与可执行文件在同一目录下(字体需支持中文)
if (!font.openFromFile("babyfont.ttf")) {
throw std::runtime_error("Could not load font!");
}
// 初始化建筑物
initBuildings();
}
void run() {
while (window.isOpen()) {
sf::Event event(sf::Event::Closed);
while (const auto event = window.pollEvent()) {
if (event->is<sf::Event::Closed>() || (event->is<sf::Event::KeyPressed>() && event->getIf<sf::Event::KeyPressed>()->code == sf::Keyboard::Key::Escape)) {
window.close();
}
else if (const auto* mouseButton = event->getIf<sf::Event::MouseButtonPressed>()) {
createParticles(mouseButton->position.x, mouseButton->position.y);
}
}
// 清空屏幕
window.clear(sf::Color::Black);
// 绘制建筑物
drawBuildings();
// 绘制2025文字
draw2025Text();
// 更新窗户状态
updateWindows();
// 更新和绘制粒子
for (auto it = particles.begin(); it != particles.end();) {
it->x += it->vx;
it->y += it->vy;
it->vy += 0.005f;
it->alpha = it->life / 120.f;
it->life -= 0.6f;
if (it->life > 0) {
sf::CircleShape particle(2.f);
particle.setPosition({it->x, it->y});
sf::Color color = it->color;
color.a = static_cast<std::uint8_t>(255 * it->alpha);
particle.setFillColor(color);
window.draw(particle);
++it;
} else {
it = particles.erase(it);
}
}
// 随机生成烟花
if (static_cast<float>(rand()) / RAND_MAX < 0.03f) {
float x = static_cast<float>(rand()) / RAND_MAX * window.getSize().x;
float y = static_cast<float>(rand()) / RAND_MAX * window.getSize().y * 0.3f;
createParticles(x, y);
}
window.display();
}
}
};
int main() {
srand(static_cast<unsigned>(time(nullptr)));
try {
FireworkCanvas app;
app.run();
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}
编译脚本 build.sh(for mac系统,windows及linux需自行更换 SFML 头文件及库路径)
#!/bin/bash
g++ -std=c++17 fireworks.cpp -o fireworks -I/opt/homebrew/include -L/opt/homebrew/lib -lsfml-graphics -lsfml-window -lsfml-system
源码下载
您可以通过以下链接访问和下载完整的代码: 源码压缩包链接
代码分析
以下是对代码实现的详细解析,帮助理解新年烟花燃放效果的核心逻辑和实现方式。
1. 程序初始化与显示设置
在 FireworkCanvas
类中,我们使用 SFML 的 sf::RenderWindow
初始化程序,并设置窗口大小以适应屏幕。同时,我们通过 initBuildings
函数生成建筑物和窗户,为烟花效果提供背景。
FireworkCanvas() : window(sf::VideoMode::getFullscreenModes()[0], "Fireworks", sf::State::Fullscreen) {
window.setFramerateLimit(60);
// 加载字体,确保字体文件与可执行文件在同一目录下(字体需支持中文)
if (!font.loadFromFile("babyfont.ttf")) {
throw std::runtime_error("Could not load font!");
}
// 初始化建筑物
initBuildings();
}
解析:
-
sf::RenderWindow
:创建一个全屏窗口,用于绘制烟花效果。 -
font.loadFromFile
:加载字体文件,用于绘制新年祝福语,这里的babyfont.ttf可在资源文件获取。 -
initBuildings
:动态生成建筑物和窗户,模拟城市夜景。
2. 建筑物的初始化与绘制
建筑物是烟花效果的背景,通过随机生成的矩形和窗户来模拟城市夜景。
void initBuildings() {
const float screenWidth = static_cast<float>(window.getSize().x);
const float screenHeight = static_cast<float>(window.getSize().y);
const int buildingCount = static_cast<int>(screenWidth / 100);
for (int i = 0; i < buildingCount; i++) {
float width = 60.f + static_cast<float>(rand()) / RAND_MAX * 40.f;
float height = 100.f + static_cast<float>(rand()) / RAND_MAX * 200.f;
float x = i * (width + 20.f);
float y = screenHeight - height;
Building building(x, y, width, height);
// 创建窗户
int windowRows = static_cast<int>(height / 30);
int windowCols = static_cast<int>(width / 20);
for (int row = 0; row < windowRows; row++) {
for (int col = 0; col < windowCols; col++) {
building.windows.emplace_back(
col * 20.f + 5.f,
row * 30.f + 10.f,
static_cast<float>(rand()) / RAND_MAX > 0.5f
);
}
}
buildings.push_back(building);
}
}
解析:
-
建筑物生成逻辑:根据屏幕宽度动态生成建筑物,每栋建筑物的宽度和高度随机生成。
-
窗户生成逻辑:每栋建筑物包含多个窗户,窗户是否点亮通过随机概率决定。
-
buildings
:将生成的建筑物存储在std::vector
中,便于后续绘制。
3. 烟花的生成与粒子系统
烟花效果的核心是粒子系统,每个烟花由多个粒子组成,粒子的运动和衰减模拟了烟花的爆炸效果。
void createParticles(float x, float y) {
const auto& colorGroup = colors[rand() % colors.size()];
for (int i = 0; i < 100; i++) {
float angle = (M_PI * 2.f * i) / 50.f;
float speed = 0.25f + static_cast<float>(rand()) / RAND_MAX * 0.75f;
particles.emplace_back(
x,
y,
std::cos(angle) * speed,
std::sin(angle) * speed,
colorGroup[rand() % colorGroup.size()]
);
}
}
解析:
-
粒子初始化:每个烟花生成 100 个粒子,粒子的初始位置为烟花的中心。
-
速度和方向:通过三角函数计算粒子的初始速度和方向,模拟烟花爆炸的向外扩散效果。
-
颜色和生命周期:粒子的颜色从预定义的色彩数组中随机选择,生命周期决定了粒子的存活时间。
4. 粒子的更新与绘制
粒子的更新和绘制逻辑在 run
函数中实现,通过物理公式模拟粒子的运动和衰减。
for (auto it = particles.begin(); it != particles.end();) {
it->x += it->vx;
it->y += it->vy;
it->vy += 0.005f;
it->alpha = it->life / 120.f;
it->life -= 0.6f;
if (it->life > 0) {
sf::CircleShape particle(2.f);
particle.setPosition({it->x, it->y});
sf::Color color = it->color;
color.a = static_cast<std::uint8_t>(255 * it->alpha);
particle.setFillColor(color);
window.draw(particle);
++it;
} else {
it = particles.erase(it);
}
}
解析:
-
物理模拟:粒子的水平速度保持不变,垂直速度通过重力加速度逐渐增加,模拟自然下落效果。
-
透明度和生命周期:粒子的透明度随生命周期减少,模拟烟花的逐渐消散。
-
绘制:使用
sf::CircleShape
绘制圆形粒子,并通过setFillColor
设置颜色和透明度。
5. 动画循环
动画循环通过 SFML 的 window.display()
实现,每次循环中清除画布、更新粒子状态并重新绘制。
void run() {
while (window.isOpen()) {
sf::Event event(sf::Event::Closed);
while (const auto event = window.pollEvent()) {
if (event->is<sf::Event::Closed>() || (event->is<sf::Event::KeyPressed>() && event->getIf<sf::Event::KeyPressed>()->code == sf::Keyboard::Key::Escape)) {
window.close();
}
else if (const auto* mouseButton = event->getIf<sf::Event::MouseButtonPressed>()) {
createParticles(mouseButton->position.x, mouseButton->position.y);
}
}
// 清空屏幕
window.clear(sf::Color::Black);
// 绘制建筑物
drawBuildings();
// 绘制2025文字
draw2025Text();
// 更新窗户状态
updateWindows();
// 更新和绘制粒子
// ... 粒子更新代码
// 随机生成烟花
if (static_cast<float>(rand()) / RAND_MAX < 0.03f) {
float x = static_cast<float>(rand()) / RAND_MAX * window.getSize().x;
float y = static_cast<float>(rand()) / RAND_MAX * window.getSize().y * 0.3f;
createParticles(x, y);
}
window.display();
}
}
解析:
-
清除画布:使用
window.clear
清空屏幕,保留部分残影,增强视觉效果。 -
随机生成烟花:每隔一段时间随机生成新的烟花,增加动态效果。
-
帧循环:通过
window.display()
实现持续的动画效果。
6. 总结
通过 C++17 和 SFML 3.0,我们实现了一个绚丽的新年烟花燃放效果。核心逻辑包括:
-
使用 SFML 绘制建筑物和烟花。
-
通过粒子系统模拟烟花的爆炸和衰减。
-
使用物理公式和随机数生成器实现动态效果。
-
利用 SFML 的帧循环实现流畅的动画效果。
这种实现方式不仅展示了 C++17 的高性能和代码结构化优势,还通过面向对象编程提升了代码的可维护性。
SFML 的发展与应用场景
SFML(Simple and Fast Multimedia Library)自发布以来,已经成为多媒体应用开发中的一个重要工具。它提供了简单易用的 API,支持图形绘制、音频处理、输入处理和网络通信等功能。随着版本的不断更新,SFML 的功能和性能也在不断提升。
SFML 的发展历程
-
2007 年:SFML 1.0 发布,最初支持 Windows 和 Linux 平台。
-
2012 年:SFML 2.0 发布,引入了对 OpenGL 的支持,并改进了音频模块。
-
2023 年:SFML 3.0 发布,引入了多项重大改进,包括对 C++17 的全面支持、改进的事件处理 API、音频模块的更新(使用 miniaudio 替代 OpenAL)以及对剪切和模板测试的支持。
SFML 的应用场景
-
游戏开发:SFML 提供了丰富的图形和音频功能,适合开发 2D 游戏。
-
多媒体应用:支持音频播放和图形渲染,可用于开发音乐播放器、视频播放器等。
-
教育和学习:其简单易用的 API 使其成为学习 C++ 和多媒体编程的理想工具。
-
科学可视化:可以用于绘制图表、模拟物理现象等。
SFML 的优势
-
跨平台:支持 Windows、Linux 和 macOS。
-
高性能:基于 OpenGL 和 OpenAL,提供高效的图形和音频处理。
-
易于上手:提供了简单直观的 API,适合初学者和专业开发者。
注意事项
-
字体文件:确保字体文件(如
babyfont.ttf
)与可执行文件在同一目录下,并支持中文显示。 -
依赖库:确保安装了 SFML 3.0 库,并正确配置了编译环境。
-
C++17 支持:本次开发基于 C++17,确保编译器支持 C++17 标准。
写在后面
感谢您的阅读!如果您在实现过程中有任何问题或建议,欢迎随时反馈。开发这个烟花效果的过程充满了乐趣,希望您也能享受其中。如果您喜欢这个效果,不妨分享到您的项目中!
版权声明
本文遵循 Creative Commons Attribution-ShareAlike 4.0 International License 协议。您可以自由使用和修改代码,但需注明原作者和出处。