- 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