小游戏和GUI编程(4) | 基于 SFML 的黑客帝国字符雨
文章目录
- 小游戏和GUI编程(4) | 基于 SFML 的黑客帝国字符雨
- 1. 简介
- 2. 规划
- 3. 一个字符的下落
- 3. 一个雨滴的下落
- 4. 每个雨滴都是独一无二的
- 5. 重构后, 降落多个雨滴
- 6. 总结
- 7. 参考
1. 简介
使用 SFML 实现黑客帝国字符雨的动态下落效果。
2. 规划
- 一个字符下落
- 一个雨滴的下落
- 每个雨滴都是独一无二的
- 重构后, 降落多个雨滴
3. 一个字符的下落
显示 SFML 窗口, 在窗口的中轴线上绘制一个降落的 H
字母, 字母是绿色的, 背景是黑色的。 每更新一帧, H
的位置就下降 16 个像素。 H
字母的大小也是 16 像素。
由于降落的比较快, 手动控制帧率为 10 FPS.
关键代码:
window.setFramerateLimit(10);...window.clear();sf::Text text;
text.setFont(font);
text.setFillColor(sf::Color::Green);
text.setCharacterSize(16);
text.setString(std::string(1, 'H'));
text.setPosition(x, y);
y = (y + 16) % win_height;
window.draw(text);window.display();
完整代码:
#include <SFML/Graphics.hpp>int main()
{constexpr int win_width = 640;constexpr int win_height = 480;sf::VideoMode videomode(win_width, win_height);const std::string title = "Matrix Rain SFML";sf::RenderWindow window(videomode, title);window.setFramerateLimit(10);sf::Font font;const std::string asset_dir = "../Resources";if (!font.loadFromFile(asset_dir + "/Arial.ttf")){printf("Error: font not found\n");return 1;}int x = win_width / 2;int y = 0;while (window.isOpen()){sf::Event event;while (window.pollEvent(event)){if (event.type == sf::Event::Closed) { window.close(); }}window.clear();sf::Text text;text.setFont(font);text.setFillColor(sf::Color::Green);text.setCharacterSize(16);text.setString(std::string(1, 'H'));text.setPosition(x, y);y = (y + 16) % win_height;window.draw(text);window.display();}return 0;
}
3. 一个雨滴的下落
一个雨滴的下落, 说的是一个动态的过程, 由于它下落的很快, 可以同时显示它的多个位置, 因此纵向方向绘制多个字符, 就表示出了一个时间间隔内的雨滴降落轨迹。
std::string seq = "abcdefghijklmnopqrstuvwxyz";for (int i = 0; i < seq.size(); i++){sf::Text text;text.setFont(font);text.setFillColor(sf::Color::Green);text.setCharacterSize(16);text.setString(std::string(1, seq[i]));y = i * 16;text.setPosition(x, y);window.draw(text);}
雨滴轨迹中最下方的是现在的位置, 颜色最深, 最上面的颜色最浅。 因此可以根据雨滴的的长度和位置, 使用渐变的颜色, 来表达雨滴的轨迹:
std::string seq = "abcdefghijklmnopqrstuvwxyz";for (int i = 0; i < seq.size(); i++){sf::Text text;text.setFont(font);text.setFillColor(sf::Color(0, ((i+1) * 1.0/seq.size())*255, 0));text.setCharacterSize(16);text.setString(std::string(1, seq[i]));y = i * 16;text.setPosition(x, y);window.draw(text);}
雨滴的轨迹, 是从上往下降落的, 因此当我们只关注单个雨滴的颜色最深的绿色字符时, 它的位置应该是从上往下的:
int base_y = 0;while(window.isOpen())
{std::string seq = "hello world";for (int i = 0; i < seq.size(); i++){sf::Text text;text.setFont(font);text.setFillColor(sf::Color(0, ((i+1) * 1.0/seq.size())*255, 0));text.setCharacterSize(16);text.setString(std::string(1, seq[i]));y = i * 16 + base_y;text.setPosition(x, y);window.draw(text);}base_y = (base_y + 16) % win_height;
}
效果:
4. 每个雨滴都是独一无二的
每个雨滴对应的字符串是不同的(字符数量, 字符内容), 出现位置也不相同。
对于单个雨滴, 首先随机化它的出现位置, 也就是 x 坐标; 而在这个雨滴降落到窗口最下方之前, x 坐标需要保持不变; 当降落到窗口最下方时, 重新随机化 x 坐标:
x = rand() % (win_width / 16) * 16;while (window.isOpen())
{std::string seq = "hello world";for (int i = 0; i < seq.size(); i++){sf::Text text;text.setFont(font);text.setFillColor(sf::Color(0, ((i+1) * 1.0/seq.size())*255, 0));text.setCharacterSize(16);text.setString(std::string(1, seq[i]));y = i * 16 + base_y;text.setPosition(x, y);window.draw(text);}base_y = base_y + 16;if (base_y >= win_height){base_y = 0;x = rand() % (win_width / 16) * 16;}
}
对于字符内容、 字符长度, 也需要在每次雨滴到达窗口最下方的时候更新:
static std::string get_random_seq(int& seqlen)
{seqlen = rand() % 10 + 5;std::string seq(seqlen, ' ');for (int i = 0; i < seq.size(); i++){seq[i] = (rand() % 26) + 'a';}return seq;
}std::string seq = get_random_seq(seqlen);
int base_y = 0;
while (window.isOpen())
{window.clear();for (int i = 0; i < seq.size(); i++){sf::Text text;text.setFont(font);text.setFillColor(sf::Color(0, ((i+1) * 1.0/seq.size())*255, 0));text.setCharacterSize(16);text.setString(std::string(1, seq[i]));y = i * 16 + base_y;text.setPosition(x, y);window.draw(text);}base_y = base_y + 16;if (base_y >= win_height){base_y = 0;x = rand() % (win_width / 16) * 16;seq = get_random_seq(seqlen);}window.display();
}
5. 重构后, 降落多个雨滴
首先, 针对单个雨滴的降落, 把之前的面向过程的写法重构为 OOP 写法. 通过封装为 class Rain
, 提供的 drop()
方法完成了渲染, 使得 main loop 代码变得清爽:
#include <SFML/Graphics.hpp>static std::string get_random_seq(int& seqlen)
{seqlen = rand() % 10 + 5;std::string seq(seqlen, ' ');for (int i = 0; i < seq.size(); i++){seq[i] = (rand() % 26) + 'a';}return seq;
}class Rain
{
public:Rain(int a_win_width, int a_win_height, sf::Font& a_font){seqlen = rand() % 10 + 5;seq = get_random_seq(seqlen);x = rand() % (win_width / 16) * 16;font = a_font;base_y = 0;win_width = a_win_width;win_height = a_win_height;}std::string seq;int seqlen;int x;sf::Font font;int base_y;int y = 0;int win_width;int win_height;void drop(sf::RenderWindow& window){for (int i = 0; i < seq.size(); i++){sf::Text text;text.setFont(font);text.setFillColor(sf::Color(0, ((i+1) * 1.0/seq.size())*255, 0));text.setCharacterSize(16);text.setString(std::string(1, seq[i]));y = i * 16 + base_y;text.setPosition(x, y);window.draw(text);}base_y = base_y + 16;if (base_y >= win_height){base_y = 0;x = rand() % (win_width / 16) * 16;seq = get_random_seq(seqlen);}}
};int main()
{srand((unsigned)time(NULL));constexpr int win_width = 640;constexpr int win_height = 480;sf::VideoMode videomode(win_width, win_height);const std::string title = "Matrix Rain SFML";sf::RenderWindow window(videomode, title);window.setFramerateLimit(10);sf::Font font;const std::string asset_dir = "../Resources";if (!font.loadFromFile(asset_dir + "/Arial.ttf")){printf("Error: font not found\n");return 1;}Rain rain(win_width, win_height, font);while (window.isOpen()){sf::Event event;while (window.pollEvent(event)){if (event.type == sf::Event::Closed) { window.close(); }}window.clear(sf::Color::Black);rain.drop(window);window.display();}return 0;
}
其次, 为了渲染多个雨滴, 每个雨滴的都需要执行 drop()
函数。 由于 drop()
函数里的最后判断重置的代码, 和构造函数里重复了, 因此进一步提取出 reset()
成员函数。
最终代码
#include <SFML/Graphics.hpp>static std::string get_random_seq(int& seqlen)
{seqlen = rand() % 20 + 8;std::string seq(seqlen, ' ');for (int i = 0; i < seq.size(); i++){seq[i] = (rand() % 26) + 'a';}return seq;
}class Rain
{
public:Rain(int a_win_width, int a_win_height, sf::Font& a_font){srand((unsigned)time(NULL));font = a_font;win_width = a_win_width;win_height = a_win_height;reset();}void reset(){seq = get_random_seq(seqlen);x = rand() % (win_width / 16) * 16;base_y = 0;}std::string seq;int seqlen;int x;sf::Font font;int base_y;int y = 0;int win_width;int win_height;void drop(sf::RenderWindow& window){for (int i = 0; i < seq.size(); i++){sf::Text text;text.setFont(font);text.setFillColor(sf::Color(0, ((i+1) * 1.0/seq.size())*255, 0));text.setCharacterSize(16);text.setString(std::string(1, seq[i]));y = i * 16 + base_y;text.setPosition(x, y);window.draw(text);if (y >= win_height){reset();break;}}base_y += 16;}
};int main()
{constexpr int win_width = 800;constexpr int win_height = 600;sf::VideoMode videomode(win_width, win_height);const std::string title = "Matrix Rain SFML";sf::RenderWindow window(videomode, title);window.setFramerateLimit(10);sf::Font font;const std::string asset_dir = "../Resources";if (!font.loadFromFile(asset_dir + "/Arial.ttf")){printf("Error: font not found\n");return 1;}std::vector<Rain> rains(40, Rain(win_width, win_height, font));for (int i = 0; i < rains.size(); i++){rains[i].reset();}while (window.isOpen()){sf::Event event;while (window.pollEvent(event)){if (event.type == sf::Event::Closed) { window.close(); }}window.clear(sf::Color::Black);for (auto& rain: rains){rain.drop(window);}window.display();}return 0;
}
最终效果:
6. 总结
本文提供了基于 SFML 的仿黑客帝国的雨滴下落效果的的动态窗口显示, 从最简单的单个字符下落显示, 到最终的多雨滴、随机出现位置、序列长度随机化, 每一步都给出了代码和基本的解释。 在简介阶段只做了初步的任务划分, 在每一小节则进一步细化需求, 通过一步或多步实现了效果, 并在必要的时候(单个雨滴到多个雨滴)时, 通过重构代码, 降低了代码调用的复杂度, 从而为多个雨滴的降落效果提供了便利。
7. 参考
- https://www.cnblogs.com/zjutzz/p/17067234.html