Evgenii Legotckoi
Evgenii Legotckoi14 липня 2016 р. 05:57

Гра на QML - Урок 1. Ігрова арена та динамічні об'єкти

Після першого досвіду написання гри на Qt під Android, хочу поділитися цим досвідом і пропоную разом написати простеньку гру в стилі "Убий крота". Це гра, в якій потрібно встигати потрапляти по кротах, які вилазять з нір, але враховуючи, що це буде спрощена гра, замість кротів використовуємо круглі мішені, які будуть з'являтися на ігровому полі. Як ігрове поле використовуватиме сітку 6 на 6 осередків. Але для створення поля не буде використовуватися якийсь спеціальний об'єкт типу GridLayout . В ігровому полі сітка формуватиметься з кількості рядків, колонок та довжини сторони квадратного осередку. Дані про заповненість осередку будуть зберігатися в двовимірному масиві, який буде сформований в javascript складовій (забуваємо, що QML - це декларативна JSON-подібна мова з підтримкою javascript).

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

Проект складається з наступних файлів:

  • TargetGame.pro - профайл проекту;
  • deployment.pri - файл складання та деплою проекту;
  • main.cpp - файл з main функцією проекту;
  • main.qml - основний файл QML-шару з об'єктом головного вікна програми;
  • Target.qml - файл QML з описом об'єкта мішені;
  • Target.png - зовнішній вигляд мішені є png малюнком;
  • GameArea.qml - ігрова арена з сіткою для розміщення мішеней;
  • logic.js - файл із javascript логікою гри.

Вивчимо зміст лише тих файлів, які від змісту за умовчанням під час створення проекту.


main.qml

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

Відмінним моментом цього файлу є підключення файлу logic.js як тип Logic , який відповідатиме з логіку гри і в даному файлі, після створення об'єкта головного вікна програми з ігровою ареною, ми передамо вказівник на ігрову арену в ядро логіки гри, щоб запам'ятати поточний стан ігрового поля та провести первинну ініціалізацію ігрового поля, а також масиву ігрового поля, що відповідатиме вміст осередків сітки. Робиться це викликом функції newGameState(), і передачею до неї як аргумент id ігрової арени. У даному уроці ми не відстежуємо стан гри, тобто чи запущено гру або зупинено, тому ігровий таймер арени запуститися відразу ж після запуску програми.

import QtQuick 2.7
import QtQuick.Controls 1.5
import "logic.js" as Logic

ApplicationWindow {
    visible: true
    width: 420
    height: 480
    title: qsTr("Target")

    GameArea {
        id: gameArea
        anchors.centerIn: parent
    }

    // По окончании создания окна инициализируем состояние игрового поля
    Component.onCompleted: {
        Logic.newGameState(gameArea)
    }
}

GameArea.qml

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

Ігрова арена є прямокутником з властивостями попередньо заданих рядків і колонок, а також розмірів однієї сторони осередку. Залежно від цих властивостей і розраховуються ширина та висота ігрової арени. Також в ігровій арені присутній таймер, зі спрацьовування якого створюватимуться мішені.

Створення мішеней супроводжується перевіркою доступності випадково вибраної комірки. Вибір осередку здійснюється за допомогою вибору випадкових рядки та колонки, за допомогою отримання випадкового цілого числа у вибраному діапазоні від мінімального до максимального включно за допомогою функції getRandomRound(min, max) , яка також описана в logic.js.

Функція checkEmptyField(row, column) перевіряє, чи існує якийсь об'єкт у заданій комірці, і якщо комірка порожня, то створюється ціль за допомогою функції createTarget(parent, row, column).

import QtQuick 2.7
import "logic.js" as Logic

Rectangle {
    id: grid
    property int squareSize: 64 // Размер ячейки игровой сетки
    property int rows: 6        // Количество строк сетки
    property int cols: 6        // Количество колонок сетки

    width: cols * squareSize    // Ширина и высота игровой арены
    height: rows * squareSize   // Будет зависеть от количества колонок, строк и размера одной ячейки

    // Таймер для создания мишеней
    Timer {
        id: createTargetsTimer
        interval: 350; running: true; repeat: true;
        // Раз в секунду выбираем случайную ячейку,
        // проверяем наличие мишени в ней, и если ячейка пустая,
        // то создаём мишень
        onTriggered: {
            var targetRow = Logic.getRandomRound(0, rows - 1);
            var targetColumns = Logic.getRandomRound(0, cols - 1);
            if (Logic.checkEmptyField(targetRow, targetColumns)) {
                Logic.createTarget(grid ,targetRow, targetColumns)
            }
        }
    }
}

Target.qml

Перш, ніж вивчати вміст логічного javascript ядра гри, вивчимо опис об'єкта мішені.

Логіка поведінки мішені така. Після створення, мета має зникнути через випадковий проміжок часу. Причому не просто зникнути, тобто змінивши рівень прозорості, а самознищення. Для цього є метод destroy(), який буде застосований на кореневому об'єкті, його id: root. Також Ви можете побачити функцію destroyTarget(row, column) у javascript ядрі гри. Ця функція служить для очищення осередку двовимірного інформаційного масиву сітки.

Оскільки розмір комірки становить 64 пікселі, а розмір мішені 56 пікселів, то використовуючи нехитрі обчислення, ми визначаємо положення мішені на ігровому полі залежно від колонки та рядка, які вона повинна займати.

import QtQuick 2.7
import "logic.js" as Logic

Image {
    id: root
    property int row: 0
    property int col: 0

    width: 56
    height: 56
    x: (col * (width + 8)) + 4
    y: (row * (height + 8)) + 4

    source: "Target.png"  // Загружаем изображение мишени

    Timer {
        interval: Logic.getRandomRound(350, 1500); running: true; repeat: false
        onTriggered: root.opacity = 0.0
    }

    // Когда изображение станет прозрачным. мы уничтожим сам объект
    onOpacityChanged: {
        if (opacity == 0.0) {
            Logic.destroyTarget(row, col)
            root.destroy()
        }
    }

    // Задаём поведение анимации свойства прозрачности объекта
    Behavior on opacity { PropertyAnimation { duration: 250 } }
}

logic.js

Цей файл містить оголошення .pragma library *. *

.pragma library

Це зроблено у зв'язку з необхідністю, щоб можна було звертатися до стану гри ( var gameState ) з будь-якого місця, де цей файл підключений в QML шарі. Фактично змінні стають ресурсами для різних об'єктів у грі. І найголовнішим ресурсом є gameState (він же ігрова арена GameArea ). Справа в тому, що якщо ми можемо звертатися до ігрової арени безпосередньо по id з різних об'єктів, які статично оголошені в ігровій арені, або у файлі, де оголошена сама арена, то в динамічних ресурсах, як, наприклад, у Target .qml , у нас такий спосіб не спрацює. Для цього і використовується gameState з logic.js, який дозволяє перевіряти стан ігрової арени незалежно від того, чи оголошено об'єкт статично (під станом я розумію, чи запущена гра, чи поставлена на паузу, що буде розглянуто у наступних статтях).

Тепер звернемося до вмісту файлу logic.js. Тут є два найцікавіші моменти:

  1. Це створення двовимірного масиву на JavaScript, який служить інформаційною моделлю про об'єкти, що містяться в сітці, і перевірка на предмет існування об'єкта в осередку.
  2. Це створення динамічних мішеней.

Якщо з першим все досить зрозуміло з коду, про друге скажу кілька слів. Для динамічного створення мішені в даному випадку використовується компонент заготівля, яка створюється з файлу Target.qml. Причому така заготівля потрібна лише одна навіть для безлічі таких однотипних елементів у грі.

var targetComponent = Qt.createComponent("Target.qml");

А створення безпосередньо самого об'єкта на ігровій арені здійснюється нижче наступної функції.

function createTarget(parent, row, column)
{
    var target = targetComponent.createObject(parent, {"row": row, "col": column})
    gameField[row][column] = target;
}

У нашому випадку достатньо створити об'єкт, застосувавши метод createObject на заготівлі компонента мішені із зазначенням батьківського об'єкта, тобто об'єкта в якому буде розташовуватися мішень (в даному випадку це буде ігрова арена) і перерахувавши параметри, які повинні бути встановлені при створенні об'єкта, в даному Якщо це рядок і колонка, в яких буде розміщуватися мета.

Повний лістинг файлу

// Объявляем, как библиотеку, чтобы иметь доступ
// к разделяемым ресурсам, например состоянию игры
.pragma library

var gameState       // Локальное объявление состояния игры
                    // в нашем случае это будет игровая область gameArea
function getGameState() { return gameState; }

var gameField;      // Игровое поле, игровая сетка
// Создаём заготовку для мишеней
var targetComponent = Qt.createComponent("Target.qml");

// Инициализируем новое состояние игры
function newGameState(gameArea)
{
    gameState = gameArea;
    // Игровой сеткой будет служить двумерный массив,
    // в котором будем сохранять информацию о наличии в ячейках объектов
    gameField = create2DArray(gameState.rows, gameState.cols);
    return gameState;
}
// Функция получения случайного целого числа
// в диапазоне чисел включительно
function getRandomRound(min, max)
{
    return Math.round(Math.random() * (max - min) + min);
}

// Создаём мишень из компонента заготовки
function createTarget(parent, row, column)
{
    var target = targetComponent.createObject(parent, {"row": row, "col": column})
    gameField[row][column] = target;
}

// Мишень удаляется из массива сетки
function destroyTarget(row, column)
{
    gameField[row][column] = null;
}

// Функция создания двумерного массива сетки
function create2DArray(rows, columns)
{
  var arr = [];

  for (var i=0;i<rows;i++) {
     arr[i] = [];
  }

  return arr;
}

// Проверка на предмет наличия какого либо объекта в выбранной ячейке
function checkEmptyField(row, column)
{
    if (gameField[row][column] == null) {
        return true;
    } else {
        return false;
    }
}

Підсумок

В результаті ми отримаємо програму з ігровим полем 6 на 6, в якому при запуску будуть генеруватися мішені, що зникають.

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

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

Відеоурок

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

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

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
Дмитрий

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:60бали,
  • Рейтинг балів-1
Дмитрий

C++ - Тест 003. Условия и циклы

  • Результат:92бали,
  • Рейтинг балів8
d
  • dsfs
  • 26 квітня 2024 р. 04:56

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80бали,
  • Рейтинг балів4
Останні коментарі
k
kmssr08 лютого 2024 р. 18:43
Qt Linux - Урок 001. Автозапуск програми Qt під Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко05 лютого 2024 р. 01:50
Qt WinAPI - Урок 007. Робота з ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 грудня 2023 р. 10:30
Boost - статичне зв&#39;язування в проекті CMake під Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 грудня 2023 р. 08:38
Boost - статичне зв&#39;язування в проекті CMake під Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik18 грудня 2023 р. 21:01
Qt/C++ - Урок 056. Підключення бібліотеки Boost в Qt для компіляторів MinGW і MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Тепер обговоріть на форумі
G
George1307 травня 2024 р. 00:27
добавить qlineseries в функции в функции: "GPlotter::addSeries(QString title, QVector &arr)" я вызываю метод setChart(...), я в конструктор передал адрес на QChartView элемент
BlinCT
BlinCT05 травня 2024 р. 05:46
Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
PS
Peter Son03 травня 2024 р. 17:57
Best Indian Food Restaurant In Cincinnati OH Ready to embark on a gastronomic journey like no other? Join us at App india restaurant and discover why we're renowned as the Best Indian Food Restaurant In Cincinnati OH . Whether y…
Evgenii Legotckoi
Evgenii Legotckoi02 травня 2024 р. 14:07
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.
IscanderChe
IscanderChe30 квітня 2024 р. 04:22
Во Flask рендер шаблона не передаётся в браузер Доброе утро! Имеется вот такой шаблон: <!doctype html><html> <head> <title>{{ title }}</title> <link rel="stylesheet" href="{{ url_…

Слідкуйте за нами в соціальних мережах