Ich wurde darauf aufmerksam gemacht, dass für die Arbeit mit Signalen und Slots anstelle einer von [QGraphicsItem und von QObject] geerbten Klasse (https://evileg .com/ ru / post / 81 /) können Sie eine Klasse verwenden, die von QGraphicsObject geerbt wurde. Wenn Sie ein wenig im Quellcode von QGraphicsObject wühlen, werden Sie feststellen, dass dies eine von QGraphicsItem und QObject geerbte Klasse ist. Das heißt, auch Mehrfachvererbung wird angewendet, nur in diesem Fall wurden alle Fahrräder bereits vor uns geschrieben. Versuchen wir daher, mit dieser Klasse am Beispiel der Spielmechanik zu arbeiten.
Ich schlage nämlich vor, ein Programm zu schreiben, in dem wir den Helden durch Klicken mit der Maus über die Grafikszene bewegen, wie in jedem RPG wie Diablo.
Projektstruktur für die Arbeit mit QGraphicsObject
- QGraphicsObjectExample.pro - Projektprofil;
- main.cpp - Hauptquellcodedatei;
- widget.h - Header-Datei des Anwendungsfensters;
- widget.cpp - Datei mit Quellcodes des Anwendungsfensters;
- customcene.h - Header-Datei der angepassten Grafikszene ;
- customcene.cpp - Datei mit den Quellcodes der angepassten Grafikszene;
- Triangle.h - Header-Datei der Triangle-Heldenklasse, die verschoben wird;
- Triangle.cpp - Quelldatei für die Triangel-Heldenklasse.
mainwindow.ui - QGraphicsObjectExample.pro - main.cpp
Die Form des Hauptfensters ist unauffällig. Es enthält nur ein QGraphicsView-Objekt. Die Dateien QGraphicsObjectExample und main.cpp werden standardmäßig erstellt und nicht geändert.
widget.h
Zur Umsetzung unseres Vorhabens benötigen wir lediglich eine angepasste Grafikszene und sonst nichts. Obwohl es durchaus lokal im Konstruktor der Klasse Widget hätte deklariert werden können.
#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
Im Konstruktor der Klasse Widget initialisieren wir die Grafikszene und das Dreiecksobjekt. Und verbinden Sie auch das signalTargetCoordinate Signal aus der Grafikszene mit dem Triangel-Slot. signalTargetCoordinate überträgt die Koordinaten der Maus, auf die sich das Dreieck bewegen soll.
#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; }
zollszene.h
Um die Koordinaten der Maus in der Grafikszene zu übertragen, wird ein Signal verwendet, und es wird von den Ereignissen mousePressEvent und mouseMoveEvent gesendet. In diesem Fall wird nur das Signal mit Daten übertragen wenn die linke Maustaste gedrückt gehalten wurde.
#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
zollszene.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()); // Передаём данный о местоположении клика } }
dreieck.h
Diese Klasse erbt von der Klasse QGraphicsObject und übernimmt keine Vererbung von der QObject-Klasse, da QGraphicsObject bereits von ihr geerbt wird, sowie von QGraphcsItem Weitere Arbeit mit dieses Objekt wird auch ausgeführt, als ob wir mit einer Klasse arbeiten würden, die von QGrapgicsItem geerbt wurde.
Ein kleiner Punkt im Vergleich zu anderen Artikeln auf GameDev , zum Beispiel, wo wir nur die Bewegung der Heldenmaus verfolgen, ist, dass es einen Zustand gibt variabel stehen / gehen ...
Es ist notwendig, um den Helden aufzuhalten, falls er auf eine Mauer oder die Grenze des Territoriums trifft und der Punkt seiner Ankunft unerreichbar ist. Und so kommt hier auch ein Spiel timer mit einem Spielslot zum Einsatz, in dem der Bewegungsablauf der Hauptfigur verarbeitet wird.
Aber der Schlitz zum Empfangen der Koordinaten des Zielpunkts übernimmt die Drehung des Helden zum Ziel.
#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
Dreieck.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; // Разрешаем идти }
Ergebnis
Als Ergebnis erhalten Sie eine Anwendung, in der Sie die Bewegung der Hauptfigur mit nur einer Maus steuern, als wäre es ein Rollenspiel wie Diablo. Eine Demonstration der Anwendung können Sie im Video-Tutorial sehen.
Link zum Download des Projekts in einem Zip-Archiv: 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?
Добрый день! Это требование руководства, увы!
Вы случаем не в ОНИИПе работаете?
Добрый день! Точно так, в НИИ, только не О...П. Остальное, к сожалению, не моя информация. Всего доброго.