小游戏和GUI编程(3) | 基于 SFML 的字符阵
1. 简介
使用 EasyX 图形库时, 官方第一个例子是字符阵。 EasyX 不开源, 也不能跨平台, API 陈旧, API 是 C 而不是 C++。 现在使用 SFML 来实现字符阵, 克服 EasyX 的这些问题。
SFML 的 API 不如 EasyX 那么简单, 稍微复杂是因为功能更强大。 主要关注这么几个功能点: 使用 SFML 时怎样渲染文字? 怎样更新屏幕来营造字符阵的效果?
SFML 版本为 2.6.1, 原始的 EasyX 代码在这里 char-matrix, 对应的 raylib 代码在这里.
2. SFML 绘制文字
2.1 加载字体
需要先加载字体, SFML 不会扫描系统字体, 传入的是字体文件的路径。
使用 sf::Font
类, 主要用它的 loadFromFile()
函数。
cpp
sf::Font font;
const std::string asset_dir = "../Resources";
if (!font.loadFromFile(asset_dir + "/SourceHanSansCN-Regular.otf"))
{
printf("Error: font not found\n");
return 1;
}
2.2 绘制文字
使用 sf::Text
类, 它继承自 Drawable 类和 Transformable 类, 因此可以使用
cpp
class SFML_GRAPHICS_API Text : public Drawable, public Transformable
{
public:
void setFont(const Font& font); // 设置字体
void setString(const String& string); // 设置文本内容
void setCharacterSize(unsigned int size); // 设置字符大小
void setFillColor(const Color& color); // 设置字体颜色
...
};
class SFML_GRAPHICS_API Transformable
{
public:
void setPosition(float x, float y); // 设置位置
...
};
根据上述 api, 能够创建 "Hello World" 的文本, 设置它为绿色, 在屏幕中央显示:
关键代码
cpp
window.clear();
// draw the matrix here
sf::Text text;
text.setFont(font);
text.setString("Hello, World");
text.setCharacterSize(42); // in pixels
text.setFillColor(sf::Color::Green);
sf::FloatRect bbox = text.getGlobalBounds();
text.setPosition(win_width / 2 - bbox.width/2, win_height / 2 - bbox.height/2);
window.draw(text);
window.display();
3. 字符阵列
这一小节, 分析字符阵列的原理, 然后在前一节的基础绘制代码基础上进行实现。
3.1 在随机位置显示三个随机字母
cpp
int x = (rand() % 80) * 8; // [0, 640] 范围内的随机数, 间距是8
int y = (rand() % 20) * 24; // [0, 480] 范围内的随机数, 间距是24
int c = (rand() % 26) + 'a'; // [97, 122] 范围内的随机数, 也就是随机小写字母
sf::Text text;
text.setFont(font);
text.setString(std::string(1, c));
text.setCharacterSize(26); // in pixels
text.setFillColor(sf::Color::Green);
text.setPosition(x, y);
window.draw(text);
3.2 擦除一个像素行
通过绘制一个和背景颜色一样(黑色)的矩形来做到。
cpp
// 画线, 擦掉一个像素行
sf::RectangleShape line(sf::Vector2f(win_width, 2));
line.setFillColor(sf::Color::Black);
line.setPosition(0, line_index);
line_index = (line_index + 1) % win_height; // line_index 初始值为0
window.draw(line);
其中 RectangleShape 类继承自 Shape 类, 因此能调用 setFillColor()
, setPosition()
等函数:
cpp
class SFML_GRAPHICS_API RectangleShape : public Shape
{
...
};
3.3 确保擦除效果
和常规不一样的地方是, 需要保持前一帧的绘制内容。
因此需要去掉 window.clear()
的调用。
3.4 完整代码和效果
cpp
#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 = "Char Matrix SFML";
sf::RenderWindow window(videomode, title);
window.setFramerateLimit(60);
sf::Font font;
const std::string asset_dir = "../Resources";
if (!font.loadFromFile(asset_dir + "/Courier-12.ttf"))
{
printf("Error: font not found\n");
return 1;
}
int line_index = 0;
srand((unsigned)time(NULL));
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed) { window.close(); }
}
// draw the matrix here
if (0)
{
window.clear();
sf::Text text;
text.setFont(font);
text.setString("Hello, World");
text.setCharacterSize(42); // in pixels
text.setFillColor(sf::Color::Green);
sf::FloatRect bbox = text.getGlobalBounds();
text.setPosition(win_width / 2 - bbox.width/2, win_height / 2 - bbox.height/2);
window.draw(text);
}
if (1)
{
for (int i = 0; i < 3; i++)
{
int x = (rand() % 80) * 8; // [0, 640] 范围内的随机数, 间距是8
int y = (rand() % 20) * 24; // [0, 480] 范围内的随机数, 间距是24
int c = (rand() % 26) + 'a'; // [97, 122] 范围内的随机数, 也就是随机小写字母
sf::Text text;
text.setFont(font);
text.setString(std::string(1, c));
text.setCharacterSize(26); // in pixels
text.setFillColor(sf::Color::Green);
text.setPosition(x, y);
window.draw(text);
}
// 画线, 擦掉一个像素行
sf::RectangleShape line(sf::Vector2f(win_width, 2));
line.setFillColor(sf::Color::Black);
line.setPosition(0, line_index);
line_index = (line_index + 1) % win_height;
window.draw(line);
}
window.display();
}
return 0;
}
4. 总结
通过查看 SFML 文档, 把字符阵的代码翻译到了基于 SFML 的实现, 关键 API 如下:
sf::Font::loadFromFile("xxx.ttf")
加载字体sf::Text
类, 用于设置字体sf::RectangleShape
类, 用于绘制单行矩形- 临时移除了
window.clear()
的调用