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
AD

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

  • Результат:50бали,
  • Рейтинг балів-4
m
  • molni99
  • 26 жовтня 2024 р. 01:37

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

  • Результат:80бали,
  • Рейтинг балів4
m
  • molni99
  • 26 жовтня 2024 р. 01:29

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

  • Результат:20бали,
  • Рейтинг балів-10
Останні коментарі
ИМ
Игорь Максимов22 листопада 2024 р. 11:51
Django - Підручник 017. Налаштуйте сторінку входу до Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 жовтня 2024 р. 14:37
Django - Урок 064. Як написати розширення для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 жовтня 2024 р. 08:19
Читалка файлів fb3 на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов05 жовтня 2024 р. 07:51
Django - Урок 064. Як написати розширення для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas505 липня 2024 р. 11:02
QML - Урок 016. База даних SQLite та робота з нею в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Тепер обговоріть на форумі
Evgenii Legotckoi
Evgenii Legotckoi24 червня 2024 р. 15:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 листопада 2024 р. 06:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject04 червня 2022 р. 03:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9Anonim25 жовтня 2024 р. 09:10
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

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