Evgenii Legotckoi
Evgenii Legotckoi06 грудня 2015 р. 09:47

Qt/C++ - Урок 033. Робота з QGraphicsObject замість QGraphicsItem

Моя увага звернула на те, що для роботи з сигналами та слотами замість класу успадкованого від 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 створюються за замовчуванням та не модифікуються.

віджет.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()); // Передаём данный о местоположении клика
    }
}

трикутник.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

Відеоурок

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Стабільний хостинг, на якому розміщується соціальна мережа EVILEG. Для проектів на Django радимо VDS хостинг.

Вам це подобається? Поділіться в соціальних мережах!

m
  • 06 березня 2018 р. 08:08

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

Evgenii Legotckoi
  • 06 березня 2018 р. 08:47

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

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

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

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

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

Evgenii Legotckoi
  • 06 березня 2018 р. 10:01

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

m
  • 06 березня 2018 р. 10:40

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

m
  • 07 березня 2018 р. 05:40

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

Evgenii Legotckoi
  • 07 березня 2018 р. 05:43

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

m
  • 08 березня 2018 р. 02:34

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

Evgenii Legotckoi
  • 08 березня 2018 р. 03:24

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

m
  • 09 березня 2018 р. 02:20

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

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
d
  • dsfs
  • 26 квітня 2024 р. 11:56

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80бали,
  • Рейтинг балів4
d
  • dsfs
  • 26 квітня 2024 р. 11:45

C++ - Тест 002. Константы

  • Результат:50бали,
  • Рейтинг балів-4
d
  • dsfs
  • 26 квітня 2024 р. 11:35

C++ - Тест 001. Первая программа и типы данных

  • Результат:73бали,
  • Рейтинг балів1
Останні коментарі
k
kmssr09 лютого 2024 р. 02:43
Qt Linux - Урок 001. Автозапуск програми Qt під Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко05 лютого 2024 р. 09:50
Qt WinAPI - Урок 007. Робота з ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 грудня 2023 р. 18:30
Boost - статичне зв&#39;язування в проекті CMake під Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 грудня 2023 р. 16:38
Boost - статичне зв&#39;язування в проекті CMake під Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 грудня 2023 р. 05:01
Qt/C++ - Урок 056. Підключення бібліотеки Boost в Qt для компіляторів MinGW і MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Тепер обговоріть на форумі
G
Gar22 квітня 2024 р. 12:46
Clipboard Как скопировать окно целиком в clipb?
DA
Dr Gangil Academics20 квітня 2024 р. 14:45
Unlock Your Aesthetic Potential: Explore MSC in Facial Aesthetics and Cosmetology in India Embark on a transformative journey with an msc in facial aesthetics and cosmetology in india . Delve into the intricate world of beauty and rejuvenation, guided by expert faculty and …
a
a_vlasov14 квітня 2024 р. 13:41
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
Павел Дорофеев
Павел Дорофеев14 квітня 2024 р. 09:35
QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь
f
fastrex04 квітня 2024 р. 11:47
Вернуть старое поведение QComboBox, не менять индекс при resetModel Добрый день! У нас много проектов в которых используется QComboBox, в версии 5.5.1, когда модель испускает сигнал resetModel, currentIndex не менялся. В версии 5.15 при resetModel происходит try…

Слідкуйте за нами в соціальних мережах