Evgenii Legotckoi
14 липня 2016 р. 15:00

Гра на QML - Урок 2. Додавання слідів пострілів і системи очок

Після того, як була зроблена ігрова арена та додані мішені, настав час реалізувати попадання по мішеням, промахи та систему нарахування та списування очок.

Система нарахування та списування очок буде наступна:

  • У разі промахів, пропоную зробити сліди кульових отворів, декількох видів, а також списування по одному окуляру з ігрового рахунку.
  • У разі зникнення мішені без влучення по ній також зробити списування одного очка з ігрового рахунку.
  • У разі влучення по мішені нараховувати від одного до п'яти очок залежно від того, наскільки близько до центру довелося це влучення.

Рахунок буде відображатися у верхній частині ігрового поля.

Структура проекту

Для реалізації цього задуму знадобиться, крім модифікації частини файлів, додати такі файли:

  • 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.

  1. import QtQuick 2.7
  2. import "logic.js" as Logic
  3.  
  4. Rectangle {
  5. id: grid
  6.  
  7. ...
  8.  
  9. property int scores: 0 // Очки
  10.  
  11. ...
  12.  
  13. InfoBar {
  14. id: infoBar
  15. scores: grid.scores
  16. }
  17.  
  18. // Создаём след от пули при клике по игровой арене
  19. MouseArea {
  20. anchors.fill: parent
  21. onClicked: {
  22. Logic.createBullet(grid, mouse.x, mouse.y)
  23. grid.scores--;
  24. }
  25. }
  26.  
  27. ...
  28. }

logic.js

У логічне ядро гри додамо лише створення кульових отворів. Обробка логіки попадань по цілях буде додаватися у файлі, що визначає мета.

  1. ...
  2.  
  3. // Создаём заготовку для мишеней
  4. var bulletComponent = Qt.createComponent("Bullet.qml");
  5.  
  6. ...
  7.  
  8. // Создаём след от пули
  9. function createBullet(parent, x, y)
  10. {
  11. bulletComponent.createObject(parent, {"x": x, "y": y});
  12. }
  13.  
  14. ...

InfoBar.qml

InfoBar являє собою звичайний RowLayout, який буде прибитий до верхнього краю ігрової арени і розтягнутий по її ширині. Поточний рахунок відображатиметься у текстовому полі з id: scores . Щоб встановити доступ ззовні до властивості тексту цього текстового поля, необхідно скористатися властивістю property alias .

  1. import QtQuick 2.7
  2. import QtQuick.Layouts 1.1
  3.  
  4. RowLayout {
  5. height: 40
  6. anchors.left: parent.left
  7. anchors.right: parent.right
  8. anchors.top: parent.top
  9. anchors.leftMargin: 6
  10. anchors.rightMargin: 6
  11. spacing: 6
  12. z: 100
  13.  
  14. // Данное свойство устанавливает доступ к текстовому полю,
  15. // в которое будет устанавливаться текущий счёт
  16. property alias scores: scores.text
  17.  
  18. Text {
  19. text: qsTr("Scores")
  20.  
  21. font.pixelSize: 20
  22. verticalAlignment: Text.AlignVCenter
  23. horizontalAlignment: Text.AlignHCenter
  24. }
  25.  
  26. Text {
  27. // Текстовое поле с текущим счётом
  28. id: scores
  29.  
  30. font.pixelSize: 20
  31. verticalAlignment: Text.AlignVCenter
  32. horizontalAlignment: Text.AlignHCenter
  33. }
  34.  
  35. // Своеобразный Spacer, который в RowLayout сдвинет текстовые поля к левому краю InfoBar
  36. Item {
  37. Layout.fillHeight: true
  38. Layout.fillWidth: true
  39. }
  40. }

Bullet.qml

Поведінка кульового отвору аналогічна до поведінки мішені. Час життя кульового отвору обмежений і після зникнення цей об'єкт знищується. Відмінність полягає в тому, що робиться зсув координати (0, 0) в середину об'єкта, щоб центр об'єкта збігався точно з центром точки, куди було здійснено клік. А також відмінність полягає в тому, що в об'єкті є масив із зображеннями, з якого під час створення виконується випадковий вибір зовнішнього вигляду кульового отвору.

  1. import QtQuick 2.7
  2. import QtQuick.Window 2.2
  3. import "logic.js" as Logic
  4.  
  5. Image {
  6. id: root
  7. width: 40
  8. height: 40
  9.  
  10. // Устанавливаем смещение координаты (0, 0) в центр изображения
  11. transform: Translate {
  12. x: -width / 2
  13. y: -height / 2
  14. }
  15.  
  16. // Задаём список массив возможных изображений выстрела пули
  17. property variant bullets: ["graphics/bullet-1.png", "graphics/bullet-2.png", "graphics/bullet-3.png",
  18. "graphics/bullet-4.png", "graphics/bullet-5.png", "graphics/bullet-6.png"]
  19.  
  20. // При создании объекта выбираем случайное изображение
  21. source: bullets[Logic.getRandomRound(0, 5)]
  22.  
  23. // Таймер, который будет отсчитывать случайную величину от 3500 до 10000 мс
  24. // и по окончании запускать исчезновение следа от пули
  25. Timer {
  26. interval: Logic.getRandomRound(3500, 10000); running: true; repeat: false
  27. onTriggered: root.opacity = 0.0
  28. }
  29.  
  30. // Когда изображение станет прозрачным. мы уничтожим сам объект
  31. onOpacityChanged: {
  32. if (opacity == 0.0) {
  33. root.destroy()
  34. }
  35. }
  36.  
  37. // Задаём поведение анимации свойства прозрачности объекта
  38. Behavior on opacity { PropertyAnimation { duration: 100 } }
  39. }

Target.qml

Ну і вся сіль – це влучення по мішені. Як бачите, тут є п'ять полів, яким ми надамо різну вартість від 1 до 5 очок.

Для того, щоб правильно вирахувати попадання по цих областях, нам доведеться застосувати теорему Піфагора. Пам'ятаєте? Із курсу шкільної тригонометрії. Квадрат гіпотенузи дорівнює сумі квадратів катетів.

[латекс]a 2 + b 2 = c^2[/латекс]

Так ось, ця сама гіпотенуза буде дальністю відхилення попадання від центру мішені, ну а факт влучення по тій чи іншій області будемо визначати по радіусу кола, починаючи від найменшого кола. Ці перевірки проводяться у функції isHit() , оголошеної в даному файлі.

Також нюансом тут є те, що зображення мішені міститься в квадрат, а значить, що і область кліка також квадратна, тому якщо клік не потрапляє в область найбільшого кола мішені, то ми повертаємо 0 і передаємо клік далі в ігрову арену. Для цього необхідно встановити propagateComposedEvents у true і заборонити accept події миші, тоді він пройде в об'єкти, що лежать нижче, по координаті z.

Також не забудемо зменшувати на одиницю рахунок гри, якщо мета зникла сама по собі без дії гравця. Це здійснюється через об'єкт, що розділяється, з логічного ядра гри, який зберігає стан ігрової арени. Тобто через Logic.gameState.scores.

  1. import QtQuick 2.7
  2. import "logic.js" as Logic
  3.  
  4. Image {
  5. id: root
  6.  
  7. ...
  8.  
  9. // Когда изображение станет прозрачным. мы уничтожим сам объект
  10. onOpacityChanged: {
  11. if (opacity == 0.0) {
  12. Logic.gameState.scores--
  13. Logic.destroyTarget(row, col)
  14. root.destroy()
  15. }
  16. }
  17.  
  18. MouseArea {
  19. id: mouseArea
  20. anchors.fill: parent
  21. // Данное свойство определяет, будут ли передаваться события клика
  22. // в ниже лежащие элементы, то есть те, которые по координате z
  23. // находятся ниже мишени
  24. propagateComposedEvents: true
  25. onClicked: {
  26. var scores = isHit(root.width/2, root.height/2, mouse.x, mouse.y);
  27. if (scores != 0) {
  28. // Если количество очков отлично от нуля, то фиксируем попадание
  29. // Увеличиваем счёт и уничтожаем мишень
  30. Logic.gameState.scores += scores;
  31. Logic.destroyTarget(row, col);
  32. root.destroy();
  33. } else {
  34. // В противном случае передаём событие клика в ниже лежащие элементы
  35. // по координате z
  36. mouse.accepted = false;
  37. }
  38. }
  39. }
  40.  
  41. ...
  42.  
  43. // Проверка попадания по мишени.
  44. // Проверяем длину линии от центра до места попадания по мишени
  45. // В зависимости от длины этой линии относительно радиуса 5-ти окружностей, из которых состоит мишень
  46. // определяем сколько очков получаем за попадание
  47. // a, b - координаты центра мишени
  48. // x, y - координаты точки попадания
  49. function isHit(a, b, x, y)
  50. {
  51. var length = Math.sqrt(Math.pow(Math.abs(a - x), 2) + Math.pow(Math.abs(b - y), 2));
  52. if (length <= 5.6) {
  53. return 5;
  54. } else if (length <= 11.2) {
  55. return 4;
  56. } else if (length <= 16.8) {
  57. return 3;
  58. } else if (length <= 22.4) {
  59. return 2;
  60. } else if (length <= 28) {
  61. return 1;
  62. } else {
  63. // В данном случае мы попали в зону MouseArea,
  64. // которая является прямоугольником, но промахнулись
  65. // по самой мишени, которая является кругом
  66. return 0;
  67. }
  68. }
  69. }

Підсумок

Тепер у нас є рахунок і зовнішній вигляд програми виглядає так.

Завантажити вихідні записи з Прикладом гри на QML з GitHub

Вся серія уроків

Відеоурок

Рекомендовані статті на цю тему

По статті запитували0питання

0

Вам це подобається? Поділіться в соціальних мережах!

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
  • Останні коментарі
  • Evgenii Legotckoi
    16 квітня 2025 р. 17:08
    Благодарю за отзыв. И вам желаю всяческих успехов!
  • IscanderChe
    12 квітня 2025 р. 17:12
    Добрый день. Спасибо Вам за этот проект и отдельно за ответы на форуме, которые мне очень помогли в некоммерческих пет-проектах. Профессиональным программистом я так и не стал, но узнал мно…
  • AK
    01 квітня 2025 р. 11:41
    Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
  • Evgenii Legotckoi
    09 березня 2025 р. 21:02
    К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
  • VP
    09 березня 2025 р. 16:14
    Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…