© EVILEG 2015-2018
Рекомендует хостинг
TIMEWEB

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

SFML, Qt, C++, QWidget

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-audio
  • sfml-graphics
  • sfml-network
  • sfml-window
  • sfml-system

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 под вашу целевую платформу и с вашим компилятором.

Комментарии

10 октября 2017 г. 15:41

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

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

Для Django рекомендую VDS-хостинг TIMEWEB

10 октября 2017 г. 16:16

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

10 октября 2017 г. 16:18

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

10 октября 2017 г. 16:21

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

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

Для Django рекомендую VDS-хостинг TIMEWEB

10 октября 2017 г. 16:24

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

10 октября 2017 г. 16:26

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

Для Django рекомендую VDS-хостинг TIMEWEB

10 октября 2017 г. 16:29

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

10 октября 2017 г. 16:32

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

RenderWindow::create(static_cast<sf::WindowHandle>(winId()));

Для Django рекомендую VDS-хостинг TIMEWEB

10 октября 2017 г. 16:35

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


10 октября 2017 г. 16:36

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

10 октября 2017 г. 16:38

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

Для Django рекомендую VDS-хостинг TIMEWEB

10 октября 2017 г. 16:38

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

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

mingw


10 октября 2017 г. 16:43

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

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

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

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
24 сентября 2018 г. 17:42
edorofeeva

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

  • Результат 100баллов,
  • Очки рейтинга10
24 сентября 2018 г. 17:37
edorofeeva

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

  • Результат 66баллов,
  • Очки рейтинга-1
23 сентября 2018 г. 14:38
No Names

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

  • Результат 60баллов,
  • Очки рейтинга-1
Последние комментарии
24 сентября 2018 г. 15:09
Евгений Легоцкой

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

А вот здесь у меня есть пример использования supervisor. https://evileg.com/ru/post/3/ Вся статья вам там не интересна, интересен только шаг с настройкой supervisor. Он получается ...
24 сентября 2018 г. 15:00
avovana

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

Не могли бы дать ссылку на пример? Какое-то рабочее использование. Т.е. у меня есть Qt Gui App, которое я бы хотел запускать при старте системы и в случае, если оно грохнется. Если о чем Вы го...
24 сентября 2018 г. 14:55
Евгений Легоцкой

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

Если честно, то я не уверен, что это вообще можно реализовать через *.desktop файл. Я сделал предположение на основе того, что вы сказали про *.desktop и рестарт. Все варианты, котор...
24 сентября 2018 г. 14:47
avovana

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

Просто сейчас правлю сам файл example.desktop. Пытаюсь понять какую пару key=value мне нужно дописать.
24 сентября 2018 г. 14:42
Евгений Легоцкой

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

Ну я имел ввиду, что дописать в коде вот сюда то, о чём вы говорили про рестарт QString autorunContent("[Desktop Entry]\n" "Type=Application\n" ...
Сейчас обсуждают на форуме
24 сентября 2018 г. 16:47
Евгений_Канусовский@1981

Чтение файлов в python

Добрый вечер Евгений и форумчане! Столкнулся с проблемой чтения файлов в python: файлы с обычным текстом в формате las и txt читаются, например: ~Version information VERS.          ...
24 сентября 2018 г. 13:29
Евгений Легоцкой

Трансляция видео с помощью VLC по RTP

Добрый день! Я не сталкивался, но предположу, что нужно настроить Input Codec в VLC. В настройках есть секция Input Codec, возможно, что там установлено низкое разрешение. ...
21 сентября 2018 г. 8:25
Евгений Легоцкой

Прокси-модель, содержащая на 1 столбец больше, чем модель-источник.

Попробуйте ещё PySide 2 - это официально поддерживаемый пакет привязок Python к Qt, возможно, что там не будет таких проблем.
20 сентября 2018 г. 20:06
Евгений Легоцкой

Qt Installer Framework

Добрый день. Зачем собирать Qt Installer Framework-то из исходников? Я ещё понимаю Qt собирают из исходников статически (хотя тоже считаю по большей части бесполезной тратой времени),...
Присоединяйтесь к нам в социальных сетях