- 1. Структура проекта
- 2. main.qml
- 3. GameArea.qml
- 4. Target.qml
- 5. logic.js
- 6. Итог
- 7. Вся серия уроков
- 8. Видеоурок
После первого опыта по написанию игры на 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. Здесь имеется два наиболее интересных момента:
- Это создание двумерного массива на javascript, который служит информационной моделью о содержащихся в сетке объектах, и проверка на предмет существования объекта в ячейке.
- Это создание динамических мишеней.
Если с первым всё достаточно понятно из кода, то про второе скажу несколько слов. Для динамического создания мишени в данном случае используется компонент заготовка, которая создаётся из файла 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