Evgenii Legotckoi
Evgenii Legotckoi6 декабря 2015 г. 20: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 создаются по умолчанию и не модифицируются.

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

Видеоурок

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

Вам это нравится? Поделитесь в социальных сетях!

m
  • 6 марта 2018 г. 19:08

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

Evgenii Legotckoi
  • 6 марта 2018 г. 19:47

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

protected:
    void signalTargetCoordinate(QPointF point);
А не так
public:
    void signalTargetCoordinate(QPointF point);
А вы видимо пытаетесь вызвать его извне объекта. Или что-то типо того...
m
  • 6 марта 2018 г. 20: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
  • 6 марта 2018 г. 21:01

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

m
  • 6 марта 2018 г. 21:40

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

m
  • 7 марта 2018 г. 16:40

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

Evgenii Legotckoi
  • 7 марта 2018 г. 16:43

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

m
  • 8 марта 2018 г. 13:34

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

Evgenii Legotckoi
  • 8 марта 2018 г. 14:24

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

m
  • 9 марта 2018 г. 13:20

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

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
Ua

Qt - Тест 001. Сигналы и слоты

  • Результат:84баллов,
  • Очки рейтинга4
Ua

Qt - Тест 001. Сигналы и слоты

  • Результат:42баллов,
  • Очки рейтинга-8
ОК

Qt - Тест 001. Сигналы и слоты

  • Результат:47баллов,
  • Очки рейтинга-6
Последние комментарии
ИМ
Игорь Максимов22 ноября 2024 г. 21:51
Django - Урок 017. Кастомизированная страница авторизации на Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 октября 2024 г. 23:37
Django - Урок 064. Как написать расширение для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 октября 2024 г. 17:19
Читалка fb3-файлов на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов5 октября 2024 г. 16:51
Django - Урок 064. Как написать расширение для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55 июля 2024 г. 20:02
QML - Урок 016. База данных SQLite и работа с ней в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Сейчас обсуждают на форуме
AH
Abdul Hadi13 февраля 2025 г. 15:21
Are you Looking for best painter services in Qatar? Looking for top painter Services in Qatar? Get high-quality, affordable, and professional painting for homes & offices. Contact expert painters today!
d
dubaicushions13 февраля 2025 г. 15:17
Are Looking for custom swing cushions in Dubai for home decor? Looking for Custom Swing Cushions in Dubai? Get high-quality, weather-resistant, and stylish cushions for your outdoor swing. Order now for comfort & elegance!
d
dubaicustomizedsofa13 февраля 2025 г. 15:11
Are you Looking for a custom sofa in Dubai? Looking for a Custom Sofa in Dubai ? Get high-quality, stylish, and tailor-made sofas to match your space. Order now for comfort, luxury, and perfect design!
b
blinds1211 февраля 2025 г. 16:08
Why Bamboo Blinds Are the Perfect Choice for Your Home When it comes to enhancing the aesthetics and functionality of your living space, choosing the right window treatment is crucial. Bamboo blinds have emerged as a popular choice for homeowners wh…
i
imperial313011 февраля 2025 г. 15:40
How to Select the Right Carpet for Your Bedroom Aesthetic Choosing the perfect carpet for your bedroom involves more than just picking a color or pattern you like. Carpets can transform the ambiance of your space, adding warmth, comfort, and style. How…

Следите за нами в социальных сетях