Evgenii Legotckoi
Evgenii Legotckoi07 жовтня 2017 р. 16:46

SFML - Підручник 001. Підключення бібліотеки SFML до проекту на Qt

SFML є об'єктно-орієнтованим аналогом SDL. Бібліотека надає простий доступ до систем введення виводу, аудіо, мережі передачі даних, а також до функціоналу OpenGL.

А якщо додатково прикрутити до цієї бібліотеки ще й функціонал Qt, отримати в управління потужні можливості сигналів і слотів, ресурсних файлів Qt? Що для цього потрібно?

  1. Завантажити з GitHub останню версію вихідних файлів SFML .
  2. Скомпілювати вихідники до бібліотеки
  3. Створити проект Qt
  4. Підключити бібліотеку до проекту
  5. Написати віджет, який рендеруватиметься безпосередньо через функціонал SFML

Складання SFML

Взагалі, можна скачати передкомпільовані бібліотеки SFML, але якщо у Вас буде компілятор, відмінний від того, яким було зібрано ці бібліотеки, то нічого не запрацює. Тому не експериментуватимемо, а відразу зберемо бібліотеку з тим компілтором, яким будемо проводити складання проекту.

SFML представлений проектом із використанням системи складання CMAKE, який без проблем відкривається у Qt Creator.

Коли проект відкритий, здійснюємо запуск CMAKE, а потім складання самої бібліотеки, як у режим Debug, так і в режим Release, щоб мати два комплекти бібіліотек.

У каталозі збірки буде каталог lib, в якому буде знаходиться цікаві для нас бібліотеки. Їх Ми додамо до проекту на Qt, у якому буде використовуватися SFML.

Будуть зібрані такі бібліотеки:

  • smfl-аудіо
  • sfml-графіка
  • sfml-мережа
  • sfml-вікно
  • sfml-система

Qt проект

Далі створюємо новий Qt-проект. Створимо проект з використанням QWidget як основне вікно програми. Після того, як проект створений, додайте в корінь проекту каталог SFML, в якому будуть міститися дві директорії: lib і include.

У каталог lib необхідно помістити всі скомпіловані бібілотеки SFML як для debug версії, так і для release версії. А в каталог include необхідно помістити файли з того самого каталогу include, який знаходиться у проекті вихідних записів бібліотеки SFML.

В результаті структура даного каталогу у Qt проекті буде виглядати наступним чином.

Після того, як бібліотека підготовлена, її необхідно підключити до Qt проекту через pro файл.

Для цього пропишемо наступні рядки у pro файлі:

win32:CONFIG(release, debug|release): LIBS += -L$$PWD/SFML/lib/ -lsfml-audio -lsfml-graphics -lsfml-network -lsfml-window -lsfml-system
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/SFML/lib/ -lsfml-audio-d -lsfml-graphics-d -lsfml-network-d -lsfml-window-d -lsfml-system-d
else:unix: LIBS += -L$$PWD/SFML/lib/ -lsfml-audio -lsfml-graphics -lsfml-network -lsfml-window -lsfml-system

INCLUDEPATH += $$PWD/SFML/include
DEPENDPATH += $$PWD/SFML/include

$$PWD - означає місцезнаходження pro файлу проекту, в якому ми прописуємо конфігурацію проекту та файли, що підключаються. Тобто всі необхідні файли бібліотеки будемо шукати щодо pro-файлу проекту.

А тепер почнемо написання самого проекту. Створимо SFML віджет, який буде розміщуватися всередині основного віджету програми, щоб показати, що для використання SFML не обов'язково віддавати все вікно програми.

Структура проекту буде наступною:

Як бачите, тут є основний віджет вікна Widget, а також є віджет qsqmlcanvas, в якому буде здійснюватися робота бібліотеки SFML. Також я додав зображення через ресурсний файл Qt... Так! Це можливо! Можливо використовувати ресурсні файли Qt у бібліотеці SFML, просто потрібно написати кілька додаткових рядків коду для правильного вилучення зображень із ресурсного файлу.

А тепер приступимо до створення проекту з використанням Qt та SFML. Було вирішено зробити додаток, у SFML віджеті якого буде крутитися логотип Qt.

Профайл проекту та main.cpp

Про підключення бібліотеки до проекту було сказано вище, а файл main.cpp не зазнає жодних змін, тому не буде акцентувати увагу на них.

widget.h

У цьому проекті я не використовуватиму графічний дизайнер, тому вручну пропишемо QGridLayout в заголовному файлі вікна, в якому розміщуватиметься SFML віджет.

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QGridLayout>
#include "qsfmlcanvas.h" // Подключение виджета SFML

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget* parent = nullptr);
    ~Widget();

private:
    QSFMLCanvas* m_sfmlWidget;
    QGridLayout* m_gridLayout;
};

#endif // WIDGET_H

widget.cpp

А в конструкторі створимо QSFMLCanvas віджет та встановимо його розміщення у вікні програми.

#include "widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent)
{
    resize(640, 480);
    m_sfmlWidget = new QSFMLCanvas(this);
    m_gridLayout = new QGridLayout(this);
    m_gridLayout->addWidget(m_sfmlWidget, 0, 0);
}

Widget::~Widget()
{

}

qsfmlcanvas.h

Розберемо вміст заголовного класу SFML віджету.

#ifndef QSFMLCANVAS_H
#define QSFMLCANVAS_H

#include <QWidget>
#include <QTimer>
#include <SFML/Graphics.hpp>

class QSFMLCanvas : public QWidget, public sf::RenderWindow
{
    Q_OBJECT
public:
    explicit QSFMLCanvas(QWidget* parent = nullptr, uint frameTime = 50);
    virtual ~QSFMLCanvas() {}

private :
    // Метод первоначальной инициализации виджета 
    void onInit();
    // Метод обновления содержимого виджета
    void onUpdate();
    // Метод, возвращающий движок отрисовки Qt
    virtual QPaintEngine* paintEngine() const override;
    // Метод событий открытия и закрытия виджета, понадобится для первоначальной инициализации
    virtual void showEvent(QShowEvent*) override;
    // Метод отрисовки, понадобится нам для перерисовки виджета
    virtual void paintEvent(QPaintEvent*) override;

private slots:
    // Слот, в котором будет вызываться событие перерисовки
    void onTimeout();

private:
    QTimer m_timer;
    bool   m_initialized;

    sf::Texture  m_texture;
    sf::Sprite   m_sprite;
};

#endif // QSFMLCANVAS_H

Методи onInit() та onUpdate() є опціональними і по суті ви можете назвати їх як захочете, важливо те, що потрібно спочатку додати всі необхідні об'єкти та періодично оновлювати їх.

А ось метод paintEngine() необхідно перевизначити обов'язково, оскільки ми не будемо використовувати движок змальовування Qt, то цей метод повинен буде повертати nullptr.

Методи showEvent() та paintEvent() будуть використовуватися для ініціалізації функціоналу SFML та початкового відтворення та оновлення відображення об'єктів. Це методи, які є методами класу QWidget , від якого успадковано клас SFML віджету. Перемальовка SFML віджету завдяки цим методам будемо проводитися в рамках інфраструктури подій Qt. Що це означає? Справа в тому, що метод paintEvent() може бути викликаний викликом методу repaint() , repaint() викликає подію перемальовки в рамках стека викликів Qt програми, тому не варто дивуватися, що для перемалювання не здійснюється прямого виклику методу paintEvent().

Також ви могли помітити, що в даному класі використовується множинне успадкування від двох класів: QWidget та sf::RenderWindow.

Наслідування від QWidget нам необхідно, щоб отримати можливість впровадження SFML віджету в будь-яке місце інтерфейсу програми, а також отримати доступ до функціоналу сигналів і слотів Qt, тоді як успадкування від sf::RenderWindow необхідно для отримання доступу до функціоналу даної бібілотеки плюс якої полягає в тому, що вона безпосередньо працює з OpenGL.

qsqmlcanvas.cpp

#include "qsfmlcanvas.h"

#include <string>

#include <QPixmap>
#include <QByteArray>
#include <QBuffer>

QSFMLCanvas::QSFMLCanvas(QWidget *parent, uint frameTime) :
    QWidget(parent),
    // Инициализируем базовый конструктор окна отрисовки SFML
    sf::RenderWindow(sf::VideoMode(800, 600), "OpenGL", sf::Style::Default, sf::ContextSettings(24)),
    m_initialized(false)
{
    // Произведём настройку для непосредственной отрисовки изображения в виджет
    setAttribute(Qt::WA_PaintOnScreen);
    setAttribute(Qt::WA_OpaquePaintEvent);
    setAttribute(Qt::WA_NoSystemBackground);

    // Установим строгую фокусировку для включения событий клавиатуры
    setFocusPolicy(Qt::StrongFocus);

    // Настроим период сработки таймера перерисовки
    m_timer.setInterval(frameTime);
}

void QSFMLCanvas::onInit()
{
    // Загрузка изображения из ресурсных файлов Qt
    // Довольно интересный момент в том плане, что
    // SFML не может загрузить изображение из ресурсов, как это делают QPixmap или QImage
    QPixmap pixmap(":/QtCreator.png");  // Но если создать объект QPixmap
    QByteArray bArray;                  // Создать объект массива байтов
    QBuffer buffer(&bArray);            // Поместить его в буфер
    buffer.open(QIODevice::WriteOnly);  // Сохранить изображение в этот буфер, то есть в память
    pixmap.save(&buffer, "PNG");     

    // А потом забрать сырые данные из буффера, с указанием количества забираемых данных
    m_texture.loadFromMemory(buffer.data().data(), buffer.data().size());
    // Эту процедуру понадобится произвести один раз при создании текстуры SFML

    // Далее устанавливаем изображения в sprite
    m_sprite.setTexture(m_texture);
    float xScale = 200. / m_sprite.getTextureRect().width;
    float yScale = 200. / m_sprite.getTextureRect().height;
    m_sprite.setScale(xScale, yScale);     // Масштабируем изображение
    m_sprite.setOrigin(m_sprite.getTextureRect().width / 2, m_sprite.getTextureRect().height / 2); // Устанавливаем центр изображения
    m_sprite.setPosition(200, 200);        // Устанавливаем позицию изображения
}

void QSFMLCanvas::onUpdate()
{
    // Очищаем экран
    clear(sf::Color(128, 0, 0));

    // Поворачиваем изображение
    m_sprite.rotate(1);

    // Отрисовываем это изображение
    draw(m_sprite);
}

QPaintEngine* QSFMLCanvas::paintEngine() const
{
    // Возвращаем nullptr вместо движка отрисовки Qt, чтобы Qt не пытался что либо рисовать сам
    return nullptr;
}

void QSFMLCanvas::showEvent(QShowEvent*)
{
    // Первичная инициализация виджета SFML
    if (!m_initialized)
    {
        // Создаём SFML окно для отрисовки с указанием Id окна, в котором будет производиться отрисовка
        RenderWindow::create(winId());

        // Инициализация объектов отрисовки
        onInit();

        // Настройка таймера для перезапуска отрисовки виджета
        connect(&m_timer, &QTimer::timeout, this, &QSFMLCanvas::onTimeout);
        m_timer.start();

        m_initialized = true;
    }
}

void QSFMLCanvas::paintEvent(QPaintEvent*)
{
    // Обновление отрисовки объектов
    onUpdate();

    // Отображение отрисованного окна
    display();
}

void QSFMLCanvas::onTimeout()
{
    // Запуск перерисовки
    repaint();
}

Завантажити проект

УВАГА: Проект створювався під KDE NEON 5.8.2 і є зразком, коректний запуск якого не гарантований під іншими ОС, зокрема Вам потрібна збірка бібліотеки SFML під вашу цільову платформу та з вашим компілятором.

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Стабільний хостинг, на якому розміщується соціальна мережа EVILEG. Для проектів на Django радимо VDS хостинг.

Вам це подобається? Поділіться в соціальних мережах!

F
  • 10 жовтня 2017 р. 11:41

Здравствуйте!

При сборке проекта выскакивает ошибка в файле qsfmlcanvas.cpp - строка  RenderWindow::create(winId());  метода showEvent пишет invalid conversion
Evgenii Legotckoi
  • 10 жовтня 2017 р. 12:14
День добрый!
Под какой ОС собирали? Компилировали SFML библиотеки под свой компилятор?
F
  • 10 жовтня 2017 р. 12:16

Собирал под Windows 7. Компилировал с тем же компилятором, с которым работает Qt

F
  • 10 жовтня 2017 р. 12:18

Но если эту строку закомментировать, то приложение запускается и создается 2 окна(никакого изображения конечно же нет). OpenGl пустое, а второе с черными квадратными  областями

Evgenii Legotckoi
  • 10 жовтня 2017 р. 12:21

Два окна из-за того, что хендлер не был передан для отрисовки.

Возможно, нужно поиграться со static_cast , чтобы получить правильное преобразование хендлера WId в то, что слопает метод RenderWindow::create().
Попробуйте так кастовать:
RenderWindow::create(static_cast<unsigned long>(winId()));
F
  • 10 жовтня 2017 р. 12:24

Нет, так есть тож не хочет

Evgenii Legotckoi
  • 10 жовтня 2017 р. 12:26

Более полный текст ошибки тогда давайте. Наверняка там есть более подробная информация. Пока у меня идей нет.

F
  • 10 жовтня 2017 р. 12:29

Когда следую вашему методу с cast пишет "invalid conversion from long unsigned int to sf::WindowHandle {aka HWND__*}' [fpermissive]"

Evgenii Legotckoi
  • 10 жовтня 2017 р. 12:32

Ну тогда такой каст

RenderWindow::create(static_cast<sf::WindowHandle>(winId()));
F
  • 10 жовтня 2017 р. 12:35

Теперь пишет тоже самое просто вместо unsigned long int - "WId{aka unsigned int} "


F
  • 10 жовтня 2017 р. 12:36

а вот так все работает  RenderWindow::create(sf::WindowHandle(winId()));

Evgenii Legotckoi
  • 10 жовтня 2017 р. 12:38

Забавно. Какой компилятор использовали?

F
  • 10 жовтня 2017 р. 12:38

открывается только 1 окно но картинка не грузится

хотя в ресурсах проекта она есть или тип PNG обязателен
F
  • 10 жовтня 2017 р. 12:39

mingw


F
  • 10 жовтня 2017 р. 12:43

А нет все отлично работает и с jpg просто это из-за размеров картинки была проблема

float xScale = 800. / m_sprite.getTextureRect().width;
float yScale = 800. / m_sprite.getTextureRect().height;
Здесь подставил свои и все норм
F
  • 10 жовтня 2017 р. 12:43

Спасибо за советы

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
d
  • dsfs
  • 26 квітня 2024 р. 01:56

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80бали,
  • Рейтинг балів4
d
  • dsfs
  • 26 квітня 2024 р. 01:45

C++ - Тест 002. Константы

  • Результат:50бали,
  • Рейтинг балів-4
d
  • dsfs
  • 26 квітня 2024 р. 01:35

C++ - Тест 001. Первая программа и типы данных

  • Результат:73бали,
  • Рейтинг балів1
Останні коментарі
k
kmssr08 лютого 2024 р. 15:43
Qt Linux - Урок 001. Автозапуск програми Qt під Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко04 лютого 2024 р. 22:50
Qt WinAPI - Урок 007. Робота з ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 грудня 2023 р. 07:30
Boost - статичне зв&#39;язування в проекті CMake під Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 грудня 2023 р. 05:38
Boost - статичне зв&#39;язування в проекті CMake під Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik18 грудня 2023 р. 18:01
Qt/C++ - Урок 056. Підключення бібліотеки Boost в Qt для компіляторів MinGW і MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Тепер обговоріть на форумі
IscanderChe
IscanderChe30 квітня 2024 р. 01:22
Во Flask рендер шаблона не передаётся в браузер Доброе утро! Имеется вот такой шаблон: <!doctype html><html> <head> <title>{{ title }}</title> <link rel="stylesheet" href="{{ url_…
G
Gar22 квітня 2024 р. 02:46
Clipboard Как скопировать окно целиком в clipb?
DA
Dr Gangil Academics20 квітня 2024 р. 04:45
Unlock Your Aesthetic Potential: Explore MSC in Facial Aesthetics and Cosmetology in India Embark on a transformative journey with an msc in facial aesthetics and cosmetology in india . Delve into the intricate world of beauty and rejuvenation, guided by expert faculty and …
a
a_vlasov14 квітня 2024 р. 03:41
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
Павел Дорофеев
Павел Дорофеев13 квітня 2024 р. 23:35
QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь

Слідкуйте за нами в соціальних мережах