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
AD

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

  • Результат:50бали,
  • Рейтинг балів-4
m
  • molni99
  • 26 жовтня 2024 р. 11:37

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

  • Результат:80бали,
  • Рейтинг балів4
m
  • molni99
  • 26 жовтня 2024 р. 11:29

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

  • Результат:20бали,
  • Рейтинг балів-10
Останні коментарі
ИМ
Игорь Максимов22 листопада 2024 р. 22:51
Django - Підручник 017. Налаштуйте сторінку входу до Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi01 листопада 2024 р. 00:37
Django - Урок 064. Як написати розширення для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 жовтня 2024 р. 18:19
Читалка файлів fb3 на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов05 жовтня 2024 р. 17:51
Django - Урок 064. Як написати розширення для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas505 липня 2024 р. 21:02
QML - Урок 016. База даних SQLite та робота з нею в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Тепер обговоріть на форумі
Evgenii Legotckoi
Evgenii Legotckoi25 червня 2024 р. 01:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 листопада 2024 р. 17:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject04 червня 2022 р. 13:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9Anonim25 жовтня 2024 р. 19:10
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

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