SFML є об'єктно-орієнтованим аналогом SDL. Бібліотека надає простий доступ до систем введення виводу, аудіо, мережі передачі даних, а також до функціоналу OpenGL.
А якщо додатково прикрутити до цієї бібліотеки ще й функціонал Qt, отримати в управління потужні можливості сигналів і слотів, ресурсних файлів Qt? Що для цього потрібно?
- Завантажити з GitHub останню версію вихідних файлів SFML .
- Скомпілювати вихідники до бібліотеки
- Створити проект Qt
- Підключити бібліотеку до проекту
- Написати віджет, який рендеруватиметься безпосередньо через функціонал 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 під вашу цільову платформу та з вашим компілятором.
Здравствуйте!
Собирал под 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 просто это из-за размеров картинки была проблема
Спасибо за советы