Задача интеграции Irrlicht в какую нибуть GUI в интренете обсуждалось много и довольно подробно. Например есть классы для связки Irrlicht и wxWidgets, есть наработки для Python, есть и готовые классы для Qt. Но они давно не обновлялись, и многое что не работает, а помощь на англоязычном форуме Irrlicht мало чего дала, когда я начинал разбирался с этим движком.
Итак, в данной статье я расскажу какие грабли можно встретить при интеграции Irrlicht с Qt, что пока так и не удалось разрешить, приведу примеры кода ну и еще что нить по мелочи.
Для чего нужно интегрировать игровой движок в Gui оболочку спросите вы ? Ну если вы пишите непосредственно игру, то да, смысла в этом нету, однако если вы разрабатываете рендеренг какой нибудь сцены то скорее всего понадобятся разные кнопки, скролл бары, деревья и т.д. Конечно можно использовать уже реализованные в движке классы irr::gui, но они не так удобны и не так продвинуты как Qt. Плюс давно пишу на этой библиотеки и полностью доволен :)
Начнем по порядку. Можно выделить две особенности интеграции:
- Выделяем отдельный поток для рендеринга сцен(много поточное приложение). То в теории код отличаться сильно не будет, например от примеров Irrlict'а. Я это пока не реализовывал, но в скором времени прпробую.
- Пишем однопоточное приложение. Тут будут пара грабель, и интересных особенностей.
Назовем наш класс Terrain3D и унаследуемся от QWidget:
// Конструктор class Terrain3D : public QWidget /*! \brief Конструктор нашего класса. Можно задать парента. Используем отрисовку через OpenGL по умолчанию /*! \brief Деструктор */ virtual ~Terrain3D(); ... ... ... }; |
Нам понадобятся переопределить пару функций, это для отрисовки, ресайза, событий клавиатуры и мышки.
protected: |
Реализация конструктора класса:
Terrain3D::Terrain3D(QWidget *parent, irr::video::E_DRIVER_TYPE driverType) : ... ... m_IrrDevice = 0;// По умолчанию 0, для того чтобы при инициализации не произошло создания более одного устройства }; |
Флаги которые мы устанавливаем, необходимы для того, чтобы отрисовкой полностью занимался Irrlicht, если не устанавливать их то будет мерцание изображния, тоесть в paintEvent() Irrlicht отрисует нам картинку, а Qt после этого её сотрет, а нам этого не нужно.
Теперь нам нужна функция инициализации, в которой будет происходить создание движка Irrlicht. Тут главная особенность в том, что мы используем QWidget::winId(), которая возвращает системный индификатор окна, и вызывать её нужно в определенном порядке, если окно не будет отображено, то мы получим неправильный индефикатор. Или если мы не включим некоторые флаги при создании движка, то тоже не получим картинку. Если в консоль после создания движка высыпались сообщения следующего содержания, ты мы иеем дело именно с этим:
Linux 2.6.35-28-generic-pae #49-Ubuntu SMP Tue Mar 1 14:58:06 UTC 2011 i686 Creating X window... Visual chosen: : 39 X Error: BadMatch (invalid parameter attributes) From call : unknown X Error: GLXBadDrawable From call : unknown Could not make context current. Using renderer: OpenGL OpenGL driver version is not 1.2 or better. Warning: OpenGL device only has one texture unit. Disabling multitexturing. GLSL not available. |
В любом случаее при создании виджета в котором будет происходить отрисовка, лучше сначало установить размер виджета, вызвать функцию show() и только потом проводить инициализацию движка.
terrain3D = new Terrain3D(this); |
Посмотрим реализацию initialize().
void Terrain3D::initialize() // Создаем Irrlicht устойство в первый раз // Id окна // Создаем движок } |
Теперь перейдем непоследственно к отрисовки изображения. Если вы смотьрели примеры, то могли видеть, что вся отрисовка выполняется в цикле while(device->run()), поэтому я и говорил, что если приложение много поточное, то расхождение с примерами будет минимальным. У нас же другая ситуация, мы должны рисовать в paintEvent(). Ч то мы и сделаем. Давайте сначало напищем функцию для открытия данных, а потом перейдем к реализации paintEvent(). В этой функции сосздадим фпс камеру, поверхность, небо и коллизию.
void Terrain3D::openMap3D(const QString & filename) // Все что нужно для связи с ядром Irrlicht driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true); m_Camera = smgr->addCameraSceneNodeFPS(smgr->getRootSceneNode()); // делаем камеру активной // Создаем поверхность // Создаем небо // Создаем коллизию на карту m_Animator = smgr->createCollisionResponseAnimator(m_Selector, } |
Переходим к отрисовке. Сначало сделаем просто отрисовку. я расскажу о её недостадках, а как их обойти расскажу в следующей статье.
void Terrain3D::paintEvent(QPaintEvent */*event*/) { m_IrrDevice->getVideoDriver()->beginScene(true, true, m_ClearColor); m_IrrDevice->getSceneManager()->drawAll(); m_IrrDevice->getVideoDriver()->endScene(); } |
Для того чтобы все правильно отрисовывалось при изменении размера окна, нужно переопредилить resizeEvent и написать функцию setAspectRaitio
void Terrain3D::resizeEvent(QResizeEvent *event) int newWidth = event->size().width(); m_IrrDevice->getVideoDriver()->setViewPort(core::rect<s32>(vector2d<s32>(0, 0), dimension2d<irr::u32>(newWidth, newHeight))); void Terrain3D::setAspectRatio(irr::f32 aspect) irr::f32 newAspectRatio; // Устанавливаем соотношение сторон для активной камеры |
Все, теперь у нас все должно рисоваться. Теперь о граблях:
- При такой отрисвке, нам необходимо самим вызывать функцию QWidget::update() чтобы обновить сцену.
- Камера не будет реагировать на нажатие кнопок клавиатуры и мыши.
Об устранении этих недостатков будет написано в следующей статье.