Моё внимание обратили на то, что для работы с сигналами и слотами вместо класса наследованного от 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
Добрый день! Пожалуйста подскажите, что может означать ошибка - ~\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?
Добрый день! Это требование руководства, увы!
Вы случаем не в ОНИИПе работаете?
Добрый день! Точно так, в НИИ, только не О...П. Остальное, к сожалению, не моя информация. Всего доброго.