После того, как была сделана игровая арена и добавлены мишени, настало время реализовать попадание по мишеням, промахи и систему начисления и списывания очков.
Система начисления и списывания очков будет следующая:
- В случае промахов, предлагаю сделать следы пулевых отверстий, нескольких видов, а также списывание по одному очку с игрового счёта.
- В случае исчезновения мишени без попадания по ней также сделать списывание одного очка с игрового счёта.
- В случае попадания по мишени начислять от одного до пяти очков в зависимости от того, насколько близко к центру пришлось данное попадание.
Счёт будет отображаться в верхней части игрового поля.
Структура проекта
Для реализации данной задумки понадобится, кроме модификации части файлов, добавить следующие файлы:
- InfoBar.qml - который будет отображать информацию о счёте;
- Bullet.qml - файл с описанием типа пулевого отверстия;
- bullet-1.png, bullet-2.png, bullet-3.png, bullet-4.png, bullet-5.png, bullet-6.png - файлы с изображениями пулевых отверстий;
Возможные варианты пулевых отверстий размещаются в папке graphics, туда же было перенесено изображение мишени.
Изображения пулевых отверстий
Для разнообразия будет применяться несколько видов пулевых отверстий похожих на это.
GameArea.qml
Прежде, чем переходить к рассмотрению содержимого новых файлов, обратимся к изменённому содержимому файла GameArea.qml, а также логического ядра на javascript в файле logic.js.
Основными нововведениями здесь является появление свойства scores для подсчёта очков, появление InfoBar и обработчика кликов мыши внутри игровой арены.
Свойство scores содержит текущий счёт и его значение передаётся свойству scores в infoBar для визуализации текущего счёта игроку.
В случае клика по игровой арене, что равносильно промаху, создаётся объект пулевого отверстия и на единицу уменьшается текущий счёт. Пулевое отверстие создаётся подобно тому, как реализовано создание мишеней в прошлом уроке, то есть с помощью логического ядра Logic, которое находится в файле logic.js.
import QtQuick 2.7 import "logic.js" as Logic Rectangle { id: grid ... property int scores: 0 // Очки ... InfoBar { id: infoBar scores: grid.scores } // Создаём след от пули при клике по игровой арене MouseArea { anchors.fill: parent onClicked: { Logic.createBullet(grid, mouse.x, mouse.y) grid.scores--; } } ... }
logic.js
В логическое ядро игры добавим только создание пулевых отверстий. Обработка логики попаданий по мишеням будет добавляться в файле, описывающем мишень.
... // Создаём заготовку для мишеней var bulletComponent = Qt.createComponent("Bullet.qml"); ... // Создаём след от пули function createBullet(parent, x, y) { bulletComponent.createObject(parent, {"x": x, "y": y}); } ...
InfoBar.qml
InfoBar представляет собой обычный RowLayout, который будет прибит к верхнему краю игровой арены и растянут по её ширине. Текущий счёт будет отображаться в текстовом поле с id: scores . Чтобы установить доступ извне к свойству text данного текстового поля, необходимо воспользоваться свойством property alias .
import QtQuick 2.7 import QtQuick.Layouts 1.1 RowLayout { height: 40 anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top anchors.leftMargin: 6 anchors.rightMargin: 6 spacing: 6 z: 100 // Данное свойство устанавливает доступ к текстовому полю, // в которое будет устанавливаться текущий счёт property alias scores: scores.text Text { text: qsTr("Scores") font.pixelSize: 20 verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } Text { // Текстовое поле с текущим счётом id: scores font.pixelSize: 20 verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } // Своеобразный Spacer, который в RowLayout сдвинет текстовые поля к левому краю InfoBar Item { Layout.fillHeight: true Layout.fillWidth: true } }
Bullet.qml
Поведение пулевого отверстия аналогично поведению мишени. Время жизни пулевого отверстия ограничено и после исчезновения этот объект уничтожается. Отличие состоит в том, что делается смещение координаты (0, 0) в середину объекта, чтобы центр объекта совпадал точно с центром точки, куда был совершён клик. А также отличие состоит в том, что в объекте имеется массив с изображениями, из которого при создании выполняется случайный выбор внешнего вида пулевого отверстия.
import QtQuick 2.7 import QtQuick.Window 2.2 import "logic.js" as Logic Image { id: root width: 40 height: 40 // Устанавливаем смещение координаты (0, 0) в центр изображения transform: Translate { x: -width / 2 y: -height / 2 } // Задаём список массив возможных изображений выстрела пули property variant bullets: ["graphics/bullet-1.png", "graphics/bullet-2.png", "graphics/bullet-3.png", "graphics/bullet-4.png", "graphics/bullet-5.png", "graphics/bullet-6.png"] // При создании объекта выбираем случайное изображение source: bullets[Logic.getRandomRound(0, 5)] // Таймер, который будет отсчитывать случайную величину от 3500 до 10000 мс // и по окончании запускать исчезновение следа от пули Timer { interval: Logic.getRandomRound(3500, 10000); running: true; repeat: false onTriggered: root.opacity = 0.0 } // Когда изображение станет прозрачным. мы уничтожим сам объект onOpacityChanged: { if (opacity == 0.0) { root.destroy() } } // Задаём поведение анимации свойства прозрачности объекта Behavior on opacity { PropertyAnimation { duration: 100 } } }
Target.qml
Ну и вся соль - это попадание по мишени. Как видите, здесь имеется пять полей, которым мы присвоим различную стоимость от 1 до 5 очков.
Для того, чтобы правильно вычислить попадание по этим областям, нам понадобиться применить теорему Пифагора. Помните? Из курса школьной тригонометрии. Квадрат гипотенузы равен сумме квадратов катетов.
[latex]a 2 + b 2 = c^2[/latex]
Так вот, эта самая гипотенуза будет дальностью отклонения попадания от центра мишени, ну а факт попадание по той или иной области будем определять по радиусу окружности, начиная от самого меньшего круга. Эти проверки проводятся в функции isHit() , объявленной в данном файле.
Также нюансом здесь является то, что изображение мишени помещается в квадрат, а значит что и область клика также квадратная, поэтому если клик не попадает в область самого большого круга мишени, то мы возвращаем 0 и передаём клик дальше в игровую арену. Для этого необходимо установить propagateComposedEvents в true и запретить accept события мыши, тогда он пройдёт в ниже лежащие объекты по координате z.
Также не забудем уменьшать на единицу счёт игры, если мишень исчезла сама по себе без воздействия игрока. Это осуществляется через разделяемый объект из логического ядра игры, который хранит состояние игровой арены. То есть через Logic.gameState.scores.
import QtQuick 2.7 import "logic.js" as Logic Image { id: root ... // Когда изображение станет прозрачным. мы уничтожим сам объект onOpacityChanged: { if (opacity == 0.0) { Logic.gameState.scores-- Logic.destroyTarget(row, col) root.destroy() } } MouseArea { id: mouseArea anchors.fill: parent // Данное свойство определяет, будут ли передаваться события клика // в ниже лежащие элементы, то есть те, которые по координате z // находятся ниже мишени propagateComposedEvents: true onClicked: { var scores = isHit(root.width/2, root.height/2, mouse.x, mouse.y); if (scores != 0) { // Если количество очков отлично от нуля, то фиксируем попадание // Увеличиваем счёт и уничтожаем мишень Logic.gameState.scores += scores; Logic.destroyTarget(row, col); root.destroy(); } else { // В противном случае передаём событие клика в ниже лежащие элементы // по координате z mouse.accepted = false; } } } ... // Проверка попадания по мишени. // Проверяем длину линии от центра до места попадания по мишени // В зависимости от длины этой линии относительно радиуса 5-ти окружностей, из которых состоит мишень // определяем сколько очков получаем за попадание // a, b - координаты центра мишени // x, y - координаты точки попадания function isHit(a, b, x, y) { var length = Math.sqrt(Math.pow(Math.abs(a - x), 2) + Math.pow(Math.abs(b - y), 2)); if (length <= 5.6) { return 5; } else if (length <= 11.2) { return 4; } else if (length <= 16.8) { return 3; } else if (length <= 22.4) { return 2; } else if (length <= 28) { return 1; } else { // В данном случае мы попали в зону MouseArea, // которая является прямоугольником, но промахнулись // по самой мишени, которая является кругом return 0; } } }
Итог
Теперь у нас есть счёт и внешний вид приложения выглядит следующим образом.
Скачать исходники с Примером игры на QML с GitHub