Після того, як була зроблена ігрова арена та додані мішені, настав час реалізувати попадання по мішеням, промахи та систему нарахування та списування очок.
Система нарахування та списування очок буде наступна:
- У разі промахів, пропоную зробити сліди кульових отворів, декількох видів, а також списування по одному окуляру з ігрового рахунку.
- У разі зникнення мішені без влучення по ній також зробити списування одного очка з ігрового рахунку.
- У разі влучення по мішені нараховувати від одного до п'яти очок залежно від того, наскільки близько до центру довелося це влучення.
Рахунок буде відображатися у верхній частині ігрового поля.
Структура проекту
Для реалізації цього задуму знадобиться, крім модифікації частини файлів, додати такі файли:
- 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 . Щоб встановити доступ ззовні до властивості тексту цього текстового поля, необхідно скористатися властивістю 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 очок.
Для того, щоб правильно вирахувати попадання по цих областях, нам доведеться застосувати теорему Піфагора. Пам'ятаєте? Із курсу шкільної тригонометрії. Квадрат гіпотенузи дорівнює сумі квадратів катетів.
[латекс]a 2 + b 2 = c^2[/латекс]
Так ось, ця сама гіпотенуза буде дальністю відхилення попадання від центру мішені, ну а факт влучення по тій чи іншій області будемо визначати по радіусу кола, починаючи від найменшого кола. Ці перевірки проводяться у функції 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