- 1. Структура проекту з класом Bullet
- 2. customscene.h
- 3. customscene.cpp
- 4. віджет.h
- 5. widget.cpp
- 6. трикутник.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); }
віджет.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)); }
трикутник.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, кроме синтаксиса с помощью плагина.