http://wm-monitoring.ru/ ')) {alert('Спасибо за то что установили нашу кнопку! =)');} else {alert('Очень жаль! =(');}"> http://wm-monitoring.ru/

Главная Новости

Разработка игры на С++, Qt

Опубликовано: 01.09.2018

видео Разработка игры на С++, Qt

Разработка мобильных игр и приложений С НУЛЯ на QT qml V-Play #1.1 Clicker Tutorial

Написал ремейк небольшой логической игрушки — «Полный квадрат». Код показался мне достаточно интересным чтобы описать на блоге.



По сюжету игры, ёжик перемещается по лабиринту из облаков. Ходить ёж умеет только по вертикали и горизонтали, а начав движение идет до тех пор, пока не упрется в пустоту (пройденные облака исчезают), край экрана или грозовую тучу. Необходимо выбрать начальную позицию для ежа и такой маршрут движения, чтобы все облака оказались пройдены.


Изучение Qt Creator | Урок #1 - Графический интерфейс на С++

В любой игре, чуть более интересной чем « Сапер «, используются анимации, проигрывается звук, поэтому следующие компоненты Qt затронуты в статье:

класс QMovie для отображения gif-анимации тучек и ежа; класс QPropertyAnimation — ежик перемещается плавно, при этом меняются его координаты (свойства); QMediaPlayer из модуля Qt Multimedia. При перемещении наш ёжик топает; Qt Style Sheets (QSS) используется для украшения элементов управления. снимки игровых экранов

Архитектура игры

UML диаграмма классов игры

Игра состоит из нескольких экранов, которыми управляет ScreenController. Экраны я рисовал с использованием Qt Designer [1]. Каждый игровой экран содержит кнопки, позволяющие перейти на другой экран, события от которых передаются контроллеру.


Разработка мобильных игр и приложений С НУЛЯ на QT qml V-Play #3 Динамические элементы

Логика игры вынесена в класс GameWidget, который загружает на графическую сцену блоки и как-то обрабатывает их сигналы. Например, если на сцене уже размещен ёжик и пользователь кликнул облачко, то GameWidget обеспечивает перемещение ежа в новую позицию при условии, что это не будет противоречить правилам игры.

Экран помощи позволяет пользователю прочитать правила и сыграть в игру с подсказками. При этом, пользователь может кликать только по специальным подсвеченным тучкам — сигналы от других блоков игнорируются.

WinScreen выводит информацию о победе и предлагает сыграть в игру еще раз.

Qt Style Sheets (QSS)

При рисовании интерфейса в Qt Designer использованы самые обычные кнопки текстовые поля. Скругленные углы кнопок, цвет и размер рамки, изменение цвета при наведении и нажатии мыши получены за счет использования таблиц стилей Qt.

Установить таблицу стилей виджету (QWidget) или приложению (QApplication) можно методом setStyleSheet(). Для стилизации уже готового приложения можно передать таблицу стилей при запуске вместе с соответствующей опцией (-stylesheet style.qss). Таблица стилей Qt являются надстройкой над каскадными таблицами стилей, используемыми при верстке веб страниц (CSS3) [2].

Ключевым понятием в QSS является селектор, т.е. элемент, к которому мы хотим применить стили, при этом, если некоторому объекту устанавливается таблица стилей, то стили будут распространяться также на все дочерние элементы. В качестве селектора могут выступать:

все виджеты; "* { background-color: rgba(176, 196, 222, 255); }" имя класса виджета. Стиль применяется ко всем элементам, которые могут быть приведены к классу с заданным именем; QLineEdit, QPushButton { border-radius: 10px; } имя объекта определенного класса. Стиль применяется только к этому объекту; A#obj { border-radius: 10px; } имя класса и свойства объекта. Стиль применяется ко всем объектам класса, обладающими заданным свойством; QPushButton[flat="true"] { border-radius: 10px; } вложенные виджеты. Можно задавать применение стилей к объектам, вложенным непосредственно в текущий, либо с произвольным уровнем вложенности. A > B { background-color: rgba(255, 255, 255, 255); } A C { background-color: rgba(0, 0, 0, 255); }

В игре стили применяются к объекту класса ScreenController, и автоматически будут распространены на все вложенные виджеты.

setStyleSheet( "* { background-color: rgba(176, 196, 222, 255); }" "QPushButton { " " background-color: rgba(255, 153, 102, 200); " " border-style: outset;" " border-width: 2px;" " border-radius: 10px;" " border-color: beige;" " font: bold 14px;" " width: 3em;" " padding: 6px;" "}" "QPushButton:hover {" "background-color: rgba(255, 102, 0, 200);" "}" "QPushButton:pressed {" "background-color: rgba(255, 0, 0, 200);" "}" "QPushButton:disabled {" "background-color: rgba(204, 153, 102, 200);" "}" "QTextEdit {" "background-color: rgba(102, 204, 102, 200);" " border-style: outset;" " border-width: 0px;" " border-radius: 10px;" " border-color: black;" " width: 3em;" " padding: 6px;" "}" );

В примере задается цвет фона для всех виджетов, параметры кнопок и текстового поля, а также цвет кнопок, находящихся в определенных псевдосостояниях. Стиль, примененный для QPushButton:disabled будет распространен на все неактивные кнопки.

При использовании QSS необходимо заглядывать в документацию, чтобы проверить возможность стилизации свойств определенного типа виджета [2], кроме того, в документации описано огромное количество примеров [3].

Анимация и звук в Qt

Ежик плавно перемещается, за счет использования QPropertyAnimation, при этом сам ёж при перемещении машет лапками — проигрывается соответствующая .gif-анимация (QMovie).

Любой элемент игры отображает какую-либо анимацию, поэтому в класс Block включены соответствующие поля и метод установки анимации.

class Block: public QWidget { Q_OBJECT public: explicit Block(QWidget *parent = 0); void animation(QString texturename, bool randomStartFrame = false); protected: QMovie *m_animation; //!< анимация, проигрываемая блоком QLabel *m_label; //!< метка для отображения анимации }; Block::Block(QWidget *parent) : QWidget(parent), m_animation(new QMovie(this)), m_label(new QLabel(this)) { // ... m_label->setMovie(m_animation); m_animation->setCacheMode(QMovie::CacheMode::CacheAll); resize(BlockParam::BlockSize, BlockParam::BlockSize); m_animation->setScaledSize(size()); } // ... void Block::animation(QString texturename, bool randomStartFrame) { m_animation->stop(); m_animation->setFileName(texturename); if (randomStartFrame) m_animation->jumpToFrame(qrand() % m_animation->frameCount()); m_animation->start(); }

Класс блок агрегирует экземпляр QLabel, на котором и отображается анимация. QMovie поддерживает кеширование, которое по умолчанию отключено. При кешировании, в память помещается не следующий кадр, а пачка кадров, поэтому сокращаются затраты на их переключение. В приведенном фрагменте используется метод jumpToFrame(), устанавливающий текущий кадр, который не должен использоваться при включенном кешировании, однако в нашем случае он производит лишь установку начального кадра анимации и никогда не переключает кадры во время проигрывания.

Я уже рассказывал про графическую сцену в Qt [4], но в тот раз на сцене размещались экземпляры QGraphicsItem, но они не являются наследниками QWidget и не могут включать в себя виджеты (которым является QLabel, используемый для отображения анимации). В связи с этим, все наши ежи и облачка являются виджетами, а вместо QGraphicsScene::addItem() используется метод QGraphicsScene::addWidget(), создающий экземпляр QGraphicsProxyWidget, который и добавляется на сцену. Независимо от того, в какой позиции находился виджет до добавления, на сцене он окажется в левом верхнем углу. При добавлении на сцену QGraphicsProxyWidget копирует состояние исходного виджета, после этого состояние виджета всегда будет соответствовать состоянию прокси и наоборот — например, если скрыть виджет методом hide() — то скрыт будет и соответствующий прокси. При освобождении памяти из под прокси, будет удален исходный виджет. У класса QWidget имеется метод для получения соответствующего прокси — QWidget::graphicsProxyWidget().

В нашей игре, за добавление виджетов на сцену отвечает виртуальный метод GameWidget::itemAdd(), принимающий тип добавляемого виджета и создающий экземпляр соответствующего класса. В классе GameHelpWidget этот метод перегружается для обработки элементов нового типа. После добавления элемента на сцену, выполняется метод move(), т.к. иначе виджет останется в левом верхнем углу сцены.

void GameHelpWidget::itemAdd(int i, int j, char type) { if (type == 'h') { m_items[i][j] = new CloudHelp(); m_scene->addWidget(m_items[i][j])->setZValue(CloudLayer); connect(m_items[i][j], SIGNAL(clicked()), SLOT(onBlockClicked())); m_items[i][j]->move(j * BlockParam::BlockSize, i * BlockParam::BlockSize); } else GameWidget::itemAdd(i, j, type); }

Класс QPropertyAnimation позволяет анимировать свойства объекта, при этом указываются начальное и конечное значения свойства и время, за которое изменение должно произойти. QPropertyAnimation рассчитывает новое значение свойства, а по приходу сигнала от таймера (который есть внутри любого QObject) изменяет свойство и запрашивает перерисовку объекта. Под свойствами тут имеется ввиду любое Q_PROPERTY, в том числе, определенное пользователем — так, например, если бы мы разрабатывали виджет, моделирующий работу чашечных весов, рационально бы было определить угол наклона весов в виде свойства, а анимацию поворота рычага выполнять с помощью стандартного QPropertyAnimation. В нашей игре требуется плавно изменять координаты ёжика, а по окончанию анимации выполнять проверки на счет того, что ёж мог оказаться в тупике — проиграл, или обошел все клетки — выиграл.

void GameWidget::blockClickedHandler(Block *block) { // ... QPropertyAnimation *animation = new QPropertyAnimation(m_actor, "pos"); animation->setDuration(delay); animation->setStartValue(m_actor->pos()); animation->setEndValue(QPoint(BlockParam::BlockSize * j, BlockParam::BlockSize * i) + ActorShift); connect(animation, SIGNAL(finished()), this, SLOT(onMoveFinished())); animation->start(); // ... } void GameWidget::onMoveFinished() { delete sender(); // ... проверки, изменения состояний игры и ежа и т.п. }

В конструкторе QPropertyAnimation указывается объект, свойство которого требуется изменять и имя свойства. Кроме того, может быть указан родительский объект, при разрушении которого из под анимации будет освобождена память — это удобно, если время проигрывания анимации совпадает со временем жизни какого-то объекта, но т.к. у нас такого класса нет — память освобождается в слоте-обработчике завершения анимации.

Во время перемещения ежа проигрывается трек с топотом. Для проигрывания звука используем класс QMediaPlayer.

Actor::Actor(QWidget *parent): Block(parent), m_player(new QMediaPlayer(this)), m_playlist(new QMediaPlaylist(m_player)) { m_player->setPlaylist(m_playlist); m_playlist->addMedia(QUrl("qrc:/game/audio/hardStep.wav")); m_playlist->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop); state(Idle); } void Actor::state(Direction direction) { switch (direction) { case Direction::Idle: animation(":/game/pics/actor_stay.gif"); m_player->stop(); break; case Direction::Up: animation(":/game/pics/actor_up.gif"); m_player->play(); break; case Direction::Down: animation(":/game/pics/actor_down.gif"); m_player->play(); break; // ...

QMediaPlaylist позволяет создать список проигрывания и управлять ими. В приведенном фрагменте трек всего один, а QMediaPlaylist используется для зацикливания проигрывания музыки.

После публикации этой записи исходный код игры в значительной мере изменился. В частности:

пришлось отказаться от QMovie и анимации в gif-формате, т.к. пользователи телефонов начали жаловаться на низкое качество картинки, обусловленное сжатием gif; исходный код был улучшен с точки зрения гибкости и удобства добавления новых возможностей — я планировал доработать игру добавив новые типы блоков, но руки не дойдут до этого — основная проблема в художнике, который опять попросит кучу денег за анимации и в огромном количестве времени, которое придется потратить на составление новых уровней;

Скачать игру и ее исходный код можно в по ссылке .

Литература по теме

Собственные виджеты в Qt Designer [Электронный ресурс] — режим доступа: https://pro-prof.com/archives/958. Дата обращения: 24.07.2014. Qt Style Sheets Reference [Электронный ресурс] — режим доступа: http://doc.qt.io/qt-5/stylesheet-reference.html. Дата обращения: 24.07.2014. Qt Style Sheets Examples [Электронный ресурс] — режим доступа: http://doc.qt.io/qt-5/stylesheet-examples.html. Дата обращения: 24.07.2014. Работа с графической сценой Qt [Электронный ресурс] — режим доступа: https://pro-prof.com/archives/1117. Дата обращения: 24.07.2014.
Карта
rss