My attention was drawn to the fact that for working with signals and slots instead of a class inherited from QGraphicsItem and from QObject , you can use a class inherited from QGraphicsObject . Indeed, if a little rummage in the source QGraphicsObject , we find that it is a class inherited from QGraphicsItem and from QObject . That is also used multiple inheritance, but in this case all the bikes have written to us. Therefore, we try to work with this class as an example of the game mechanics.
Specifically, I propose to write a program in which we will move the hero mouse click on the graphic scene, as in any RPG like Diablo.
Project structure for work with QGraphicsObject
- QGraphicsObjectExample.pro - the profile of the project;
- main.cpp - the main source file;
- widget.h - header file of the application window;
- widget.cpp - file source code of the application window;
- customscene.h - customized header graphic scene;
- customscene.cpp - file source customized graphic scene ;
- triangle.h - header file of hero class;
- triangle.cpp - source file triangle class.
mainwindow.ui - QGraphicsObjectExample.pro - main.cpp
The shape of the main window unremarkable. It is only the object QGraphicsView. QGraphicsObjectExample and main.cpp files are created by default and is not modified.
widget.h
To implement the conceived, we need only a customized graphical scene and nothing else. Although it was quite possible to declare and locally within the Widget class's constructor.
#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
The Widget class constructor initializes the graphic scenes and the triangle object. And also connect the signal from the graphics signalTargetCoordinate with triangle slot scene. signalTargetCoordinate will transmit the coordinates of the mouse, which will need to move the triangle.
#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
To transfer the coordinates of the mouse on the graphic scene to be used signal, and it will be passed from mousePressEvent and mouseMoveEvent . At the same time signaling data will be made only in the event that was pressed left mouse button.
#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
This class inherit from QGraphicsObject class and do not use inheritance from the QObject class, because QGraphicsObject have inherited from him, as well as QGrapihcsItem . Further work with this object is also produced, as if we were working with a class, inherited from QGrapgicsItem .
Small moment compared to other articles about GameDev , such as the one where we simply track the movement of the mouse hero, it is that there is a state variable standing / walking.
It is necessary to stop the hero in case he rested into the wall or border territory and its point of arrival would be unattainable. And here it is also used by the game timer to the game slot that handles the process of movement of the main character.
But the slot receiving the coordinates of the target point handles turn the hero in the direction of the target.
#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)); } /* Check the output of the field boundary * If the subject is beyond the set boundaries, then return it back * */ if(this->x() - 30 < 0){ this->setX(30); /// left state = STOP; } if(this->x() + 30 > 520){ this->setX(520 - 30); /// riht state = STOP; } if(this->y() - 30 < 0){ this->setY(30); /// top state = STOP; } if(this->y() + 30 > 520){ this->setY(520 - 30); /// bottom state = STOP; } } } void Triangle::slotTarget(QPointF point) { // Determine the distance to the target target = point; QLineF lineToTarget(QPointF(0, 0), mapFromScene(target)); // The angle of rotation in the direction to the target qreal angleToTarget = ::acos(lineToTarget.dx() / lineToTarget.length()); if (lineToTarget.dy() < 0) angleToTarget = TwoPi - angleToTarget; angleToTarget = normalizeAngle((Pi - angleToTarget) + Pi / 2); // Turn the hero to the goal 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; }
Result
As a result, you will receive an application, which will control the movement of the hero with the help of only one mouse, if it's an RPG, similar to Diablo.
Link to the project download in zip-archive: 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?
Добрый день! Это требование руководства, увы!
Вы случаем не в ОНИИПе работаете?
Добрый день! Точно так, в НИИ, только не О...П. Остальное, к сожалению, не моя информация. Всего доброго.