Qt/C++ - Lesson 033. Working with QGraphicsObject instead of QGraphicsItem

GameDev, QGraphicsItem, QGraphicsObject, QGraphicsScene, Qt

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

Video

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.
Support the author Donate
m

Добрый день! Пожалуйста подскажите, что может означать ошибка - ~\customscene.h:17: ошибка: 'void CustomScene::signalTargetCoordinate(QPointF)' is protected

Добрый день! По какой-то причине у вас данный сигнал оказался в protected секции, а не в секции сигналов, то есть скорее всего он объявлен в заголовочнике так

protected:
    void signalTargetCoordinate(QPointF point);
А не так
public:
    void signalTargetCoordinate(QPointF point);
А вы видимо пытаетесь вызвать его извне объекта. Или что-то типо того...
m

Большое спасибо. Файл был Ваш, но принудительное добавление public: перед void signal эту ошибку устранило. Но есть еще ошибка -

~\widget.cpp:27: ошибка: no matching function for call to 'Widget::connect(CustomScene*&, void (CustomScene::*)(QPointF), Triangle*&, void (Triangle::*)(QPointF))'

Если можно помогите пожалуйста - я в Qt вторую неделю всего.

Скорее всего в одном из классов не объявлена нужная функция (слот или сигнал). Нужно, чтобы сигнатуры сигнала и слота совпадали в данном конкретном примере. А в одном из классов видимо отсутствует нужный метод ( сигнал или слот - почитайте эту статью, чтобы разобраться с теорией сигналов и слотов).

m

Большое спасибо, удачи Вам, здоровья, и всего доброго.

m

Добрый день! Еще раз спасибо за уроки. Все заработало, помог ваш урок 24. Public был ни при чем, нужно было две строки проекта с connect записать в стиле Qt 4.8.5. Правда - стрелками на клавиатуре треугольник двигается с точностью наоборот. Но это мелочи. Всего доброго.

Добрый день!
Что? А почему? Вы используете Qt 4.8?

m

Добрый день! Это требование руководства, увы!

Вы случаем не в ОНИИПе работаете?

m

Добрый день! Точно так, в НИИ, только не О...П. Остальное, к сожалению, не моя информация. Всего доброго.

Comments

Only authorized users can post comments.
Please, Log in or Sign up
Donate

Hello, Dear Users of EVILEG!!!

If the site helped you, then support the development of the site financially, please.

You can do it by following ways:

Thank you, Evgenii Legotckoi

DK
April 1, 2020, 8:03 a.m.
Dmitry Kozhinov

C++ - Test 001. The first program and data types

  • Result:40points,
  • Rating points-8
A
March 30, 2020, 12:47 p.m.
Anna

C++ - Test 001. The first program and data types

  • Result:60points,
  • Rating points-1
A
March 29, 2020, 12:14 p.m.
Alexanderv66

C++ - Тест 003. Условия и циклы

  • Result:71points,
  • Rating points1
Last comments
April 3, 2020, 8:06 a.m.
Konstantin Grudnitskiy

Я надеюсь вы уже разобрались в чем дело, но если вдруг нет, то проблема состоит в том, что вы пытаетесь запустить программу из интерпретатора питона. Файл main.py это уже готова…
April 3, 2020, 6:18 a.m.
Konstantin Grudnitskiy

>>> text = 'hello world'>>> ' '.join(word for word in text.split()[:-1])'hello'>>> def remove_last_word(text):... return text and ' '.join(word for word in text.s…
March 27, 2020, 2:40 p.m.
Evgenij Legotskoj

Добрый день. В конце пятой статьи скачать можете.
March 27, 2020, 2:28 p.m.
mkdir _

Здравствуйте, а можно, пожалуйста, ссылку на целые исходники, если есть?
March 27, 2020, 4:36 a.m.
Evgenij Legotskoj

Скорее всего также, как и для установки всех остальных переменых в CMake, через использование set
Now discuss on the forum
April 5, 2020, 5:09 a.m.
IscanderChe

Попробуйте CQtDeployer или windeployqt.
April 5, 2020, 2:35 a.m.
Mihailll

Так работает console.log(textEmail.text) var str = textEmail.text; var n = str.search(/^((([0-9A-Za-z]{1}[-0-9A-z\.]{1,}[0-9A-Za-z]{1})|([0-9А-Яа-я]{1}[-0-9А-я\.]{1,}[…
April 3, 2020, 12:53 p.m.
BlinCT

Само собою на компе этого незаметно.
April 3, 2020, 8:48 a.m.
Intruder

Евгений, добрый день. Спасибо!
s
April 3, 2020, 7:52 a.m.
solmik

да вроде много чего установленно, если неправильный путь указать то же самое, пробовал запустить видео через плей лист (по примерам из док)и из него назад путь взять, не получилось
EVILEG
About
Services
© EVILEG 2015-2019
Recommend hosting TIMEWEB