SFML представляет собой объектно-ориентированный аналог SDL. Библиотека предоставляет простой доступ к системам ввода вывода, аудио, сети передачи данных, а также к функционалу OpenGL.
А что если дополнительно прикрутить к этой библиотеке ещё и функционал Qt, получить в управление мощные возможности сигналов и слотов, ресурсных файлов Qt? Что для этого потребуется?
- Скачать с GitHub последнюю версию исходников SFML .
- Скомпилировать исходники в библиотеку
- Создать проект Qt
- Подключить библиотеку в проект
- Написать виджет, который будет рендериться напрямую через функционал 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 под вашу целевую платформу и с вашим компилятором.
Здравствуйте!
Собирал под Windows 7. Компилировал с тем же компилятором, с которым работает Qt
Но если эту строку закомментировать, то приложение запускается и создается 2 окна(никакого изображения конечно же нет). OpenGl пустое, а второе с черными квадратными областями
Два окна из-за того, что хендлер не был передан для отрисовки.
Нет, так есть тож не хочет
Более полный текст ошибки тогда давайте. Наверняка там есть более подробная информация. Пока у меня идей нет.
Когда следую вашему методу с cast пишет "invalid conversion from long unsigned int to sf::WindowHandle {aka HWND__*}' [fpermissive]"
Ну тогда такой каст
Теперь пишет тоже самое просто вместо unsigned long int - "WId{aka unsigned int} "
а вот так все работает RenderWindow::create(sf::WindowHandle(winId()));
Забавно. Какой компилятор использовали?
открывается только 1 окно но картинка не грузится
mingw
А нет все отлично работает и с jpg просто это из-за размеров картинки была проблема
Спасибо за советы