小游戏和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() 函数。

    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 类, 因此可以使用

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” 的文本, 设置它为绿色, 在屏幕中央显示:

小游戏和GUI编程(3) | 基于 SFML 的字符阵-LMLPHP

关键代码

        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 在随机位置显示三个随机字母

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 擦除一个像素行

通过绘制一个和背景颜色一样(黑色)的矩形来做到。

// 画线, 擦掉一个像素行
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() 等函数:

class SFML_GRAPHICS_API RectangleShape : public Shape
{
    ...
};

3.3 确保擦除效果

和常规不一样的地方是, 需要保持前一帧的绘制内容。

因此需要去掉 window.clear() 的调用。

3.4 完整代码和效果

#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;
}

小游戏和GUI编程(3) | 基于 SFML 的字符阵-LMLPHP

4. 总结

通过查看 SFML 文档, 把字符阵的代码翻译到了基于 SFML 的实现, 关键 API 如下:

  • sf::Font::loadFromFile("xxx.ttf") 加载字体
  • sf::Text 类, 用于设置字体
  • sf::RectangleShape 类, 用于绘制单行矩形
  • 临时移除了 window.clear() 的调用

References

  • https://www.sfml-dev.org/tutorials/2.6/graphics-text.php
  • https://docs.easyx.cn/zh-cn/char-matrix
  • https://www.cnblogs.com/zjutzz/p/17067313.html
02-10 15:55