Реклама

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
День добрый!
Под какой ОС собирали? Компилировали SFML библиотеки под свой компилятор?

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

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

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

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

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

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

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

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

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

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

RenderWindow::create(static_cast<sf::WindowHandle>(winId()));
  • #
  • 10 октября 2017 г. 16:35

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


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

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

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

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

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

хотя в ресурсах проекта она есть или тип PNG обязателен

mingw


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

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

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

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

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь

Qt - Тест 001. Сигналы и слоты

  • Результат 0 баллов
  • Очки рейтинга -10
  • boa
  • 10 декабря 2017 г. 3:04

Qt - Тест 001. Сигналы и слоты

  • Результат 84 баллов
  • Очки рейтинга 4
  • Shalfy
  • 8 декабря 2017 г. 14:05

Qt - Тест 001. Сигналы и слоты

  • Результат 100 баллов
  • Очки рейтинга 10
Последние комментарии
  • EVILEG
  • 7 декабря 2017 г. 9:47

Django - Урок 011. Добавление комментариев на сайт с Django

Визуальный пример чего? комментариев? При ответе на конкретный комментарий рядом с ником отвечающего будет стрелочка и указание ник другого пользователя. Который будет ссылкой на коммента...

  • Bernar
  • 7 декабря 2017 г. 9:24

Django - Урок 011. Добавление комментариев на сайт с Django

есть визуальный пример ?

  • EVILEG
  • 6 декабря 2017 г. 11:30

Django - Урок 011. Добавление комментариев на сайт с Django

Да, так будет даже лучше, я на сайте уже обновил до такого вида код Вот это уже не нужно if request.method == 'POST': Поскольку Вы и так используете метод post, то есть эта про...

  • Bernar
  • 6 декабря 2017 г. 11:19

Django - Урок 011. Добавление комментариев на сайт с Django

сделал немного по другому class EArticleView(View): template_name = 'knowledge/article.html' comment_form = CommentForm def get(self, request, *args, **kwargs): ...

Сейчас обсуждают на форуме

Как значение текущего элемента ListView получить вне ListView

Вот реально огромное спасибо!! У вас большое терпение с нами возиться и отзывчивость.

  • EVILEG
  • 11 декабря 2017 г. 8:09

QCustomPlot исчезает часть графика при перестроении

Объявить volumeAxisRect в заголовочном файле

  • EVILEG
  • 9 декабря 2017 г. 21:24

Как написать парсер страницы при помощи js

Эм... лично я даже растерялся от такого вопроса... У javascript есть методы типо document.getElementById document.getElementByTag, которые выбирают нужные теги и мож...

Ошибки при многопоточном запуске функции библиотке

Большое спасибо, так заработало QFuture<void> Perebor2 = QtConcurrent::map(Perebor,[=](const double& d){ StrategyCod(d,this);});

  • grig_p
  • 8 декабря 2017 г. 12:49

Прерывание таймера в режиме singleShot

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