Evgenii Legotckoi
8 октября 2017 г. 2: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-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 файле:

  1. win32:CONFIG(release, debug|release): LIBS += -L$$PWD/SFML/lib/ -lsfml-audio -lsfml-graphics -lsfml-network -lsfml-window -lsfml-system
  2. 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
  3. else:unix: LIBS += -L$$PWD/SFML/lib/ -lsfml-audio -lsfml-graphics -lsfml-network -lsfml-window -lsfml-system
  4.  
  5. INCLUDEPATH += $$PWD/SFML/include
  6. 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 виджет.

  1. #ifndef WIDGET_H
  2. #define WIDGET_H
  3.  
  4. #include <QWidget>
  5. #include <QGridLayout>
  6. #include "qsfmlcanvas.h" // Подключение виджета SFML
  7.  
  8. class Widget : public QWidget
  9. {
  10. Q_OBJECT
  11.  
  12. public:
  13. explicit Widget(QWidget* parent = nullptr);
  14. ~Widget();
  15.  
  16. private:
  17. QSFMLCanvas* m_sfmlWidget;
  18. QGridLayout* m_gridLayout;
  19. };
  20.  
  21. #endif // WIDGET_H

widget.cpp

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

  1. #include "widget.h"
  2.  
  3. Widget::Widget(QWidget *parent) :
  4. QWidget(parent)
  5. {
  6. resize(640, 480);
  7. m_sfmlWidget = new QSFMLCanvas(this);
  8. m_gridLayout = new QGridLayout(this);
  9. m_gridLayout->addWidget(m_sfmlWidget, 0, 0);
  10. }
  11.  
  12. Widget::~Widget()
  13. {
  14.  
  15. }

qsfmlcanvas.h

Разберём содержимое заголовочного класса SFML виджета.

  1. #ifndef QSFMLCANVAS_H
  2. #define QSFMLCANVAS_H
  3.  
  4. #include <QWidget>
  5. #include <QTimer>
  6. #include <SFML/Graphics.hpp>
  7.  
  8. class QSFMLCanvas : public QWidget, public sf::RenderWindow
  9. {
  10. Q_OBJECT
  11. public:
  12. explicit QSFMLCanvas(QWidget* parent = nullptr, uint frameTime = 50);
  13. virtual ~QSFMLCanvas() {}
  14.  
  15. private :
  16. // Метод первоначальной инициализации виджета
  17. void onInit();
  18. // Метод обновления содержимого виджета
  19. void onUpdate();
  20. // Метод, возвращающий движок отрисовки Qt
  21. virtual QPaintEngine* paintEngine() const override;
  22. // Метод событий открытия и закрытия виджета, понадобится для первоначальной инициализации
  23. virtual void showEvent(QShowEvent*) override;
  24. // Метод отрисовки, понадобится нам для перерисовки виджета
  25. virtual void paintEvent(QPaintEvent*) override;
  26.  
  27. private slots:
  28.   // Слот, в котором будет вызываться событие перерисовки
  29. void onTimeout();
  30.  
  31. private:
  32. QTimer m_timer;
  33. bool m_initialized;
  34.  
  35. sf::Texture m_texture;
  36. sf::Sprite m_sprite;
  37. };
  38.  
  39. #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

  1. #include "qsfmlcanvas.h"
  2.  
  3. #include <string>
  4.  
  5. #include <QPixmap>
  6. #include <QByteArray>
  7. #include <QBuffer>
  8.  
  9. QSFMLCanvas::QSFMLCanvas(QWidget *parent, uint frameTime) :
  10. QWidget(parent),
  11.   // Инициализируем базовый конструктор окна отрисовки SFML
  12. sf::RenderWindow(sf::VideoMode(800, 600), "OpenGL", sf::Style::Default, sf::ContextSettings(24)),
  13. m_initialized(false)
  14. {
  15. // Произведём настройку для непосредственной отрисовки изображения в виджет
  16. setAttribute(Qt::WA_PaintOnScreen);
  17. setAttribute(Qt::WA_OpaquePaintEvent);
  18. setAttribute(Qt::WA_NoSystemBackground);
  19.  
  20. // Установим строгую фокусировку для включения событий клавиатуры
  21. setFocusPolicy(Qt::StrongFocus);
  22.  
  23. // Настроим период сработки таймера перерисовки
  24. m_timer.setInterval(frameTime);
  25. }
  26.  
  27. void QSFMLCanvas::onInit()
  28. {
  29. // Загрузка изображения из ресурсных файлов Qt
  30.   // Довольно интересный момент в том плане, что
  31.   // SFML не может загрузить изображение из ресурсов, как это делают QPixmap или QImage
  32. QPixmap pixmap(":/QtCreator.png"); // Но если создать объект QPixmap
  33. QByteArray bArray; // Создать объект массива байтов
  34. QBuffer buffer(&bArray); // Поместить его в буфер
  35. buffer.open(QIODevice::WriteOnly); // Сохранить изображение в этот буфер, то есть в память
  36. pixmap.save(&buffer, "PNG");
  37.  
  38.   // А потом забрать сырые данные из буффера, с указанием количества забираемых данных
  39. m_texture.loadFromMemory(buffer.data().data(), buffer.data().size());
  40.   // Эту процедуру понадобится произвести один раз при создании текстуры SFML
  41.  
  42. // Далее устанавливаем изображения в sprite
  43. m_sprite.setTexture(m_texture);
  44. float xScale = 200. / m_sprite.getTextureRect().width;
  45. float yScale = 200. / m_sprite.getTextureRect().height;
  46. m_sprite.setScale(xScale, yScale); // Масштабируем изображение
  47. m_sprite.setOrigin(m_sprite.getTextureRect().width / 2, m_sprite.getTextureRect().height / 2); // Устанавливаем центр изображения
  48. m_sprite.setPosition(200, 200); // Устанавливаем позицию изображения
  49. }
  50.  
  51. void QSFMLCanvas::onUpdate()
  52. {
  53. // Очищаем экран
  54. clear(sf::Color(128, 0, 0));
  55.  
  56.   // Поворачиваем изображение
  57. m_sprite.rotate(1);
  58.  
  59. // Отрисовываем это изображение
  60. draw(m_sprite);
  61. }
  62.  
  63. QPaintEngine* QSFMLCanvas::paintEngine() const
  64. {
  65.   // Возвращаем nullptr вместо движка отрисовки Qt, чтобы Qt не пытался что либо рисовать сам
  66. return nullptr;
  67. }
  68.  
  69. void QSFMLCanvas::showEvent(QShowEvent*)
  70. {
  71.   // Первичная инициализация виджета SFML
  72. if (!m_initialized)
  73. {
  74. // Создаём SFML окно для отрисовки с указанием Id окна, в котором будет производиться отрисовка
  75. RenderWindow::create(winId());
  76.  
  77. // Инициализация объектов отрисовки
  78. onInit();
  79.  
  80. // Настройка таймера для перезапуска отрисовки виджета
  81. connect(&m_timer, &QTimer::timeout, this, &QSFMLCanvas::onTimeout);
  82. m_timer.start();
  83.  
  84. m_initialized = true;
  85. }
  86. }
  87.  
  88. void QSFMLCanvas::paintEvent(QPaintEvent*)
  89. {
  90. // Обновление отрисовки объектов
  91. onUpdate();
  92.  
  93. // Отображение отрисованного окна
  94. display();
  95. }
  96.  
  97. void QSFMLCanvas::onTimeout()
  98. {
  99.   // Запуск перерисовки
  100. repaint();
  101. }

Скачать проект

ВНИМАНИЕ: Проект создавался под KDE NEON 5.8.2 и является образцом, корректный запуск которого не гарантирован под другими ОС, в частности Вам потребуется сборка библиотеки SFML под вашу целевую платформу и с вашим компилятором.

Рекомендуемые статьи по этой тематике

По статье задано0вопрос(ов)

2

Вам это нравится? Поделитесь в социальных сетях!

F
  • 10 октября 2017 г. 21:41

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

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

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

F
  • 10 октября 2017 г. 22:18

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

Evgenii Legotckoi
  • 10 октября 2017 г. 22:21

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

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

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

Evgenii Legotckoi
  • 10 октября 2017 г. 22:26

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

F
  • 10 октября 2017 г. 22:29

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

Evgenii Legotckoi
  • 10 октября 2017 г. 22:32

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

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

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


F
  • 10 октября 2017 г. 22:36

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

Evgenii Legotckoi
  • 10 октября 2017 г. 22:38

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

F
  • 10 октября 2017 г. 22:38

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

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

mingw


F
  • 10 октября 2017 г. 22:43

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

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

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

Комментарии

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