Evgenii Legotckoi
01 жовтня 2015 р. 20:55

GameDev на Qt - Підручник 2. Клас Writing Bullet для стрільби в Qt

Після того, як Ми почали керувати своїм героєм, і його погляд завжди звернений у бік мети, настав час написати клас Bullet , який відповідатиме за кулі та їхній політ по ігровій сцені. Механіка переміщення кулі за графічною сценою буде аналогічна механіці переміщення головного героя. Відмінність буде в тому, що куля завжди рухається по прямій і розворот кулі потрібно буде встановити тільки в момент створення об'єкта класу **Bullet, щоб задати напрямки польоту кулі.

Структура проекту з класом Bullet

Структура проекту з попереднього уроку змінюється в тому плані, що додається новий клас Bullet. Також потрібно доопрацювати всі інші класи для забезпечення взаємодії з новим класом .

Тому почнемо по порядку, а саме з класу, де ініціалізується подія, що викликає процес стрілянини.


customscene.h

У кастомізованій графічній сцені необхідно буде додати функції для обробки натискань клавіш миші ( mousePressEvent і mouseReleaseEvent ), а також сигнал передачі дозволу на стрілянину від графічної сцени, до головного героя. Звичайно, перевіряти натискання клавіш миші можна було б і в класі головного героя, але проблема полягає в тому, що потрібно дозволити стрілянину тільки в області графічної сцени, оскільки її можуть бути інші інтерактивні елементи, які не повинні викликати події стрілянини.

  1. #ifndef CUSTOMSCENE_H
  2. #define CUSTOMSCENE_H
  3.  
  4. #include <QObject>
  5. #include <QGraphicsScene>
  6. #include <QGraphicsSceneMouseEvent>
  7. #include <QDebug>
  8.  
  9. class CustomScene : public QGraphicsScene
  10. {
  11. Q_OBJECT
  12. public:
  13. explicit CustomScene(QObject *parent = 0);
  14. ~CustomScene();
  15.  
  16. signals:
  17. // Сигнал для передачи координат положения курсора мыши
  18. void signalTargetCoordinate(QPointF point);
  19. void signalShot(bool shot); // Сигнал на стрельбу
  20.  
  21. public slots:
  22.  
  23. private:
  24. // Функция, в которой производится отслеживание положения мыши
  25. void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
  26. void mousePressEvent(QGraphicsSceneMouseEvent *event);
  27. void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
  28. };
  29.  
  30. #endif // CUSTOMSCENE_H

customscene.cpp

  1. #include "customscene.h"
  2.  
  3. CustomScene::CustomScene(QObject *parent) :
  4. QGraphicsScene()
  5. {
  6. Q_UNUSED(parent);
  7. }
  8.  
  9. CustomScene::~CustomScene()
  10. {
  11.  
  12. }
  13.  
  14. void CustomScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
  15. {
  16. emit signalTargetCoordinate(event->scenePos());
  17. }
  18.  
  19. void CustomScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
  20. {
  21. emit signalShot(true); // Когда клавиша мыши нажата, то можно стрелять
  22. Q_UNUSED(event);
  23. }
  24.  
  25. void CustomScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
  26. {
  27. emit signalShot(false); // Когда клавишу мыши отпустили, то стрелять нельзя
  28. Q_UNUSED(event);
  29. }

віджет.h

Для передачі даних про дозвіл стрільби підключаємо signalShot із графічної сцени до slotShot у трикутнику. У ньому буде передаватися інформація про те, чи натиснуто клавішу миші в області графічної сцени. Також із трикутника передається signalBullet, який ініціює створення кулі, в ядро гри, у клас Widget. У слоті slotBullet створюється об'єкт класу Bullet та встановлюється на графічну сцену.

  1. #ifndef WIDGET_H
  2. #define WIDGET_H
  3.  
  4. #include <QWidget>
  5. #include <QGraphicsScene>
  6. #include <QGraphicsItem>
  7.  
  8. #include <triangle.h>
  9. #include <customscene.h>
  10. #include <bullet.h>
  11.  
  12. namespace Ui {
  13. class Widget;
  14. }
  15.  
  16. class Widget : public QWidget
  17. {
  18. Q_OBJECT
  19.  
  20. public:
  21. explicit Widget(QWidget *parent = 0);
  22. ~Widget();
  23.  
  24. private:
  25. Ui::Widget *ui;
  26. CustomScene *scene; // Объявляем графическую сцену
  27. Triangle *triangle; // Объявляем треугольник
  28.  
  29. private slots:
  30. void slotBullet(QPointF start, QPointF end);
  31. };
  32.  
  33. #endif // WIDGET_H

widget.cpp

  1. #include "widget.h"
  2. #include "ui_widget.h"
  3.  
  4. Widget::Widget(QWidget *parent) :
  5. QWidget(parent),
  6. ui(new Ui::Widget)
  7. {
  8. /* Программный код из урока
  9. * GameDev. Отслеживание перемещения мыши в QGraphicsScene
  10. */
  11. // Соединяем сигнала стрельбы с графической сцены со слотом разрешения стрельбы треугольника
  12. connect(scene, &CustomScene::signalShot, triangle, &Triangle::slotShot);
  13. // Соединяем сигнал на создание пули со слотом, создающим пули в игре
  14. connect(triangle, &Triangle::signalBullet, this, &Widget::slotBullet);
  15. }
  16.  
  17. Widget::~Widget()
  18. {
  19. delete ui;
  20. }
  21.  
  22. void Widget::slotBullet(QPointF start, QPointF end)
  23. {
  24. // Добавляем на сцену пулю
  25. scene->addItem(new Bullet(start, end));
  26. }

трикутник.h

У класі головного героя необхідно створити новий таймер, який ініціюватиме створення нової кулі 6 разів на секунду, якщо надійшов сигнал із графічної сцени на дозвіл стрілянини. Як тільки надійшов сигнал на припинення стрілянини, створення куль ігнорується.

  1. #ifndef TRIANGLE_H
  2. #define TRIANGLE_H
  3.  
  4. #include <QObject>
  5. #include <QGraphicsItem>
  6. #include <QPainter>
  7. #include <QPolygon>
  8. #include <QTimer>
  9. #include <QDebug>
  10. #include <QCursor>
  11.  
  12. #include <windows.h>
  13.  
  14. class Triangle : public QObject, public QGraphicsItem
  15. {
  16. Q_OBJECT
  17. public:
  18. explicit Triangle(QObject *parent = 0);
  19. ~Triangle();
  20.  
  21. signals:
  22. // Сигнал для создания пули с параметрами траектории
  23. void signalBullet(QPointF start, QPointF end);
  24.  
  25. public slots:
  26. // Слот для получения данных о положении курсора
  27. void slotTarget(QPointF point);
  28. // слот для обработки разрешения стрельбы
  29. void slotShot(bool shot);
  30.  
  31. private:
  32. QRectF boundingRect() const;
  33. void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
  34.  
  35. private slots:
  36. void slotGameTimer(); // Игровой слот
  37. void slotBulletTimer(); // Слот проверки пули
  38.  
  39. private:
  40. bool shot; // Переменная состояния стрельбы
  41. QTimer *bulletTimer; // Таймер пули
  42. QTimer *gameTimer; // Игровой таймер
  43. QPointF target; // Положение курсора
  44. };
  45.  
  46. #endif // TRIANGLE_H

triangle.cpp

  1. #include "triangle.h"
  2. #include <math.h>
  3.  
  4. static const double Pi = 3.14159265358979323846264338327950288419717;
  5. static double TwoPi = 2.0 * Pi;
  6.  
  7. static qreal normalizeAngle(qreal angle)
  8. {
  9. while (angle < 0)
  10. angle += TwoPi;
  11. while (angle > TwoPi)
  12. angle -= TwoPi;
  13. return angle;
  14. }
  15.  
  16. Triangle::Triangle(QObject *parent) :
  17. QObject(parent), QGraphicsItem()
  18. {
  19. setRotation(0); // Устанавливаем исходный разворот треугольника
  20.  
  21. target = QPointF(0,0); // Устанавливаем изначальное положение курсора
  22. shot = false;
  23.  
  24. gameTimer = new QTimer(); // Инициализируем игровой таймер
  25. // Подключаем сигнал от таймера и слоту обработки игрового таймера
  26. connect(gameTimer, &QTimer::timeout, this, &Triangle::slotGameTimer);
  27. gameTimer->start(10); // Стартуем таймер
  28.  
  29. bulletTimer = new QTimer(); // Инициализируем таймер создания пуль
  30. connect(bulletTimer, &QTimer::timeout, this, &Triangle::slotBulletTimer);
  31. bulletTimer->start(1000/6); // Стреляем 6 раз в секунду
  32.  
  33.  
  34. }
  35.  
  36. Triangle::~Triangle()
  37. {
  38.  
  39. }
  40.  
  41. QRectF Triangle::boundingRect() const
  42. {
  43. return QRectF(-20,-30,40,60);
  44. }
  45.  
  46. void Triangle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
  47. {
  48. /** Отрисовка треугольника
  49. * */
  50. QPolygon polygon;
  51. polygon << QPoint(0,-30) << QPoint(20,30) << QPoint(-20,30);
  52. painter->setBrush(Qt::red);
  53. painter->drawPolygon(polygon);
  54.  
  55. Q_UNUSED(option);
  56. Q_UNUSED(widget);
  57. }
  58.  
  59. void Triangle::slotTarget(QPointF point)
  60. {
  61. /* Программный код из урока
  62. * GameDev. Отслеживание перемещения мыши в QGraphicsScene
  63. */
  64. }
  65.  
  66. void Triangle::slotGameTimer()
  67. {
  68. /* Программный код из урока
  69. * GameDev. Отслеживание перемещения мыши в QGraphicsScene
  70. */
  71. }
  72.  
  73. void Triangle::slotBulletTimer()
  74. {
  75. // Если стрельба разрешена, то вызываем сигнал на создание пули
  76. if(shot) emit signalBullet(QPointF(this->x(),this->y()), target);
  77.  
  78. }
  79.  
  80. void Triangle::slotShot(bool shot)
  81. {
  82. this->shot = shot; // Получаем разрешение или запрет на стрельбу
  83. }

bullet.h

А тепер приступимо до створення самої кулі за допомогою класу Bullet . Цей клас успадковується від QGraphicsItem і робота з ним проводиться аналогічно до роботи з головним героєм. При виході кулі за межі ігрової області вона повинна бути знищена для звільнення пам'яті програми.

  1. #ifndef BULLET_H
  2. #define BULLET_H
  3.  
  4. #include <QObject>
  5. #include <QGraphicsItem>
  6. #include <QTimer>
  7. #include <QPainter>
  8.  
  9. class Bullet : public QObject, public QGraphicsItem
  10. {
  11. Q_OBJECT
  12. public:
  13. explicit Bullet(QPointF start, QPointF end, QObject *parent = 0);
  14. ~Bullet();
  15.  
  16. signals:
  17.  
  18.  
  19. public slots:
  20.  
  21. private:
  22. QRectF boundingRect() const;
  23. void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
  24.  
  25. private:
  26. QTimer *timerBullet; // Слот для обработки таймера пули
  27.  
  28. private slots:
  29. void slotTimerBullet(); // Слот для обработки полёта пули
  30. };
  31.  
  32. #endif // BULLET_H

bullet.cpp

У конструктор кулі вдається два об'єкти QPointF, які задають траєкторію польоту кулі. Перша координата - це місце вильоту кулі, місце, де ініційовано її створення, і друга координата - це місцезнаходження курсору на момент пострілу. За цими двома координатами задається траєкторія її польоту.

Ігровий таймер кулі по тиках ініціює запуск ігрового слота, в якому куля просувається на 10 пікселів уперед і у разі виходу за межі ігрової області знищується.

  1. #include "bullet.h"
  2. #include <math.h>
  3.  
  4. static const double Pi = 3.14159265358979323846264338327950288419717;
  5. static double TwoPi = 2.0 * Pi;
  6.  
  7. static qreal normalizeAngle(qreal angle)
  8. {
  9. while (angle < 0)
  10. angle += TwoPi;
  11. while (angle > TwoPi)
  12. angle -= TwoPi;
  13. return angle;
  14. }
  15.  
  16. Bullet::Bullet(QPointF start, QPointF end, QObject *parent)
  17. : QObject(parent), QGraphicsItem()
  18. {
  19. this->setRotation(0); // Устанавливаем начальный разворот
  20. this->setPos(start); // Устанавливаем стартовую позицию
  21. // Определяем траекторию полёта пули
  22. QLineF lineToTarget(start, end);
  23. // Угол поворота в направлении к цели
  24. qreal angleToTarget = ::acos(lineToTarget.dx() / lineToTarget.length());
  25. if (lineToTarget.dy() < 0)
  26. angleToTarget = TwoPi - angleToTarget;
  27. angleToTarget = normalizeAngle((Pi - angleToTarget) + Pi / 2);
  28.  
  29. /* Разворачиваем пули по траектории
  30. * */
  31. if (angleToTarget >= 0 && angleToTarget < Pi) {
  32. /// Rotate left
  33. setRotation(rotation() - angleToTarget * 180 /Pi);
  34. } else if (angleToTarget <= TwoPi && angleToTarget > Pi) {
  35. /// Rotate right
  36. setRotation(rotation() + (angleToTarget - TwoPi )* (-180) /Pi);
  37. }
  38. // Инициализируем полётный таймер
  39. timerBullet = new QTimer();
  40. // И подключаем его к слоту для обработки полёта пули
  41. connect(timerBullet, &QTimer::timeout, this, &Bullet::slotTimerBullet);
  42. timerBullet->start(7);
  43. }
  44.  
  45. Bullet::~Bullet()
  46. {
  47.  
  48. }
  49.  
  50. QRectF Bullet::boundingRect() const
  51. {
  52. return QRectF(0,0,2,4);
  53. }
  54.  
  55. void Bullet::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
  56. {
  57. painter->setPen(Qt::black);
  58. painter->setBrush(Qt::black);
  59. painter->drawRect(0,0,2,4);
  60.  
  61. Q_UNUSED(option);
  62. Q_UNUSED(widget);
  63. }
  64.  
  65. void Bullet::slotTimerBullet()
  66. {
  67. setPos(mapToParent(0, -10));
  68.  
  69. /* Проверка выхода за границы поля
  70. * Если пуля вылетает за заданные границы, то пулю необходимо уничтожить
  71. * */
  72. if(this->x() < 0){
  73. this->deleteLater();
  74. }
  75. if(this->x() > 500){
  76. this->deleteLater();
  77. }
  78.  
  79. if(this->y() < 0){
  80. this->deleteLater();
  81. }
  82. if(this->y() > 500){
  83. this->deleteLater();
  84. }
  85. }

Підсумок

В результаті наш головний герой обзавівся здатністю стріляти. Що докладно продемонстровано у відеоуроці за цією статтею. У відеоуроці також подано коментарі за програмним кодом.

Відеоурок

Вам це подобається? Поділіться в соціальних мережах!

k
  • 07 лютого 2018 р. 00:41

почему-то при компиляции ругается на Bullet тут

  1. void Widget::slotBullet(QPointF start, QPointF end)
  2. {
  3. // Добавляем на сцену пулю
  4. scene->addItem(new Bullet(start, end));
  5. }
ошибка: invalid new-expression of abstract class type 'Bullet'
Почему компилятор считает его абстрактным?
Qt 5.10, компилятор minGW 5.3.0
Evgenii Legotckoi
  • 07 лютого 2018 р. 04:19

Вы переопределили методы paint и boundingRect?

И возможно, что требуется переопределить ещё какие-то методы в Qt 5.10. Посмотрите, что там есть из виртуальных методов у базового класса bullet.
Конкретно посмотрите методы которые приравнены нулю, например, с такой записью
virtual void someMethod() = 0;
k
  • 12 лютого 2018 р. 22:33

Все оказалось куда проще: не поставил const перед QStyleOptionGraphicsItem *option как в заголовочном файле, так и в фале исходного кода.

Зато получше познакомился с интерфейсом Qt Creator, пока следовал вашему совету, очень удобный. Спасибо :)
Evgenii Legotckoi
  • 13 лютого 2018 р. 03:17

Наиболее удобный интерфейс и управление у CLion, однако он не имеет поддержки синтаксиса и макросов Qt, что очень сильно тормозит скорость разработки, по сравнению с Qt Creator, а также абсолютно нет поддержки QML, кроме синтаксиса с помощью плагина.

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
  • Останні коментарі
  • Evgenii Legotckoi
    16 квітня 2025 р. 17:08
    Благодарю за отзыв. И вам желаю всяческих успехов!
  • IscanderChe
    12 квітня 2025 р. 17:12
    Добрый день. Спасибо Вам за этот проект и отдельно за ответы на форуме, которые мне очень помогли в некоммерческих пет-проектах. Профессиональным программистом я так и не стал, но узнал мно…
  • AK
    01 квітня 2025 р. 11:41
    Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
  • Evgenii Legotckoi
    09 березня 2025 р. 21:02
    К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
  • VP
    09 березня 2025 р. 16:14
    Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…