Реклама

Как написать игру на Qt - Урок 1. Управление объектом

GameDev, getasynckeystate, QGraphicsItem, QGraphicsScene, Qt, WinAPI, написать игру

С этого урока начинается серия статей о том, как написать игру на Qt. В предыдущей статье было рассказано о системе позиционирования графических элементов QGraphicsItem в графической сцене QGraphicsScene. Был нарисован треугольник и помещён в центр графической сцены, размеры которой были 500 на 500 пикселей. А теперь настало время этот треугольник оживить, а вернее начать им управлять.

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

  • В окне располагается графическая сцена с размерами 500 на 500 пикселей (это уже сделано в предыдущем уроке);
  • В центре графической сцены находится красный треугольник (что также уже сделано в прошлом уроке);
  • Треугольник должен перемещаться при нажатии клавиш со стрелками Up, Down, Left, Right;
  • Треугольник не должен выходит за пределы графической сцены, то есть должен быть ограничен размерами графической сцены.

Примечание. В данном проекте используется WinAPI, поэтому проект применим для использования в операционной системе Windows, а для Linux и MacOS применим лишь алгоритм, который используется в данном уроке. Поэтому если Вы желаете написать игру под эти ОС, то необходимо будет использовать библиотеки этих ОС для асинхронной обработки нажатия клавиш.

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

  • Triangle.pro - профайл проекта, создается по умолчанию и в данном проекте не требует корректироваки;
  • main.cpp - файл, с которого стартует приложение, в данном файле вызывается widget, в котором будет располагаться графическая сцена с треугольником, которым мы будем управлять;
  • widget.h - заголовочный файл, вызываемого виджета с графической сценой;
  • widget.cpp - файл исходных кодов виджета;
  • triangle.h - заголовочный файл класса Треугольника , который наследован от QGraphicsItem;
  • triangle.cpp - файл исходных кодов класса Треугольник.

mainwindow.ui

В дизайнере интерфейсов просто закидываем QGraphicsView в виджет. Больше ничего не требуется.

widget.h

В данном файле всего лишь объявляем графическую сцену, объект треугольника, которым будем управлять, а также таймер, по отсчетам которого будет производиться проверка состояния клавиш клавиатуры, которыми мы будем управлять треугольником на графической сцене.

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QGraphicsScene>
#include <QShortcut>
#include <QTimer>

#include <triangle.h>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

private:
    Ui::Widget      *ui;
    QGraphicsScene  *scene;     /// Объявляем графическую сцену
    Triangle        *triangle;  /// и треугольник
    QTimer          *timer;     /* Объявляем игровой таймер, благодаря которому
                                 * будет производиться изменения положения объекта на сцене
                                 * При воздействии на него клавишами клавиатуры
                                 * */
};

#endif // WIDGET_H

widget.cpp

В данном файле производится инициализация графической сцены и её размеров, также в графической сцене отрисовываются границы поля, где будет перемещаться треугольник. Но ключевым моментом является инициализация таймера, по обработке сигнала от которого будет производится изменения состояния графической сцены, а также будут изменяться координаты расположения треугольника, при отслеживании состояния клавиш клавиатуры.

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->resize(600,600);          /// Задаем размеры виджета, то есть окна
    this->setFixedSize(600,600);    /// Фиксируем размеры виджета

    scene = new QGraphicsScene();   /// Инициализируем графическую сцену
    triangle = new Triangle();      /// Инициализируем треугольник

    ui->graphicsView->setScene(scene);  /// Устанавливаем графическую сцену в graphicsView
    ui->graphicsView->setRenderHint(QPainter::Antialiasing);    /// Устанавливаем сглаживание
    ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); /// Отключаем скроллбар по вертикали
    ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); /// Отключаем скроллбар по горизонтали

    scene->setSceneRect(-250,-250,500,500); /// Устанавливаем область графической сцены

    scene->addLine(-250,0,250,0,QPen(Qt::black));   /// Добавляем горизонтальную линию через центр
    scene->addLine(0,-250,0,250,QPen(Qt::black));   /// Добавляем вертикальную линию через центр

    /* Дополнительно нарисуем органичение территории в графической сцене */
    scene->addLine(-250,-250, 250,-250, QPen(Qt::black));
    scene->addLine(-250, 250, 250, 250, QPen(Qt::black));
    scene->addLine(-250,-250,-250, 250, QPen(Qt::black));
    scene->addLine( 250,-250, 250, 250, QPen(Qt::black));

    scene->addItem(triangle);   /// Добавляем на сцену треугольник
    triangle->setPos(0,0);      /// Устанавливаем треугольник в центр сцены

    /* Инициализируем таймер и вызываем слот обработки сигнала таймера
     * у Треугольника 20 раз в секунду.
     * Управляя скоростью отсчётов, соответственно управляем скоростью
     * изменения состояния графической сцены
     * */
    timer = new QTimer();
    connect(timer, &QTimer::timeout, triangle, &Triangle::slotGameTimer);
    timer->start(1000 / 50);
}

Widget::~Widget()
{
    delete ui;
}

triangle.h

А вот теперь приступаем к программному коду, который отвечает за графический объект, которым мы будем управлять. Класс наследуется от QObject для работы с сигналами и слотами , а также от QGraphicsItem.

Именно в этом файле подключается заголовочный файл windows.h для работы с функционалом

#ifndef TRIANGLE_H
#define TRIANGLE_H

#include <QObject>
#include <QGraphicsItem>
#include <QPainter>
#include <QGraphicsScene>

/* Подключаем библиотеку, отвечающую за использование WinAPI
 * Данная библиотека необходима для асинхронной проверки состояния клавиш
 * */
#include <windows.h>

class Triangle : public QObject, public QGraphicsItem
{
    Q_OBJECT
public:
    explicit Triangle(QObject *parent = 0);
    ~Triangle();

signals:

public slots:
    void slotGameTimer(); // Слот, который отвечает за обработку перемещения треугольника

protected:
    QRectF boundingRect() const;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);

private:
    qreal angle;    // Угол поворота графического объекта

};

#endif // TRIANGLE_H

triangle.cpp

Отрисовка треугольника взята из предыдущего урока по позиционированию графических элементов в графической сцене, а вот перерисовка в слоте, который будет обрабатывать сигнал от таймера, а также инициализация первичного поворота объекта уже является новым куском кода.

Поворот объекта задается в градусах переменной angle и устанавливается функцией setRotation() , которая была унаследована от QGraphicsItem . Также для отслеживания состояния кнопок клавиатуры используется функция из WinAPI, а именно GetAsyncKeyState(), которая по коду кнопки определяет состояние этой самой кнопки. При каждом сигнале от объекта класса QTimer происходит проверка нажатых клавиш и в зависимости от их состояния изменяется положение треугольника.

#include "triangle.h"

Triangle::Triangle(QObject *parent) :
    QObject(parent), QGraphicsItem()
{
    angle = 0;     // Задаём угол поворота графического объекта
    setRotation(angle);     // Устанавилваем угол поворота графического объекта
}

Triangle::~Triangle()
{

}

QRectF Triangle::boundingRect() const
{
    return QRectF(-25,-40,50,80);   /// Ограничиваем область, в которой лежит треугольник
}

void Triangle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
        QPolygon polygon;   /// Используем класс полигона, чтобы отрисовать треугольник
        /// Помещаем координаты точек в полигональную модель
        polygon << QPoint(0,-40) << QPoint(25,40) << QPoint(-25,40);
        painter->setBrush(Qt::red);     /// Устанавливаем кисть, которой будем отрисовывать объект
        painter->drawPolygon(polygon);  /// Рисуем треугольник по полигональной модели
        Q_UNUSED(option);
        Q_UNUSED(widget);
}

void Triangle::slotGameTimer()
{
    /* Поочерёдно выполняем проверку на нажатие клавиш
     * с помощью функции асинхронного получения состояния клавиш,
     * которая предоставляется WinAPI
     * */
    if(GetAsyncKeyState(VK_LEFT)){
        angle -= 10;        // Задаём поворот на 10 градусов влево
        setRotation(angle); // Поворачиваем объект
    }
    if(GetAsyncKeyState(VK_RIGHT)){
        angle += 10;        // Задаём поворот на 10 градусов вправо
        setRotation(angle); // Поворачиваем объект
    }
    if(GetAsyncKeyState(VK_UP)){
        setPos(mapToParent(0, -5));     /* Продвигаем объект на 5 пискселей вперёд
                                         * перетранслировав их в координатную систему
                                         * графической сцены
                                         * */
    }
    if(GetAsyncKeyState(VK_DOWN)){
        setPos(mapToParent(0, 5));      /* Продвигаем объект на 5 пискселей назад
                                         * перетранслировав их в координатную систему
                                         * графической сцены
                                         * */
    }

    /* Проверка выхода за границы поля
     * Если объект выходит за заданные границы, то возвращаем его назад
     * */
    if(this->x() - 10 < -250){
        this->setX(-240);       // слева
    }
    if(this->x() + 10 > 250){
        this->setX(240);        // справа
    }

    if(this->y() - 10 < -250){
        this->setY(-240);       // сверху
    }
    if(this->y() + 10 > 250){
        this->setY(240);        // снизу
    }
}

Примечание

Для того, чтобы проект скомпилировался с комплектом сборки MSVC, добавьте в pro файл проекта следующие строки:

win32-msvc*{
    LIBS += -luser32
}

Итог

В результате проделанной работы мы сделали первые шаги к тому, чтобы написать игру. А именно научились управлять объектом, то есть нашим героем-треугольником, с которым мы также поработаем в будущих уроках по написанию первой нашей игры.

В видеоуроке даны дополнительные комментарии и объяснения к проекту, а также продемонстрирована работа приложения.

Полный список статей данного цикла:

Видеоурок

Реклама

Комментарии

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
  • Vadym
  • 26 сентября 2017 г. 18:10

C++ - Тест 005. Структуры и Классы

  • Результат - 83 баллов
  • Vadym
  • 26 сентября 2017 г. 18:05

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

  • Результат - 80 баллов
  • Vadym
  • 26 сентября 2017 г. 4:44

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

  • Результат - 78 баллов
Последние комментарии
  • EVILEG
  • 22 сентября 2017 г. 12:45

Qt/C++ - Урок 055. QSignalMapper VS лямбда функции

Вы используете стандартную практику замыканий, когда нет никакой необходимости объявлять функции в классе, поскольку они используются в одном единственном месте класса, а объявление всех эти л...

  • Damir
  • 22 сентября 2017 г. 2:35

Qt/C++ - Урок 055. QSignalMapper VS лямбда функции

Может и кривовато но чёрт побери работает и класс от ненужной больше ни где фигни не разбухает.

  • Damir
  • 22 сентября 2017 г. 2:29

Qt/C++ - Урок 055. QSignalMapper VS лямбда функции

Как вам такое enum { PROFILE_TOOLPATH_FORM, POCKET_TOOLPATH_FORM, DRILLING_TOOLPATH_FORM }; QToolBar* toolpathToolBar = addToolBar(tr("Toolpa...

  • Mr_lKl
  • 17 сентября 2017 г. 16:14

QML - Урок 031. Отключаем системное обрамление окна в QML и пишем код для обработки перемещения и ресайза окна

Спасибо! Этим и займусь. Ещё попробую скинуть проект другу, посмотрю, как QT будет справляться там.

  • EVILEG
  • 17 сентября 2017 г. 14:14

QML - Урок 031. Отключаем системное обрамление окна в QML и пишем код для обработки перемещения и ресайза окна

Тогда это однознано баг, я бы глянул на официальном багтрекере Qt, есть ли информация об этом баге, и возможно стоит создать таск с этим багом.

Сейчас обсуждают на форуме
  • EVILEG
  • 27 сентября 2017 г. 1:54

Сборка проекта в Qt под Android.

В общем я вас не обрадую, я сегодня сам поразбирался с этой проблемой. И ... (барабанная дробь) ... Qt Creator 4.4 с багом. Это не работает в принципе. Фикс будет в Qt Creator 4.5. ...

  • EVILEG
  • 26 сентября 2017 г. 18:03

Как дождаться выполнения функции

Именно, а самому писать скачивание файла - это вам не нужно. Поэтому нужно правильно написать обработку процесса скачивания. Для этого и потребуется делать либо буфер. Либо обновлять GUI когда...

  • EVILEG
  • 26 сентября 2017 г. 16:58

Virtual Keyboard

Больше похоже на какой-то баг с клавиатурой. Перекопал разные варианты, а результат такой же. Кроме Британской раскладки ничего не работает.

  • verside
  • 20 сентября 2017 г. 12:39

Qt и Visual Studio (32-битная версия)

Делал ровно так, и описано. Но что-то не подхватывает Qt. Есть идеи, что Visual Studio какие-то переменные в окружение не прописал, но какие, пока не удалось понять. Я про...

Проблема при компиляции WebKit для Qt 5.7.1

Здравствуйте! Еще есть некоторые пользователи которые остались на Win XP.