Evgenii Legotckoi
Evgenii Legotckoi01 жовтня 2015 р. 10:55

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

Після того, як Ми почали керувати своїм героєм, і його погляд завжди звернений у бік мети, настав час написати клас 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();
    }
}

Підсумок

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

Відеоурок

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Стабільний хостинг, на якому розміщується соціальна мережа EVILEG. Для проектів на Django радимо VDS хостинг.

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

k
  • 06 лютого 2018 р. 13: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
  • 06 лютого 2018 р. 17:19

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

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

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

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

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

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
Дмитрий

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:60бали,
  • Рейтинг балів-1
Дмитрий

C++ - Тест 003. Условия и циклы

  • Результат:92бали,
  • Рейтинг балів8
d
  • dsfs
  • 26 квітня 2024 р. 16:56

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80бали,
  • Рейтинг балів4
Останні коментарі
k
kmssr09 лютого 2024 р. 07:43
Qt Linux - Урок 001. Автозапуск програми Qt під Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко05 лютого 2024 р. 14:50
Qt WinAPI - Урок 007. Робота з ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 грудня 2023 р. 23:30
Boost - статичне зв&#39;язування в проекті CMake під Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 грудня 2023 р. 21:38
Boost - статичне зв&#39;язування в проекті CMake під Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 грудня 2023 р. 10:01
Qt/C++ - Урок 056. Підключення бібліотеки Boost в Qt для компіляторів MinGW і MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Тепер обговоріть на форумі
G
George1307 травня 2024 р. 12:27
добавить qlineseries в функции в функции: "GPlotter::addSeries(QString title, QVector &arr)" я вызываю метод setChart(...), я в конструктор передал адрес на QChartView элемент
BlinCT
BlinCT05 травня 2024 р. 17:46
Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
PS
Peter Son04 травня 2024 р. 05:57
Best Indian Food Restaurant In Cincinnati OH Ready to embark on a gastronomic journey like no other? Join us at App india restaurant and discover why we're renowned as the Best Indian Food Restaurant In Cincinnati OH . Whether y…
Evgenii Legotckoi
Evgenii Legotckoi03 травня 2024 р. 02:07
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.
IscanderChe
IscanderChe30 квітня 2024 р. 16:22
Во Flask рендер шаблона не передаётся в браузер Доброе утро! Имеется вот такой шаблон: <!doctype html><html> <head> <title>{{ title }}</title> <link rel="stylesheet" href="{{ url_…

Слідкуйте за нами в соціальних мережах