Моё внимание обратили на то, что для работы с сигналами и слотами вместо класса наследованного от QGraphicsItem и от QObject можно использовать класс наследованный от QGraphicsObject . И действительно, если немного покопаться в исходниках QGraphicsObject , то обнаружится, что это класс наследованный от QGraphicsItem и от QObject . То есть также применяется множественное наследование, только в данном случае все велосипеды уже написаны до нас. Поэтому попробуем поработать с данным классом на примере игровой механики.
А именно, предлагаю написать программу, в которой Мы будет перемещать героя кликом мыши по графической сцене, как в любой РПГ наподобие Diablo.
Структура проекта для работы с QGraphicsObject
- QGraphicsObjectExample.pro - профайл проекта;
- main.cpp - основной файл исходных кодов;
- widget.h - заголовочный файл окна приложения;
- widget.cpp - файл исходных кодов окна приложения;
- customscene.h - заголовочный файл кастомизированной графической сцены ;
- customscene.cpp - файл исходных кодов кастомизированной графической сцены;
- triangle.h - заголовочный файл класса героя-треугольника, который будет передвигаться;
- triangle.cpp - файл исходных кодов класса героя-треугольника.
mainwindow.ui - QGraphicsObjectExample.pro - main.cpp
Форма главного окна ничем не примечательна. В ней находится только объект QGraphicsView. Файлы QGraphicsObjectExample и main.cpp создаются по умолчанию и не модифицируются.
widget.h
Для реализации задуманного нам понадобится только кастомизированная графическая сцена и больше ничего. Хотя её вполне можно было объявить и локально внутри конструктора класса Widget .
- #ifndef WIDGET_H
- #define WIDGET_H
- #include <QWidget>
- #include "customscene.h"
- #include "triangle.h"
- namespace Ui {
- class Widget;
- }
- class Widget : public QWidget
- {
- Q_OBJECT
- public:
- explicit Widget(QWidget *parent = 0);
- ~Widget();
- private:
- Ui::Widget *ui;
- CustomScene *scene;
- };
- #endif // WIDGET_H
widget.cpp
В конструкторе класса Widget производим инициализацию графической сцены и объекта треугольника. А также соединяем сигнал signalTargetCoordinate от графической сцены со слотом треугольника. signalTargetCoordinate будет передавать координаты мыши, к которым должен будет двигаться треугольник.
- #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);
- ui->graphicsView->setRenderHint(QPainter::Antialiasing); // Устанавливаем сглаживание
- ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Отключаем скроллбар по вертикали
- ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Отключаем скроллбар по горизонтали
- scene->setSceneRect(0,0,520,520); // Устанавливаем размеры графической сцены
- Triangle *triangle = new Triangle();
- triangle->setPos(260,260);
- scene->addItem(triangle);
- // Соединяем сигнал о положении курсора со слотом для передвижения героя
- connect(scene, &CustomScene::signalTargetCoordinate, triangle, &Triangle::slotTarget);
- }
- Widget::~Widget()
- {
- delete ui;
- }
customscene.h
Для передачи координат мыши на графической сцене будет использоваться сигнал, а передаваться он будет из событий mousePressEvent и mouseMoveEvent. При этом передача сигнала с данными будет производиться только в том случае, если была зажата левая кнопка мыши.
- #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);
- void mousePressEvent(QGraphicsSceneMouseEvent *event);
- };
- #endif // CUSTOMSCENE_H
customscene.cpp
- #include "customscene.h"
- #include <QApplication>
- CustomScene::CustomScene(QObject *parent) :
- QGraphicsScene(parent)
- {
- }
- CustomScene::~CustomScene()
- {
- }
- void CustomScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
- {
- if(QApplication::mouseButtons() == Qt::LeftButton){
- emit signalTargetCoordinate(event->scenePos()); // Передаём данный о местоположении клика
- }
- }
- void CustomScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
- {
- if(QApplication::mouseButtons() == Qt::LeftButton){
- emit signalTargetCoordinate(event->scenePos()); // Передаём данный о местоположении клика
- }
- }
triangle.h
Данный класс наследуем от класса QGraphicsObject и не применяем наследование от класса QObject, поскольку QGraphicsObject уже наследован от него, а также от QGrapihcsItem. Дальнейшая работа с данным объектом производится также, как если бы мы работали с классом, наследованным от QGrapgicsItem.
Небольшим моментом по сравнению с другими статьями о GameDev , например той, где мы просто отслеживаем перемещение мыши герой, является то, что здесь имеется переменная состояния стоять/идти.
Она необходима для того, чтобы героя остановить в том случае, когда он упрётся в стену или границу территории и точка его прибытия окажется недостижимой. А так здесь также используется игровой таймер с игровым слотом, где обрабатывается процесс движения главного героя.
А вот слот получения координат целевой точки обрабатывает поворот героя в сторону цели.
- #ifndef TRIANGLE_H
- #define TRIANGLE_H
- #include <QObject>
- #include <QTimer>
- #include <QGraphicsObject>
- #define GO true
- #define STOP false
- class Triangle : public QGraphicsObject
- {
- Q_OBJECT
- public:
- explicit Triangle();
- signals:
- private:
- QRectF boundingRect() const;
- void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
- public slots:
- void slotTarget(QPointF point); // Слот для установки координаты, куда нужно идти
- private:
- QTimer *gameTimer; // Игровой таймер
- QPointF target; // Положение курсора
- bool state; // Статус идти/стоять
- private slots:
- void slotGameTimer(); // Игровой слот
- };
- #endif // TRIANGLE_H
triangle.cpp
- #include "triangle.h"
- #include <math.h>
- #include <QPainter>
- 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()
- : QGraphicsObject()
- {
- setRotation(0);
- state = STOP;
- gameTimer = new QTimer(); // Инициализируем игровой таймер
- // Подключаем сигнал от таймера и слоту обработки игрового таймера
- connect(gameTimer, &QTimer::timeout, this, &Triangle::slotGameTimer);
- gameTimer->start(5); // Стартуем таймер
- }
- QRectF Triangle::boundingRect() const
- {
- return QRectF(-12,-15,24,30);
- }
- void Triangle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
- {
- // Отрисовка треугольника
- QPolygon polygon;
- polygon << QPoint(0,-15) << QPoint(12,15) << QPoint(-12,15);
- painter->setBrush(Qt::red);
- painter->drawPolygon(polygon);
- Q_UNUSED(option);
- Q_UNUSED(widget);
- }
- void Triangle::slotGameTimer()
- {
- if(state){
- QLineF lineToTarget(QPoint(0,0), mapFromScene(target));
- if(lineToTarget.length() > 2){
- setPos(mapToParent(0, -2));
- }
- /* Проверка выхода за границы поля
- * Если объект выходит за заданные границы, то возвращаем его назад
- * */
- if(this->x() - 30 < 0){
- this->setX(30); /// слева
- state = STOP; // Останавливаемся
- }
- if(this->x() + 30 > 520){
- this->setX(520 - 30); /// справа
- state = STOP; // Останавливаемся
- }
- if(this->y() - 30 < 0){
- this->setY(30); /// сверху
- state = STOP; // Останавливаемся
- }
- if(this->y() + 30 > 520){
- this->setY(520 - 30); /// снизу
- state = STOP; // Останавливаемся
- }
- }
- }
- 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);
- }
- state = GO; // Разрешаем идти
- }
Итог
В результате Вы получите приложение, в котором будете управлять перемещением главного героя с помощью одной лишь мышки, будто это РПГ, наподобие Diablo. Демонстрацию работы приложения Вы можете увидеть в видеоуроке.
Ссылка на скачивание проекта в zip-архиве: QGraphicsObjectExample
Добрый день! Пожалуйста подскажите, что может означать ошибка - ~\customscene.h:17: ошибка: 'void CustomScene::signalTargetCoordinate(QPointF)' is protected
Добрый день! По какой-то причине у вас данный сигнал оказался в protected секции, а не в секции сигналов, то есть скорее всего он объявлен в заголовочнике так
Большое спасибо. Файл был Ваш, но принудительное добавление public: перед void signal эту ошибку устранило. Но есть еще ошибка -
~\widget.cpp:27: ошибка: no matching function for call to 'Widget::connect(CustomScene*&, void (CustomScene::*)(QPointF), Triangle*&, void (Triangle::*)(QPointF))'
Если можно помогите пожалуйста - я в Qt вторую неделю всего.
Скорее всего в одном из классов не объявлена нужная функция (слот или сигнал). Нужно, чтобы сигнатуры сигнала и слота совпадали в данном конкретном примере. А в одном из классов видимо отсутствует нужный метод ( сигнал или слот - почитайте эту статью, чтобы разобраться с теорией сигналов и слотов).
Большое спасибо, удачи Вам, здоровья, и всего доброго.
Добрый день! Еще раз спасибо за уроки. Все заработало, помог ваш урок 24. Public был ни при чем, нужно было две строки проекта с connect записать в стиле Qt 4.8.5. Правда - стрелками на клавиатуре треугольник двигается с точностью наоборот. Но это мелочи. Всего доброго.
Добрый день!
Что? А почему? Вы используете Qt 4.8?
Добрый день! Это требование руководства, увы!
Вы случаем не в ОНИИПе работаете?
Добрый день! Точно так, в НИИ, только не О...П. Остальное, к сожалению, не моя информация. Всего доброго.