Этот пост участвует в конкурсе „Умные телефоны за умные посты“
Даже не смотря на то, что многие программисты, в данный момент, не спешат переводить разработку своих приложений и игр на рельсы 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
Благодарю за потраченное время.