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

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

Комментарии

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

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

  • Результат:50баллов,
  • Очки рейтинга-4
m
  • molni99
  • 26 октября 2024 г. 11:37

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

  • Результат:80баллов,
  • Очки рейтинга4
m
  • molni99
  • 26 октября 2024 г. 11:29

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

  • Результат:20баллов,
  • Очки рейтинга-10
Последние комментарии
ИМ
Игорь Максимов22 ноября 2024 г. 22:51
Django - Урок 017. Кастомизированная страница авторизации на Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi1 ноября 2024 г. 0:37
Django - Урок 064. Как написать расширение для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 октября 2024 г. 18:19
Читалка fb3-файлов на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов5 октября 2024 г. 17:51
Django - Урок 064. Как написать расширение для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55 июля 2024 г. 21:02
QML - Урок 016. База данных SQLite и работа с ней в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Сейчас обсуждают на форуме
m
moogo22 ноября 2024 г. 18:17
Mosquito Spray System Effective Mosquito Systems for Backyard | Eco-Friendly Misting Control Device & Repellent Spray - Moogo ; Upgrade your backyard with our mosquito-repellent device! Our misters conce…
Evgenii Legotckoi
Evgenii Legotckoi25 июня 2024 г. 1:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 ноября 2024 г. 17:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject4 июня 2022 г. 13:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…

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