- 1. Структура проекта
- 2. mainwindow.ui
- 3. віджет.h
- 4. widget.cpp
- 5. трикутник.h
- 6. triangle.cpp
- 7. Примітка
- 8. Підсумок
- 9. Відеоурок
З цього уроку починається серія статей у тому, як написати гру на 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 у віджет. Більше нічого не потрібне.
віджет.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;
- }
трикутник.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, додайте до файлу проекту наступні рядки:
- win32-msvc*{
- LIBS += -luser32
- }
Підсумок
В результаті виконаної роботи ми зробили перші кроки до того, щоб написати гру. А саме навчилися керувати об'єктом, тобто нашим героєм-трикутником, з яким ми також попрацюємо в майбутніх уроках написання першої нашої гри.
У відеоуроці подано додаткові коментарі та пояснення до проекту, а також продемонстровано роботу програми.
Повний список статей цього циклу:
- Урок 1. Як написати гру на Qt. Управління об'єктом
- Урок 2. Як написати гру на Qt. Анімація героя ігри (2D)
- Урок 3. Як написати гру на Qt. Взаємодія з іншими об'єктами
- Урок 4. Як написати гру на Qt. Ворог - сенс у виживанні
- Урок 5. Як написати гру на Qt. Додаємо звук із QMediaPlayer
Добрый день,
Подскажите пожалуйста, чем может быть вызвана ошибка
"C:\Android\Qt\Mark\untitled4\triangle.cpp:5: ошибка: undefined reference to `vtable for Triangle'" ?
В конструкторе указывает на наследуемый класс QGraphicsItem и деструктор Triangle.
Я сначала пытался код сам исправить, но ничего не вышло и я просто скопировал Ваш.
Скопировал triangle.h и triangle.cpp.
Что это может быть?
Отвратительная ошибка на самом деле.
Вы как всегда правы, удалить билд и перезапустить было решением проблемы
Добрый день.

Проблема следующая:
треугольник при перемещении не отрисовывается полностью, кроме того, на графической сцене остаются его следы, но если, например, свернуть приложение и заново его открыть, то треугольник отрисовывется как положено.
Добрый день. Переопределили boundingRect? и переопределили ли его правильно? выглядит так, что либо boundingRect неправильно расчитывается, либо не вызывается update сцены.
Спасибо, не поставил минус в boundingRect
Евгений здравствуйте!Только начинаю разбираться в QT и работаю в Unreal Engine 4(делаю игры и приложения).Можно ли их сравнивать или они для разных задач.И что не сделать в Unreal,что можно сделать в QT.Оба на с++,у обоих есть дизайнеры для интерфейса.В Unreal правда blueprints,а в QT Qml(я так понимаю это его плюс перед Unreal).На Qml можно писать красивый интерфейс,но в Unreal это делать проще?
Добрый день
Они для разных задач.
В Qt вам для игры придётся написать собственный движок, который будет отвечать за физику и т.д. А в Unreal Engine 4 придётся писать очень много подготовительной логики для моделей данных (таблицы и т.д.). Ну либо использовать Felgo - фреймворк для написания приложений, который написан поверх Qt, они там сделали большую работу для мобильных приложений. В качестве движка используют cocos2d для игр.
Делать вёрстку приложения на QML определённо проще, якоря очень хорошая вещь. Когда я пробовал делать простую вёрстку в UE4 для меню, то мне якорей очень сильно не хватало, которые есть в QML.
blueprints - это конёчно жёсткое псевдопрограммирование )))) работать с ними можно, но я прекрасно понимал, когда пробовал себя в UE4, что логику, которую я писал в blueprints 8-10 блоками , на С++ я мог бы написать в две строки кода, которые были бы предельно понятны. Поэтому сразу начал искать варианты переноса логики в C++.
blueprints хороши для того, что требует 3D представления, но биты и байты ими перебирать - это самовредительство.
В общем сравнивать можно по ощущениям, но точно не по назначению. UE4 - игры, а Qt - это всё остальное.
Евгений, спасибо за развёрнутый ответ!
"В дизайнере интерфейсов просто закидываем QGraphicsView в виджет. Больше ничего не требуется."
Для тупеньких, объясните пж (Qt Creator 4.10.2)
В Qt Creator двойным кликом открыть ui файл, и перетащить в виджет Graphics View. Потом выделить корневой виджет и нажать кнопку для создания GridLayoyt, чтобы Graphics View нормально позиционировался.
Спасибо, и пока пользуясь случаем. PushButton в основном для чего?
Это обычная кнопка, клик по этой кнопке можно привязать к какому угодно функционалу, от применения настроек до открытия диалога. Зависит от логики, которую в неё вкладывают.
Если у вас и дальше будут вопросы, то задавайте их на форуме, каждый в отдельной теме, чтобы они уже были логически структурированы и не было оффтопов.
Я просматриваю все вопросы, которые появляются на форуме и по возможности отвечаю на них. Форум
Может вы мне подскажете, ибо я не могу понять и найти как эти строчки изменить под linux:
Под Linux Не подскажу, там всё несколько сложнее
Привет, такой вопрос. Пишу игру, где отрисовываю абсолютно всё через QPainter. Спрайты анимации вывожу через DrawImage(). Какой из вариантов (QPainter и QGraphicsView) лучше в плане производительности? По поводу функционала понятно, но интересует именно скорость отрисовки, так как на средних смартфонах бывает падает FPS