- 1. Структура проекта с классом Bullet
- 2. customscene.h
- 3. customscene.cpp
- 4. widget.h
- 5. widget.cpp
- 6. triangle.h
- 7. triangle.cpp
- 8. bullet.h
- 9. bullet.cpp
- 10. Итог
- 11. Видеоурок
После того, как Мы начали управлять своим героем, и его взгляд всегда обращён в сторону цели, настало время написать класс Bullet , который будет отвечать за пули и их полёт по игровой сцене. Механика перемещения пули по графической сцене будет аналогична механике перемещения главного героя. Различие будет в том, что пуля всегда движется по прямой и разворот пули нужно будет установить только в момент создания объекта класса Bullet, чтобы задать направления полёта пули.
Структура проекта с классом Bullet
Структура проекта из предыдущего урока изменяется в том плане, что добавляется новый класс Bullet. Также потребуется доработать все остальные классы для обеспечения взаимодействия с новым классом.
Поэтому начнём по порядку, а именно с класса где инициализируется событие вызывающее процесс стрельбы.
customscene.h
В кастомизированной графической сцене необходимо будет добавить функции для обработки нажатий клавиш мыши ( mousePressEvent и mouseReleaseEvent ), а также сигнал для передачи разрешения на стрельбу от графической сцены, к главному герою. Конечно, проверять нажатия клавиш мыши можно было бы и в классе главного героя, но проблема состоит в том, что нужно разрешить стрельбу только в области графической сцены, поскольку в неё её могут быть другие интерактивные элементы, которые не должны вызывать события стрельбы.
#ifndef CUSTOMSCENE_H #define CUSTOMSCENE_H #include <QObject> #include <QGraphicsScene> #include <QGraphicsSceneMouseEvent> #include <QDebug> class CustomScene : public QGraphicsScene { Q_OBJECT public: explicit CustomScene(QObject *parent = 0); ~CustomScene(); signals: // Сигнал для передачи координат положения курсора мыши void signalTargetCoordinate(QPointF point); void signalShot(bool shot); // Сигнал на стрельбу public slots: private: // Функция, в которой производится отслеживание положения мыши void mouseMoveEvent(QGraphicsSceneMouseEvent *event); void mousePressEvent(QGraphicsSceneMouseEvent *event); void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); }; #endif // CUSTOMSCENE_H
customscene.cpp
#include "customscene.h" CustomScene::CustomScene(QObject *parent) : QGraphicsScene() { Q_UNUSED(parent); } CustomScene::~CustomScene() { } void CustomScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { emit signalTargetCoordinate(event->scenePos()); } void CustomScene::mousePressEvent(QGraphicsSceneMouseEvent *event) { emit signalShot(true); // Когда клавиша мыши нажата, то можно стрелять Q_UNUSED(event); } void CustomScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { emit signalShot(false); // Когда клавишу мыши отпустили, то стрелять нельзя Q_UNUSED(event); }
widget.h
Для передачи данных о разрешении стрельбы подключаем signalShot из графической сцены к slotShot в треугольнике. В нём будет передаваться информация о том, нажата ли клавиша мыши в области графической сцены. Также из треугольника передаётся signalBullet, который инициирует создание пули, в ядро игры, в класс Widget. В слоте slotBullet создается объект класса Bullet и устанавливается на графическую сцену.
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QGraphicsScene> #include <QGraphicsItem> #include <triangle.h> #include <customscene.h> #include <bullet.h> namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = 0); ~Widget(); private: Ui::Widget *ui; CustomScene *scene; // Объявляем графическую сцену Triangle *triangle; // Объявляем треугольник private slots: void slotBullet(QPointF start, QPointF end); }; #endif // WIDGET_H
widget.cpp
#include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { /* Программный код из урока * GameDev. Отслеживание перемещения мыши в QGraphicsScene */ // Соединяем сигнала стрельбы с графической сцены со слотом разрешения стрельбы треугольника connect(scene, &CustomScene::signalShot, triangle, &Triangle::slotShot); // Соединяем сигнал на создание пули со слотом, создающим пули в игре connect(triangle, &Triangle::signalBullet, this, &Widget::slotBullet); } Widget::~Widget() { delete ui; } void Widget::slotBullet(QPointF start, QPointF end) { // Добавляем на сцену пулю scene->addItem(new Bullet(start, end)); }
triangle.h
В классе главного героя необходимо создать новый таймер, который будет инициировать создание новой пули 6 раз в секунду, если поступил сигнал из графической сцены на разрешение стрельбы. Как только поступил сигнал на прекращение стрельбы, то создание пуль игнорируется.
#ifndef TRIANGLE_H #define TRIANGLE_H #include <QObject> #include <QGraphicsItem> #include <QPainter> #include <QPolygon> #include <QTimer> #include <QDebug> #include <QCursor> #include <windows.h> class Triangle : public QObject, public QGraphicsItem { Q_OBJECT public: explicit Triangle(QObject *parent = 0); ~Triangle(); signals: // Сигнал для создания пули с параметрами траектории void signalBullet(QPointF start, QPointF end); public slots: // Слот для получения данных о положении курсора void slotTarget(QPointF point); // слот для обработки разрешения стрельбы void slotShot(bool shot); private: QRectF boundingRect() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); private slots: void slotGameTimer(); // Игровой слот void slotBulletTimer(); // Слот проверки пули private: bool shot; // Переменная состояния стрельбы QTimer *bulletTimer; // Таймер пули QTimer *gameTimer; // Игровой таймер QPointF target; // Положение курсора }; #endif // TRIANGLE_H
triangle.cpp
#include "triangle.h" #include <math.h> 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(QObject *parent) : QObject(parent), QGraphicsItem() { setRotation(0); // Устанавливаем исходный разворот треугольника target = QPointF(0,0); // Устанавливаем изначальное положение курсора shot = false; gameTimer = new QTimer(); // Инициализируем игровой таймер // Подключаем сигнал от таймера и слоту обработки игрового таймера connect(gameTimer, &QTimer::timeout, this, &Triangle::slotGameTimer); gameTimer->start(10); // Стартуем таймер bulletTimer = new QTimer(); // Инициализируем таймер создания пуль connect(bulletTimer, &QTimer::timeout, this, &Triangle::slotBulletTimer); bulletTimer->start(1000/6); // Стреляем 6 раз в секунду } Triangle::~Triangle() { } QRectF Triangle::boundingRect() const { return QRectF(-20,-30,40,60); } void Triangle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { /** Отрисовка треугольника * */ QPolygon polygon; polygon << QPoint(0,-30) << QPoint(20,30) << QPoint(-20,30); painter->setBrush(Qt::red); painter->drawPolygon(polygon); Q_UNUSED(option); Q_UNUSED(widget); } void Triangle::slotTarget(QPointF point) { /* Программный код из урока * GameDev. Отслеживание перемещения мыши в QGraphicsScene */ } void Triangle::slotGameTimer() { /* Программный код из урока * GameDev. Отслеживание перемещения мыши в QGraphicsScene */ } void Triangle::slotBulletTimer() { // Если стрельба разрешена, то вызываем сигнал на создание пули if(shot) emit signalBullet(QPointF(this->x(),this->y()), target); } void Triangle::slotShot(bool shot) { this->shot = shot; // Получаем разрешение или запрет на стрельбу }
bullet.h
А теперь приступим к созданию самой пули с помощью класса Bullet . Данный класс наследуется от QGraphicsItem и работа с ним производится аналогично работе с главным героем. При выходе пули за пределы игровой области, она должна быть уничтожена для освобождения памяти программы.
#ifndef BULLET_H #define BULLET_H #include <QObject> #include <QGraphicsItem> #include <QTimer> #include <QPainter> class Bullet : public QObject, public QGraphicsItem { Q_OBJECT public: explicit Bullet(QPointF start, QPointF end, QObject *parent = 0); ~Bullet(); signals: public slots: private: QRectF boundingRect() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); private: QTimer *timerBullet; // Слот для обработки таймера пули private slots: void slotTimerBullet(); // Слот для обработки полёта пули }; #endif // BULLET_H
bullet.cpp
В конструктор пули предаётся два объекта QPointF, которые задают траекторию полёта пули. Первая координата - это место вылета пули, место, где было инициировано её создание, и вторая координата - это местонахождение курсора в момент выстрела. По этим двум координатам задаётся траектория её полета.
Игровой таймер пули по тикам инициирует запуск игрового слота, в котором пуля продвигается на 10 пикселей вперёд и в случае выхода за пределы игровой области уничтожается.
#include "bullet.h" #include <math.h> 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; } Bullet::Bullet(QPointF start, QPointF end, QObject *parent) : QObject(parent), QGraphicsItem() { this->setRotation(0); // Устанавливаем начальный разворот this->setPos(start); // Устанавливаем стартовую позицию // Определяем траекторию полёта пули QLineF lineToTarget(start, end); // Угол поворота в направлении к цели 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); } // Инициализируем полётный таймер timerBullet = new QTimer(); // И подключаем его к слоту для обработки полёта пули connect(timerBullet, &QTimer::timeout, this, &Bullet::slotTimerBullet); timerBullet->start(7); } Bullet::~Bullet() { } QRectF Bullet::boundingRect() const { return QRectF(0,0,2,4); } void Bullet::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { painter->setPen(Qt::black); painter->setBrush(Qt::black); painter->drawRect(0,0,2,4); Q_UNUSED(option); Q_UNUSED(widget); } void Bullet::slotTimerBullet() { setPos(mapToParent(0, -10)); /* Проверка выхода за границы поля * Если пуля вылетает за заданные границы, то пулю необходимо уничтожить * */ if(this->x() < 0){ this->deleteLater(); } if(this->x() > 500){ this->deleteLater(); } if(this->y() < 0){ this->deleteLater(); } if(this->y() > 500){ this->deleteLater(); } }
Итог
В результате наш главный герой обзавёлся способностью стрелять. Что подробно продемонстрировано в видеоуроке по данной статье. В видеоуроке также даны комментарии по программному коду.
почему-то при компиляции ругается на Bullet тут
Вы переопределили методы paint и boundingRect?
Все оказалось куда проще: не поставил const перед QStyleOptionGraphicsItem *option как в заголовочном файле, так и в фале исходного кода.
Наиболее удобный интерфейс и управление у CLion, однако он не имеет поддержки синтаксиса и макросов Qt, что очень сильно тормозит скорость разработки, по сравнению с Qt Creator, а также абсолютно нет поддержки QML, кроме синтаксиса с помощью плагина.