Evgenii Legotckoi
Evgenii Legotckoi14 июля 2016 г. 5: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.

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

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

Видеоурок

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
B

C++ - Тест 002. Константы

  • Результат:16баллов,
  • Очки рейтинга-10
B

C++ - Тест 001. Первая программа и типы данных

  • Результат:46баллов,
  • Очки рейтинга-6
FL

C++ - Тест 006. Перечисления

  • Результат:80баллов,
  • Очки рейтинга4
Последние комментарии
k
kmssr9 февраля 2024 г. 2:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко5 февраля 2024 г. 9:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 18:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 декабря 2023 г. 16:38
Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 декабря 2023 г. 5:01
Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Сейчас обсуждают на форуме
P
Pisych27 февраля 2023 г. 12:04
Как получить в массив значения из связанной модели? Спасибо, разобрался:))
AC
Alexandru Codreanu19 января 2024 г. 19:57
QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…
BlinCT
BlinCT27 декабря 2023 г. 16:57
Растягивать Image на парент по высоте Ну и само собою дял включения scrollbar надо чтобы был Flickable. Так что выходит как то так Flickable{ id: root anchors.fill: parent clip: true property url linkFile p…
Дмитрий
Дмитрий10 января 2024 г. 12:18
Qt Creator загружает всю оперативную память Проблема решена. Удалось разобраться с помощью утилиты strace. Запустил ее: strace ./qtcreator Начал выводиться весь лог работы креатора. В один момент он начал считывать фай…
Evgenii Legotckoi
Evgenii Legotckoi12 декабря 2023 г. 14:48
Побуквенное сравнение двух строк Добрый день. Там случайно не высылается этот сигнал textChanged ещё и при форматировани текста? Если решиать в лоб, то можно просто отключать сигнал/слотовое соединение внутри слота и …

Следите за нами в социальных сетях