Этот пост участвует в конкурсе „Умные телефоны за умные посты“

Даже не смотря на то, что многие программисты, в данный момент, не спешат переводить разработку своих приложений и игр на рельсы Qt Quick, инфраструктура вокруг самой технологии с каждым днём лишь растёт и развивается.
Вот и до симуляции физики в двухмерном пространстве дошло дело. А вернее до появления QML-плагина. который позволяет с присущей Qt Quick легкостью интегрировать в своё приложения физический движок Box2D. Вот об этом сегодня и поговорим. А точнее, разберём на примере реализации простого арканоида, насколько быстро можно создать простенькую игру, никогда ранее не работая с физическими движками и почти незная терминологии.
Первым делом, нам необходимо получить исходники плагина. Для этого переходим по ссылке gitorious.org/qml-box2d/qml-box2d/trees/master и справа кликаем по кнопке «Download master as tar.gz». Откладываем пока архив в сторонку и переходим в Qt Creator.
Здесь, создаём новый проект, типа «Приложение Qt Qucik». В мастере вводим название, расположение в файловой системе, выбираем профиль Qt, далее, далее, завершить.
А вот теперь начинается одна из самых важных частей. И обычно одна из самых сложных в ДРУГИХ языках и технологиях. Необходимо собственно подключить плагин к свежесозданному приложению. Для этого, распаковываем полученный архив в корневой каталог приложения и переименовываем получившийся каталог qml-box2d-qml-box2d в qml-box2d. B вносим одну новую строчку в .pro файл нашегно приложения:
А main.cpp приведём к такому виду:
Здесь строчкой #include «box2dplugin.h» включаем заголовок плагина, а строчками
регистрируем в приложении типы Qt/Box2D, которые будут доступны и необходимы нам в будущем в QML.
Вот и всё. Этого достаточно для подключения плагина как статически линкуемой библиотеки. Конечно же, плагин можно собрать как самостоятельную единицу и сложить в общий каталог всех QML-плагинов в системе. Но для нашей цели подойдёт и первый вариант. Внешний вид получающегося проекта примерно таков:

Если сейчас попробовать скомпилировать приложение, то мы увидим стандартный Hello World, который является шаблоном для проекта по умолчанию в Qt Quick. Но это не интересно. Нам интересно использовать физику.
Итак, мы определились что будем делать арканоид. Перечислим, что нам необходимо в игрушке данного плана:
Вот по этому простенькому ТЗ и будем работать далее. Как было показано выше, в main.cpp, мы уже указали нашему приложению запускаться в ландшафтном режиме. Значит больше необходимости править C++-код у нас нет. Открываем файл main.qml и приводим его к виду:
Что мы сделали? Мы создали окошко размером 640x360, задали его фон и добавили один дочерний элемент типа World, который в будущем должен является предком для всех физических объектов. Как несложно догадаться, объект World описывает весь игровой мир и задаёт его базовые параметры, а именно:
Их описание можно подглядеть в заголовочном файле box2dworld.h
Пустой физический мир в три строчки — это клёво. Но давайте разбавим его статикой. Или стенами, кому как угодно. Создадим новый QML-файл, назовём его Wall.qml. сложим рядом с приложением и заполним следующим содержимым:
Стена, как и все объекты на сцене (а объект Wold — это по сути сцена), являются объектами типа Body. Следовательно, Body — базовый класс для всех физических элементов. Он имеет следующие свойства:
Тело, как таковое не может обрабатывать столкновения с другими объектами. Для того, чтобы научить тело этому, необходимо задать свойство fixtures. Значениями для этого свойства могут являться Circle, Box и Polygon. Все они являются потомками базового класса Fixture, отвечающего за взаимодействие с другими объектами. Самостоятельно он конечно же недоступен из QML, а только через трёх своих потомков. Перечислим для наглядности доступные свойства.
Класс Fixture:
Каждый из потомков немного расширяет данный класс собственными свойствами:
Из теории становится понятно, что стена представляет собой физическое тело (Body) типа прямоугольника (Box) и графически представляется картинкой с заливкой. И теперь, имея одну стену, мы можем создать сколько угодно стен, нам же нужно их 4. Открываем main.qml и внутрь объекта World, после gravity.y: 0, дописываем описание наших стен:
Сохраняем всё и запускаем наше приложение, на экране мы увидим фоновый рисунок и 4 стены обрамляющие мир по краям.

Далее по плану у нас шарик, который может летать в пределах нашего мира и ударяться о стены. Для описания шарика создаём файл Ball.qml и заполняем его содержимым следующего характера:
Тоже самое что и со стеной, только вместо Box у нас Circle. Добавим наш шарик в созданный нами мир, после описания последней стены в объект World дописываем описание шарика:
Запускаем, видим шарик по центру экрана, который никуда не двигается за неимением гравитации и линейного ускорения. От умница какой…
Следующий шаг — платформа, представляющая единственный элемент управления игрока, которой мы будем отбивать шарик. По прежней схеме, новый файл Platform.qml, в нём:
Этот физический объект отличается от прочих тем, что мы позволяем пользователю водить им по экрану при помощи курсора мышки/пальца в горизонтальном направлении. В main.qml после описания Ball добавляем описание платформы:
В данный момент советую вспомнить о наших стенах. По хорошему, мы точно знаем, что они работают, но так как мы ограничены размерами экрана, мы можем скрыть наши стены за пределы экрана. чтобы не мозолили глаза и не мешались. Для этого, по очереди, в каждый из объектов Wall внутри World добавим по одному из свойств: к левой leftMargin: -width, к правой rightMargin: -width, к верхней topMargin: -height, и к нижней bottomMargin: -height. После чего вновь запустим и глянем на то что у нас получается:

Следующий пункт нашего плана. Кирпичики, которые необходимо сбивать шариком. Но! Мы не должны забывать, что места на экране у нас будет маловато. Поэтому попробуем реализовать данную часть игры иначе. А именно — вверху экрана будут находиться несколько кирпичиков зелёного цвета, по которым постоянно надо лупить шариком, не допуская того чтобы они стали красными. Если кирпич становится красным окончательно — лупить по нему уже бестолку. А в игру введём таймер, отсчитывающий количество времени до того момента, пока все кирпичики не покраснеют. Анимация перехода из зелёного в красный будет равна к примеру 20 секундам. После того как кирпич краснеет окончательно, он исчезает. Если же мы успеваем попасть по кирпичу, то 20-секундный таймер обнуляется и кирпичик начинает краснеть заново. Начнём с описания кирпичика в файле Brick.qml:
Как видим, здесь также нет ничего сложного: описание тела, описание его отображения, два состояния с плавной анимацией перехода между ними, таймер отсчитывающий 20 секунд с перезапуском после каждого столкновения с шариком и вспомогательная функция show(). В файле main.qml после объявления платформы добавляем объявления наших кирпичиков:
Кстати, не спрашивайте меня, почему я не воспользовался элементами Row и Repeat — с их использованием для автоматического создания элементов типа Body приложение падает. В самое начало файла добавим объявление новой переменной, после определения высоты и ширины:
По ней будем считать количество оставшихся кирпичей, когда оно поравняется к примеру двум — завершаем игру. То есть логика взаимодействия пользователя с игрой будет проста — необходимо, чтобы как можно больше времени на экране оставалось как минимум три кирпичика. Опишем счётчик секунд в самом низу объекта World:
Что же нам остаётся? Остаётся добавить экраны старта и финиша, ну и чуток поправить логику игры. Собственно это мелочи, которые в статье можно опустить. Приведу лишь наконец полный итоговый листинг файла main.qml:
Вот такое получилось демонстрационное приложение. Сейчас предлагаю посмотреть на то, что в итоге получилось, а затем прочесть пару заключительных строчек и написать свои впечатления от проделанной разработчиками плагина работы. Смотрим:
На мой взгляд вышло неплохо. Собственно говоря, на разработку самого приложения и на написание данной статьи ушло всего два вечера (вчера и сегодня). Это во первых говорит о простоте и очень низком пороге входа в разработку с использованием QML, а во вторых — о том качестве кода, которого раз за разом удаётся достигать разработчикам как самого фреймворка Qt, так и сторонних разработчиков, пишущих для него подобные плагины.
Плюс. хочется конечно же отметить, что Box2D сам по себе не имеет привязок к какой-либо ОС и является платформо независимым, следовательно, созданное приложение одинаково хорошо будет работать как на десктопных, так и на мобильных платформах. Ну даже в данном примере вы можете видеть скриншоты из под Windows и видео из под Linux.
Конечно. в данной статье рассмотрен не весь функционал Box2D, который был перенесён в QML, остались ещё как минимум Joint-ы. С другой стороны, я считаю, что этого материала вполне достаточно для понимания сути вещей. И уже имея представление о связке QML/Box2D можно запросто клепать игрушки с использованием физики. Это могут быть и лабиринты использующие акселерометер телефона и падающие кубики, забавно разлетающиеся от ударов друг о друга и машинки или мотоциклы по типу X-Moto и много чего ещё. При этом не забываем. что QML — лишь обёртка над C++ классами и работать собственно приложение будет так как будто изначально писалось на C++.
Как обычно, исходники можно забрать на страничке проекта: code.google.com/p/quickanoid/downloads/list
Благодарю за потраченное время.

Даже не смотря на то, что многие программисты, в данный момент, не спешат переводить разработку своих приложений и игр на рельсы Qt Quick, инфраструктура вокруг самой технологии с каждым днём лишь растёт и развивается.
Вот и до симуляции физики в двухмерном пространстве дошло дело. А вернее до появления QML-плагина. который позволяет с присущей Qt Quick легкостью интегрировать в своё приложения физический движок Box2D. Вот об этом сегодня и поговорим. А точнее, разберём на примере реализации простого арканоида, насколько быстро можно создать простенькую игру, никогда ранее не работая с физическими движками и почти незная терминологии.
Связываем QML и Box2d
Первым делом, нам необходимо получить исходники плагина. Для этого переходим по ссылке gitorious.org/qml-box2d/qml-box2d/trees/master и справа кликаем по кнопке «Download master as tar.gz». Откладываем пока архив в сторонку и переходим в Qt Creator.
Здесь, создаём новый проект, типа «Приложение Qt Qucik». В мастере вводим название, расположение в файловой системе, выбираем профиль Qt, далее, далее, завершить.
А вот теперь начинается одна из самых важных частей. И обычно одна из самых сложных в ДРУГИХ языках и технологиях. Необходимо собственно подключить плагин к свежесозданному приложению. Для этого, распаковываем полученный архив в корневой каталог приложения и переименовываем получившийся каталог qml-box2d-qml-box2d в qml-box2d. B вносим одну новую строчку в .pro файл нашегно приложения:
include(qml-box2d/box2d-static.pri)
А main.cpp приведём к такому виду:
#include <QtGui/QApplication> #include "qmlapplicationviewer.h" #include "box2dplugin.h" Q_DECL_EXPORT int main(int argc, char *argv[]) { QScopedPointer<QApplication> app(createApplication(argc, argv)); Box2DPlugin plugin; plugin.registerTypes("Box2D"); QScopedPointer<QmlApplicationViewer> viewer(QmlApplicationViewer::create()); viewer->setOrientation(QmlApplicationViewer::ScreenOrientationLockLandscape); viewer->setMainQmlFile(QLatin1String("qml/Quickanoid/main.qml")); viewer->showExpanded(); return app->exec(); }
Здесь строчкой #include «box2dplugin.h» включаем заголовок плагина, а строчками
Box2DPlugin plugin; plugin.registerTypes("Box2D");
регистрируем в приложении типы Qt/Box2D, которые будут доступны и необходимы нам в будущем в QML.
Вот и всё. Этого достаточно для подключения плагина как статически линкуемой библиотеки. Конечно же, плагин можно собрать как самостоятельную единицу и сложить в общий каталог всех QML-плагинов в системе. Но для нашей цели подойдёт и первый вариант. Внешний вид получающегося проекта примерно таков:

Если сейчас попробовать скомпилировать приложение, то мы увидим стандартный Hello World, который является шаблоном для проекта по умолчанию в Qt Quick. Но это не интересно. Нам интересно использовать физику.
Формализуем описание игры
Итак, мы определились что будем делать арканоид. Перечислим, что нам необходимо в игрушке данного плана:
- Окно по умолчанию 360x640 — для более легкого портирования в будущем на мобильные устройства. И конечно закрепления его в ландшафтном режиме.
- Задний фон приложения — простенькая картинка, на фоне которой будет удобно играть.
- 4 стены, ограничивающие наш мир по краям окна.
- Шарик, летающий в пределах мира.
- Платформа в нижней части окна, для отбивания шарика.
- Несколько кирпичиков в верхней части окна, которые необходимо сбивать нашим шариком.
- Счётчик времени на экране.
- Стартовый и финишный экраны игры.
Реализуем составленное задание
Вот по этому простенькому ТЗ и будем работать далее. Как было показано выше, в main.cpp, мы уже указали нашему приложению запускаться в ландшафтном режиме. Значит больше необходимости править C++-код у нас нет. Открываем файл main.qml и приводим его к виду:
import QtQuick 1.0 import Box2D 1.0 Image { id: screen; source: "images/bg.jpeg" width: 640 height: 360 World { id: world width: screen.width height: screen.height gravity.x: 0 gravity.y: 0 } }
Что мы сделали? Мы создали окошко размером 640x360, задали его фон и добавили один дочерний элемент типа World, который в будущем должен является предком для всех физических объектов. Как несложно догадаться, объект World описывает весь игровой мир и задаёт его базовые параметры, а именно:
- gravity — гравитация по X и Y. Для нашего приложения гравитация не нужна.
- И несколько параметров, с правильным переводом которых, у меня, к сожалению, возникают проблемы: timeStep, velocityIterations, positionIterations, frameTime
Их описание можно подглядеть в заголовочном файле box2dworld.h
Пустой физический мир в три строчки — это клёво. Но давайте разбавим его статикой. Или стенами, кому как угодно. Создадим новый QML-файл, назовём его Wall.qml. сложим рядом с приложением и заполним следующим содержимым:
import QtQuick 1.0 import Box2D 1.0 Body { property alias image: image.source bodyType: Body.Static fixtures: Box { anchors.fill: parent friction: 1.0 } Rectangle { anchors.fill: parent color: "brown" } Image { id: image anchors.fill: parent source: "images/wall.jpg" fillMode: Image.Tile } }
Перерыв на теорию
Стена, как и все объекты на сцене (а объект Wold — это по сути сцена), являются объектами типа Body. Следовательно, Body — базовый класс для всех физических элементов. Он имеет следующие свойства:
- active — включить/выключить физику на элементе
- linearVelocity — линейное ускорение
- fixtures — границы тела, по которым будут определяться столкновения
- bodyType — тип тела, статическое, динамическое или кинематическое
- fixedRotation — запретить вращение
- sleepingAllowed — разрешить автоматически отключать физику для экономии ресурсов
- linearDamping, angularDamping, bullet — не понятно с первого взгляда
Тело, как таковое не может обрабатывать столкновения с другими объектами. Для того, чтобы научить тело этому, необходимо задать свойство fixtures. Значениями для этого свойства могут являться Circle, Box и Polygon. Все они являются потомками базового класса Fixture, отвечающего за взаимодействие с другими объектами. Самостоятельно он конечно же недоступен из QML, а только через трёх своих потомков. Перечислим для наглядности доступные свойства.
Класс Fixture:
- density — удельный вес
- friction — сила трения
- restitution — упругость/отдача
- groupIndex — индекс в группе (предположительно группа — один объект Body)
- collidesWith — список объектов. с которыми соприкасается текущий объект в текущий момент
- sensor, categories — дополнительные параметры
Каждый из потомков немного расширяет данный класс собственными свойствами:
- Класс Box, не добавляет новых свойств, но использует стандартные ширину и высоту для задания границ прямоугольника.
- Класс Circle вводит свойство radius, который как это ни странно является радиусом круглого объекта, например колеса.
- Класс Polygon добавляет свойство verticles, содержащий список вершин объекта для более точной физической симуляции.
Обратно к практике
Из теории становится понятно, что стена представляет собой физическое тело (Body) типа прямоугольника (Box) и графически представляется картинкой с заливкой. И теперь, имея одну стену, мы можем создать сколько угодно стен, нам же нужно их 4. Открываем main.qml и внутрь объекта World, после gravity.y: 0, дописываем описание наших стен:
Wall { id: wallLeft width: 10 anchors { bottom: parent.bottom left: parent.left top: parent.top } } Wall { id: wallRight width: 10 anchors { bottom: parent.bottom right: parent.right top: parent.top } } Wall { id: wallTop height: 10 anchors { left: parent.left right: parent.right top: parent.top } } Wall { id: wallBottom height: 10 anchors { left: parent.left right: parent.right bottom: parent.bottom } }
Сохраняем всё и запускаем наше приложение, на экране мы увидим фоновый рисунок и 4 стены обрамляющие мир по краям.

Далее по плану у нас шарик, который может летать в пределах нашего мира и ударяться о стены. Для описания шарика создаём файл Ball.qml и заполняем его содержимым следующего характера:
import QtQuick 1.0 import Box2D 1.0 Body { id: ball fixedRotation: false sleepingAllowed: false fixtures: Circle { id: circle radius: 12 anchors.fill: parent density: 0; friction: 10; restitution: 1.05; } Image { id: circleRect anchors.centerIn: parent width: circle.radius * 2 height: width smooth: true source: "images/ball.png" } }
Тоже самое что и со стеной, только вместо Box у нас Circle. Добавим наш шарик в созданный нами мир, после описания последней стены в объект World дописываем описание шарика:
Ball { id: ball x: parent.width/2 y: parent.height/2 }
Запускаем, видим шарик по центру экрана, который никуда не двигается за неимением гравитации и линейного ускорения. От умница какой…
Следующий шаг — платформа, представляющая единственный элемент управления игрока, которой мы будем отбивать шарик. По прежней схеме, новый файл Platform.qml, в нём:
import QtQuick 1.0 import Box2D 1.0 Body { id: platform width: platformBg.width height: platformBg.height x: parent.width/2 - width/2 y: parent.height - platformBg.height - 5 bodyType: Body.Static fixtures: Box { id: platformBox anchors.fill: parent friction: 10 density: 300; } Image { id: platformBg smooth: true source: "images/platform.png" } MouseArea { anchors.fill: parent drag.target: platform drag.axis: Drag.XAxis drag.minimumX: 0 drag.maximumX: screen.width - platform.width } }
Этот физический объект отличается от прочих тем, что мы позволяем пользователю водить им по экрану при помощи курсора мышки/пальца в горизонтальном направлении. В main.qml после описания Ball добавляем описание платформы:
Platform { id: platform }
В данный момент советую вспомнить о наших стенах. По хорошему, мы точно знаем, что они работают, но так как мы ограничены размерами экрана, мы можем скрыть наши стены за пределы экрана. чтобы не мозолили глаза и не мешались. Для этого, по очереди, в каждый из объектов Wall внутри World добавим по одному из свойств: к левой leftMargin: -width, к правой rightMargin: -width, к верхней topMargin: -height, и к нижней bottomMargin: -height. После чего вновь запустим и глянем на то что у нас получается:

Следующий пункт нашего плана. Кирпичики, которые необходимо сбивать шариком. Но! Мы не должны забывать, что места на экране у нас будет маловато. Поэтому попробуем реализовать данную часть игры иначе. А именно — вверху экрана будут находиться несколько кирпичиков зелёного цвета, по которым постоянно надо лупить шариком, не допуская того чтобы они стали красными. Если кирпич становится красным окончательно — лупить по нему уже бестолку. А в игру введём таймер, отсчитывающий количество времени до того момента, пока все кирпичики не покраснеют. Анимация перехода из зелёного в красный будет равна к примеру 20 секундам. После того как кирпич краснеет окончательно, он исчезает. Если же мы успеваем попасть по кирпичу, то 20-секундный таймер обнуляется и кирпичик начинает краснеть заново. Начнём с описания кирпичика в файле Brick.qml:
import QtQuick 1.0 import Box2D 1.0 Body { id: brick width: parent.width/5 - 5 height: 15 anchors { top: parent.top topMargin: -height/2 } signal disappear() property bool contacted : false bodyType: Body.Static fixtures: Box { anchors.fill: parent friction: 1.0 onBeginContact: { contacted = true } onEndContact: { contacted = false } } Timer { id: timer interval: 20000; running: true; repeat: false onTriggered: { brick.visible = false; brick.active = false; disappear(); } } Rectangle { id: brickRect anchors.fill: parent radius: 6 state: "green" states: [ State { name: "green" when: brick.contacted PropertyChanges { target: brickRect color: "#7FFF00" } PropertyChanges { target: timer running: false } }, State { name: "red" when: !brick.contacted PropertyChanges { target: brickRect color: "red" } PropertyChanges { target: timer running: true } } ] transitions: [ Transition { from: "green" to: "red" ColorAnimation { from: "#7FFF00"; to: "red"; duration: 20000; } } ] } function show() { brick.visible = true; brick.active = true; state = "green" } function hide() { brick.visible = false; brick.active = false; } }
Как видим, здесь также нет ничего сложного: описание тела, описание его отображения, два состояния с плавной анимацией перехода между ними, таймер отсчитывающий 20 секунд с перезапуском после каждого столкновения с шариком и вспомогательная функция show(). В файле main.qml после объявления платформы добавляем объявления наших кирпичиков:
Brick { id: brick1 x: 3; onDisappear: bricksCount-- } Brick { id: brick2 anchors { left:brick1.right leftMargin: 5 } onDisappear: bricksCount-- } Brick { id: brick3 anchors { left:brick2.right leftMargin: 5 } onDisappear: bricksCount-- } Brick { id: brick4 anchors { left:brick3.right leftMargin: 5 } onDisappear: bricksCount-- } Brick { id: brick5 anchors { left:brick4.right leftMargin: 5 } onDisappear: bricksCount-- }
Кстати, не спрашивайте меня, почему я не воспользовался элементами Row и Repeat — с их использованием для автоматического создания элементов типа Body приложение падает. В самое начало файла добавим объявление новой переменной, после определения высоты и ширины:
property int bricksCount: 5
По ней будем считать количество оставшихся кирпичей, когда оно поравняется к примеру двум — завершаем игру. То есть логика взаимодействия пользователя с игрой будет проста — необходимо, чтобы как можно больше времени на экране оставалось как минимум три кирпичика. Опишем счётчик секунд в самом низу объекта World:
Text { id: secondsPerGame anchors { bottom: parent.bottom left: parent.left } color: "white" font.pixelSize: 36 text: "0" Timer { interval: 1000; running: true; repeat: true onTriggered: secondsPerGame.text = parseInt(secondsPerGame.text) + 1 } }
Что же нам остаётся? Остаётся добавить экраны старта и финиша, ну и чуток поправить логику игры. Собственно это мелочи, которые в статье можно опустить. Приведу лишь наконец полный итоговый листинг файла main.qml:
import QtQuick 1.0 import Box2D 1.0 Image { id: screen; source: "images/bg.jpeg" width: 640 height: 360 property int bricksCount: 5 World { id: world width: screen.width height: screen.height visible: false gravity.x: 0 gravity.y: 0 Wall { id: wallLeft width: 10 anchors { bottom: parent.bottom left: parent.left leftMargin: -width top: parent.top } } Wall { id: wallRight width: 10 anchors { bottom: parent.bottom right: parent.right rightMargin: -width top: parent.top } } Wall { id: wallTop height: 10 anchors { left: parent.left right: parent.right topMargin: -height top: parent.top } } Wall { id: wallBottom height: 10 anchors { left: parent.left right: parent.right bottom: parent.bottom bottomMargin: -height } onBeginContact: { console.log(other) finishGame() } } Ball { id: ball x: parent.width/2 y: parent.height/2 } Platform { id: platform } Brick { id: brick1 x: 3; onDisappear: bricksCount-- } Brick { id: brick2 anchors { left:brick1.right leftMargin: 5 } onDisappear: bricksCount-- } Brick { id: brick3 anchors { left:brick2.right leftMargin: 5 } onDisappear: bricksCount-- } Brick { id: brick4 anchors { left:brick3.right leftMargin: 5 } onDisappear: bricksCount-- } Brick { id: brick5 anchors { left:brick4.right leftMargin: 5 } onDisappear: bricksCount-- } Text { id: secondsPerGame anchors { bottom: parent.bottom left: parent.left } color: "white" font.pixelSize: 36 text: "0" Timer { id: scoreTimer interval: 1000; running: true; repeat: true onTriggered: secondsPerGame.text = parseInt(secondsPerGame.text) + 1 } } } Item { id:screenStart anchors.fill: parent visible: false Text { id: startGame anchors.centerIn: parent color: "white" font.pixelSize: 36 text: "Start Game!" MouseArea { anchors.fill: parent onClicked: { screen.startGame() } } } } Item { id:screenFinish anchors.fill: parent visible: false Text { id: score anchors.centerIn: parent color: "white" font.pixelSize: 36 text: "Game over! Your score is: " + secondsPerGame.text } Text { id: restartGame anchors { top: score.bottom horizontalCenter: parent.horizontalCenter } color: "white" font.pixelSize: 36 text: "Restart Game!" MouseArea { anchors.fill: parent onClicked: { screen.startGame() } } } } function startGame() { screen.state = "InGame"; bricksCount = 5 brick1.show() brick2.show() brick3.show() brick4.show() brick5.show() secondsPerGame.text = "0" platform.x = screen.width/2 - platform.width/2 ball.linearVelocity.x = 0 ball.linearVelocity.y = 0 ball.active = true; ball.x = platform.x + platform.width/2 ball.y = platform.y - ball.height ball.x = screen.width/2 ball.y = screen.height/2 ball.applyLinearImpulse(Qt.point(50, 300), Qt.point(ball.x, ball.y)) scoreTimer.running = true } function finishGame(){ screen.state = "FinishScreen"; brick1.hide() brick2.hide() brick3.hide() brick4.hide() brick5.hide() ball.active = false; ball.applyLinearImpulse(Qt.point(0,0), Qt.point(ball.x, ball.y)) scoreTimer.running = false } onBricksCountChanged: { console.log(bricksCount) if (bricksCount <=2){ finishGame() } } Component.onCompleted: { startGame() } states: [ State { name: "StartScreen" PropertyChanges { target: screenStart visible: true } }, State { name: "InGame" PropertyChanges { target: world visible: true } }, State { name: "FinishScreen" PropertyChanges { target: screenFinish visible: true } } ] state: "StartScreen" }
В сумме
Вот такое получилось демонстрационное приложение. Сейчас предлагаю посмотреть на то, что в итоге получилось, а затем прочесть пару заключительных строчек и написать свои впечатления от проделанной разработчиками плагина работы. Смотрим:
На мой взгляд вышло неплохо. Собственно говоря, на разработку самого приложения и на написание данной статьи ушло всего два вечера (вчера и сегодня). Это во первых говорит о простоте и очень низком пороге входа в разработку с использованием QML, а во вторых — о том качестве кода, которого раз за разом удаётся достигать разработчикам как самого фреймворка Qt, так и сторонних разработчиков, пишущих для него подобные плагины.
Плюс. хочется конечно же отметить, что Box2D сам по себе не имеет привязок к какой-либо ОС и является платформо независимым, следовательно, созданное приложение одинаково хорошо будет работать как на десктопных, так и на мобильных платформах. Ну даже в данном примере вы можете видеть скриншоты из под Windows и видео из под Linux.
Конечно. в данной статье рассмотрен не весь функционал Box2D, который был перенесён в QML, остались ещё как минимум Joint-ы. С другой стороны, я считаю, что этого материала вполне достаточно для понимания сути вещей. И уже имея представление о связке QML/Box2D можно запросто клепать игрушки с использованием физики. Это могут быть и лабиринты использующие акселерометер телефона и падающие кубики, забавно разлетающиеся от ударов друг о друга и машинки или мотоциклы по типу X-Moto и много чего ещё. При этом не забываем. что QML — лишь обёртка над C++ классами и работать собственно приложение будет так как будто изначально писалось на C++.
Как обычно, исходники можно забрать на страничке проекта: code.google.com/p/quickanoid/downloads/list
Благодарю за потраченное время.