- 1. Структура проекта
- 2. Пишем отслеживание курсора мыши
- 3. widget.ui
- 4. customscene.h
- 5. customscene.cpp
- 6. triangle.h
- 7. triangle.cpp
- 8. widget.h
- 9. widget.cpp
- 10. Итог
- 11. Видеоурок
Все помнят игру Crimsonland ? Там нужно было валить монстров штабелями. Для перемещения по игровому полю мы использовали клавиши W, A, S, D, а для направления стрельбы курсор мыши , за которым производилось отслеживание. Так вот, сам по себе механизм работы этого самого отслеживания мыши предельно прост. Реализаций может быть множество, в зависимости от используемых инструментов в программировании, но если говорить об обычном QGraphicsScene , то позвольте продемонстрировать Вам мой вариант реализации подобного механизма.
Структура проекта
В этом проекте помимо основных файлов используется два дополнительных класса. Первый - это кастомизированный QGraphicsScene , который будет производить отслеживание положения курсора и передавать информацию о его положении, а второй - это главный герой, наш любимый Красный Треугольник , которым мы будем управлять с помощью клавиш W, A, S, D.
Структура проекта:
- TargetMotion.pro - Профайл проекта;
- widget.h - Заголовочный файл основного окна приложения;
- widget.cpp - Файл исходных кодов основного окна приложения;
- triangle.h - Заголовочный файл главного героя Красного Треугольника;
- triangle.cpp - Файл исходных кодов главного героя Красного Треугольника;
- customscene.h - Заголовочный файл кастомизированной графической сцены;
- customscene.cpp - Файл исходных кодов кастомизированной графической сцены;
- cursor.qrc - файл ресурсов, в котором содержится кастомизированный курсор мыши.
Пишем отслеживание курсора мыши
widget.ui
Файл формы главного окна приложения. В него лишь закидываем объект класса QGraphicsView и растягиваем его по всему окну.
customscene.h
Всё, что нужно сделать в данном заголовочном файле, это лишь объявить метод для отслеживания перемещения мыши mouseMoveEvent() и сигнал, в котором будут передаваться координаты положения мыши, не забывая отнаследовать класс от QGraphicsScene.
- #ifndef CUSTOMSCENE_H
- #define CUSTOMSCENE_H
- #include <QObject>
- #include <QGraphicsScene>
- #include <QGraphicsSceneMouseEvent>
- class CustomScene : public QGraphicsScene
- {
- Q_OBJECT
- public:
- explicit CustomScene(QObject *parent = 0);
- ~CustomScene();
- signals:
- // Сигнал для передачи координат положения курсора мыши
- void signalTargetCoordinate(QPointF point);
- public slots:
- private:
- // Функция, в которой производится отслеживание положения мыши
- void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
- };
- #endif // CUSTOMSCENE_H
customscene.cpp
Вы не поверите, но в файле исходных кодов Мы всего лишь вызываем сигнал с координатами в методе mouseMoveEvent.
- #include "customscene.h"
- CustomScene::CustomScene(QObject *parent) :
- QGraphicsScene()
- {
- Q_UNUSED(parent);
- }
- CustomScene::~CustomScene()
- {
- }
- void CustomScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
- {
- emit signalTargetCoordinate(event->scenePos());
- }
triangle.h
А теперь объявим все объекты и функции, необходимые для правильной отрисовки нашего героя и реализации отслеживания курсора в игре. Для более полного представления о работе данного класса и его истоков рекомендую ознакомиться с циклом статей "Как написать игру на Qt" , который представлен в разделе Qt Уроки .
Примечание . Для обработки нажатий кнопок используются библиотеки WinAPI .
- #ifndef TRIANGLE_H
- #define TRIANGLE_H
- #include <QObject>
- #include <QGraphicsItem>
- #include <QPainter>
- #include <QPolygon>
- #include <QTimer>
- #include <windows.h>
- class Triangle : public QObject, public QGraphicsItem
- {
- Q_OBJECT
- public:
- explicit Triangle(QObject *parent = 0);
- ~Triangle();
- signals:
- public slots:
- // Слот для получения данных о положении курсора
- void slotTarget(QPointF point);
- private:
- QRectF boundingRect() const;
- void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
- private slots:
- void slotGameTimer(); // Игровой слот
- private:
- QTimer *gameTimer; // Игровой таймер
- QPointF target; // Положение курсора
- };
- #endif // TRIANGLE_H
triangle.cpp
Отрисовка треугольника происходит один раз в данном случае, но разворот этого объекта и его перемещение по графической сцене происходит периодически по сигналу от игрового таймера и по сигналам от графической сцены о перемещении курсора. Если курсор не перемещается, а перемещается лишь треугольник , то треугольник всё равно следует за курсором мыши, поскольку треугольник хранит информацию о последнем положении курсора на графической сцене.
- #include "triangle.h"
- #include <math.h>
- static const double Pi = 3.14159265358979323846264338327950288419717;
- static double TwoPi = 2.0 * Pi;
- static qreal normalizeAngle(qreal angle)
- {
- while (angle < 0)
- angle += TwoPi;
- while (angle > TwoPi)
- angle -= TwoPi;
- return angle;
- }
- Triangle::Triangle(QObject *parent) :
- QObject(parent), QGraphicsItem()
- {
- setRotation(0); // Устанавливаем исходный разворот треугольника
- gameTimer = new QTimer(); // Инициализируем игровой таймер
- // Подключаем сигнал от таймера и слоту обработки игрового таймера
- connect(gameTimer, &QTimer::timeout, this, &Triangle::slotGameTimer);
- gameTimer->start(10); // Стартуем таймер
- target = QPointF(0,0); // Устанавливаем изначальное положение курсора
- }
- Triangle::~Triangle()
- {
- }
- QRectF Triangle::boundingRect() const
- {
- return QRectF(-20,-30,40,60);
- }
- void Triangle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
- {
- /* Отрисовка треугольника
- * */
- QPolygon polygon;
- polygon << QPoint(0,-30) << QPoint(20,30) << QPoint(-20,30);
- painter->setBrush(Qt::red);
- painter->drawPolygon(polygon);
- Q_UNUSED(option);
- Q_UNUSED(widget);
- }
- void Triangle::slotTarget(QPointF point)
- {
- // Определяем расстояние до цели
- target = point;
- QLineF lineToTarget(QPointF(0, 0), mapFromScene(target));
- // Угол поворота в направлении к цели
- qreal angleToTarget = ::acos(lineToTarget.dx() / lineToTarget.length());
- if (lineToTarget.dy() < 0)
- angleToTarget = TwoPi - angleToTarget;
- angleToTarget = normalizeAngle((Pi - angleToTarget) + Pi / 2);
- /* В Зависимости от того, слева или справа находится Цель от Героя,
- * устанавливаем направление поворота Героя в данном тике таймера
- * */
- if (angleToTarget >= 0 && angleToTarget < Pi) {
- // Rotate left
- setRotation(rotation() - angleToTarget * 180 /Pi);
- } else if (angleToTarget <= TwoPi && angleToTarget > Pi) {
- // Rotate right
- setRotation(rotation() + (angleToTarget - TwoPi )* (-180) /Pi);
- }
- }
- void Triangle::slotGameTimer()
- {
- /* Перемещаем треугольник в зависимости от нажатых кнопок
- * */
- if(GetAsyncKeyState('A')){
- this->setX(this->x() - 2);
- }
- if(GetAsyncKeyState('D')){
- this->setX(this->x() + 2);
- }
- if(GetAsyncKeyState('W')){
- this->setY(this->y() - 2);
- }
- if(GetAsyncKeyState('S')){
- this->setY(this->y() + 2);
- }
- /* Проверка выхода за границы поля
- * Если объект выходит за заданные границы, то возвращаем его назад
- * */
- if(this->x() - 30 < 0){
- this->setX(30); // слева
- }
- if(this->x() + 30 > 500){
- this->setX(500 - 30); // справа
- }
- if(this->y() - 30 < 0){
- this->setY(30); // сверху
- }
- if(this->y() + 30 > 500){
- this->setY(500 - 30); // снизу
- }
- QLineF lineToTarget(QPointF(0, 0), mapFromScene(target));
- // Угол поворота в направлении к цели
- qreal angleToTarget = ::acos(lineToTarget.dx() / lineToTarget.length());
- if (lineToTarget.dy() < 0)
- angleToTarget = TwoPi - angleToTarget;
- angleToTarget = normalizeAngle((Pi - angleToTarget) + Pi / 2);
- /* В Зависимости от того, слева или справа находится Цель от Героя,
- * устанавливаем направление поворота Героя в данном тике таймера
- * */
- if (angleToTarget >= 0 && angleToTarget < Pi) {
- // Rotate left
- setRotation(rotation() - angleToTarget * 180 /Pi);
- } else if (angleToTarget <= TwoPi && angleToTarget > Pi) {
- // Rotate right
- setRotation(rotation() + (angleToTarget - TwoPi )* (-180) /Pi);
- }
- }
widget.h
А теперь необходимо всё свести в игровом ядре, которое будет находится в классе главного окна приложения.
- #ifndef WIDGET_H
- #define WIDGET_H
- #include <QWidget>
- #include <QGraphicsScene>
- #include <QGraphicsItem>
- #include <triangle.h>
- #include <customscene.h>
- namespace Ui {
- class Widget;
- }
- class Widget : public QWidget
- {
- Q_OBJECT
- public:
- explicit Widget(QWidget *parent = 0);
- ~Widget();
- private:
- Ui::Widget *ui;
- CustomScene *scene; // Объявляем графическую сцену
- Triangle *triangle; // Объявляем треугольник
- };
- #endif // WIDGET_H
widget.cpp
В данном файле объявляются и инициализируются все игровые объекты (графическая сцена и треугольник), а также используется паттерн проектирования типа Мост, через который передаётся сигнал от графической сцены к треугольнику.
- #include "widget.h"
- #include "ui_widget.h"
- Widget::Widget(QWidget *parent) :
- QWidget(parent),
- ui(new Ui::Widget)
- {
- // Устанавливаем параметры окна приложения
- this->resize(600,600);
- this->setFixedSize(600,600);
- ui->setupUi(this);
- scene = new CustomScene(); // Инициализируем кастомизированную сцену
- ui->graphicsView->setScene(scene); /// Устанавливаем графическую сцену в graphicsView
- ui->graphicsView->setRenderHint(QPainter::Antialiasing); /// Устанавливаем сглаживание
- ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); /// Отключаем скроллбар по вертикали
- ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); /// Отключаем скроллбар по горизонтали
- scene->setSceneRect(0,0,500,500); // Устанавливаем размеры графической сцены
- // Создаем кастомизированный курсор из ресурсного файла
- QCursor cursor = QCursor(QPixmap(":/cursor/cursorTarget.png"));
- ui->graphicsView->setCursor(cursor); // Устанавливаем курсор в QGraphicsView
- triangle = new Triangle(); // Инициализируем треугольник
- triangle->setPos(250,250); // Устанавливаем стартовую позицию треугольника
- scene->addItem(triangle); // Добавляем треугольник на графическую сцену
- /* Разрешаем отслеживание положение курсора мыши
- * без необходимости нажатия на кнопки мыши
- * Применяем это свойство именно для QGraphicsView,
- * в котором установлена графическая сцена
- * */
- ui->graphicsView->setMouseTracking(true);
- // Подключаем сигнал от графической сцены к слоту треугольника
- connect(scene, &CustomScene::signalTargetCoordinate, triangle, &Triangle::slotTarget);
- }
- Widget::~Widget()
- {
- delete ui;
- }
Итог
В результате вы получите треугольник на графической сцене, который будет перемещаться по ней под воздействием клавиш W, A, S, D и всегда смотреть в сторону курсора мыши, если тот находится в пределах графической сцены.
Демонстрация данного примера приведена в видеоуроке по данной статье.
А где можно найти полный код проекта (проектов)? У меня ошибки линковки типа ( LNK2019: ссылка на неразрешенный внешний символ "public: void __cdecl Triangle::slotTarget(class QPointF)" (?slotTarget@Triangle@@QEAAXVQPointF@@@Z) в функции "public: __cdecl Widget::Widget(class QWidget *)" ). И может кто подскажет как поправить?
Вот в этой статье в конце есть полный проект.
спасибо
а если например я хочу сделать вместо треугольников - текстурки, я могу это сделать в этом виджете Qt? или надо брать что то другое?
Для отрисовки используется объект класса QPainter методе paint у треугольника. А этот класс QPainter имеет метод drawPixmap(), который может отрисовывать изображение из png файла например. Поэтому да, можете лишь переписать метод paint, и отрисовывать текстуры.
еще такой вопрос. у меня Qt ругается на GetAsyncKeyState (ошибка линковки). Надо либу какую то подключить? 2) а есть такая функция н кросс платформенная (зачем в таком достаточно высокоуровневом фреймворке использовать winApi?)?
Для MSVC пропишите в pro файле проекта следующие строчки
цепочку событий Qt приложения. Что влёчет к значительному усложнению кода, да ещё и может помешать фокусировка на некоторых виджетах, то есть события фокуса ещё вмешиваются. Здесь фокус должен сохраняться на виджете, в котором происходит действие, чтобы корректно отрабатывать нажатия клавиш. В общем много головной боли сразу появляется. Обработка события клавиш в Qt немного неподходит для реализации игровой логики на клавиатуре, это как раз один из тех моментов, когда действительно проще навелосипедить своё... хотя я противник велосипедов, но Qt не даёт нужного функционала в достаточной мере.
я собираю под win64. LIBS += -luser32 и LIBS += -luser64 не помогает
Вы вообще что-то своё уже пишите, или как? Я скачал проект, запустил его на работе на Win64 и он работает.
Да нет. Чет у меня с qt только проблемы. Ваш проект так в qt не смог собрать (из за GetAsyncKeyState). Собрал его в visual studio, но там почему то пришлось в ручную кидать .dll в каталог (хотя путь добавлял) (может тут что изменить см скрин https://yadi.sk/i/-D9CsBhh3SL2eQ ).
Еще пару вопросов по code-style
Честно говоря... не знаю... Я не работаю с Visual Studio вообще. В основном Qt Creator, да иногда с CLion. Больше походит на то, что у вас некорректно установлены пути к библиотекам Qt в операционной системе. Либо сама Visual Studio криво встала.
По code-style
Спасибо за ответы. Еще хотел спросить. Вы занимались программированием сетевых игр? вот например если у нас 2 клиента (у каждого свой треугольник). Как например синхронизируется картинка, сколько раз в секунду передаются действия? просто не оч понимаю как удается избегать задержек и делать плавную картинку
А и по библиотеке. Зачем у qt своя библиотека вещей которые есть в в stl, boost и тд. Например зачем использовать всякие вектора Qt и тд когда они есть в stl? Или это зачем то нужно?
К моему великому сожалению я имею к геймдеву не очень большое отношение, особенно не занимался сетевыми играми, но почитываю информацию об этом в свободное время.
Ммм... здесь за версту отдаёт желанием написать всё своё. Кстати, голос разума у них всё-таки проснулся в Qt 5. Например, для сортировки там используется и рекомендуется уже std::sort , вместо qSort, который они пометили как deprecated. Что касается контейнеров, то у контейнеров Qt есть кое-какие удобные методы. Но последнее время я перехожу на контейнеры stl. Они гораздо функциональнее. Что касается boost, то сигналы и слоты, которые есть в boost, как говорят в интернетах, слизали именно с Qt. Так что все понемножку друг у друга заимствуют.
я переопределил paint треугольника. сделал рисование танчика и его пушки.
разобрался. заменил белый фон на альфаканал. теперь другой вопрос. вот Pixmap пушки я хочу поворачивать вокруг своей оси. можно это сделать както через setRotation например? (тоесть нарисовал корпус, повернул обьект, нарисовал пушку)
Фух и с этим разобрался. Теперь такой вопрос, а можно ли как-то по лучше обрабатывать столкновения обьектов, и чтоб к стене например можно было подъехать в плотную, и при этом не вьехать в ней повернувшись на месте?
Здесь уже нужно обрабатывать очертания танка и не давать возможность ему разворачиваться, если есть риск въехать в стену.
А не подскажите как сделать плавность анимации или как то так. например танк (прямоугольник) едет вверх и вертикален. Потом игрок нажимает клавишу влево и он должен уже ехать вертикально. Можно конечно плавно поворачивать текстуру но я думаю можно сделать лучше. Просто играл в танчики где корпус и пушка могут вращатся и ехать мгновенно на любой градус, и при этом там не было видимого резкого скачка (может это какоето сглаживание?)
Если просто сменить угол поворота или направление, то скачок всегда будет резким. Если хотите добавить некоторую плавность в поворот, то нужно делать поворот за несколько отсчётов игрового таймера, чтобы это было достаточно быстро, но при этом добавило некоторую плавность.
forgetting otnasledovat class from QGraphicsScene :D
Ну да, поленился внимательно посмотреть перевод после Google Translate :D
Добрый день!

Я хотел объединить 2 ваших уроков: "GameDev на Qt" и "Как написать игру на Qt", а именно:
1) Передвижение персонажа и его оружия;
2) Вращение оружия с помощью мыши.
Но чтобы я не делал, у меня уже на постоянной основе 10 внутренних, непонятных мне, ошибок.
Если есть возможность, не могли бы вы мне подсказать, что я делаю не так и как это можно это исправить.
Для удобства скидываю всю программу и скриншот ошибок.
Добрый день! На сам проект у меня пока нет времени посмотреть, но судя по ошибке, вам нужно добавить макрос Q_OBJECT в triangle и в widget.
Обычно выглядит так
В том то и дело, я добавлял, удалял, миксовал, но всё равно эти ошибки появляются раз за разом.

Если во всех файлах оставить "Q_OBJECT", то появляются данные ошибки:
Здравствуйте! Прохожу 1ю часть GameDev на Qt. Сделал код, однако выдает ошибки. Не подскажете что с этим делать?

Вполне возможно, что ты не закинул graphicsView в дизайнере в виджет
Кому интересно, поворот в slotTarget можно в одну строку организовать
Вполне возможно, что ты не закинул graphicsView в дизайнере в виджет