Реклама

GameDev на Qt - Урок 5. Взрыв от пуль с помощью sprite картинки

GameDev, Bullet, QPixmap, Qt, sprite, sprite sheet, explosion. взрыв, Спрайт

В предыдущих статьях Мы научились рисовать sprite картинку , а также применять её в Qt с помощью QPixmap так, чтобы у нас получился анимированный взрыв. А теперь Нам необходимо этот самый взрыв помещать как раз в то место, куда ударяется пуля. То есть пуля будет взрываться.

Для реализации данной задумки добавим класс спрайта из прошлого урока и модифицируем его. Дело в том, что данный объект спрайта является классом, наследованным от QGraphicsItem , а следовательно пуля при столкновении со взрывом от другой пули будет вести себя также, как при столкновении с препятствием или мишенью. Поэтому нужно будет сделать так, чтобы пули игнорировали взрывы от других пули и пролетали насквозь.

Структура проекта

Проект подвергается модификации в том плане, что добавляется новый класс со следующими файлами:

  • sprite.h
  • sprite.cpp

В данном классе будет вызываться анимированный спрайт взрыва, по истечении кадров которого данный объект будет удаляться с графической сцены.

sprite.h

По сравнению с кодом из урока по подключению анимированных спрайтов в Qt в данном уроке необходимо переопределить виртуальную функцию type(), а также заменить идентификатор типа нашего графического объекта, чтобы он отличался от идентификаторов типа остальных графических объектов. Например Type будет равен UserType + 1 вместо UserType.

#ifndef SPRITE_H
#define SPRITE_H

#include <QObject>
#include <QGraphicsItem>
#include <QTimer>
#include <QPixmap>
#include <QPainter>

class Sprite : public QObject, public QGraphicsItem
{
    Q_OBJECT
public:
    explicit Sprite(QPointF point, QObject *parent = 0);

    /* Переопределяем тип Графического объекта взрыва,
     * чтобы пуля могла данный объект игнорировать
     * */
    enum { Type = UserType + 1 };

    // Также переопределяем функцию для получения типа объекта
    int type() const;

signals:

public slots:

private slots:
    void nextFrame();   /// Слот для перелистывания кадров

private:
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
    QRectF boundingRect() const;

private:
    QTimer *timer;  /// Таймер для анимации взрыва
    QPixmap *spriteImage;   /// QPixmap для спрайта со взрывом
    int currentFrame;   /// Координата текущего кадра в спрайте
};

#endif // SPRITE_H

sprite.cpp

В исходных кодах также будет присутствовать изменение. Помимо того, что мы переопределяем функцию type(). Мы также вносим изменение в слот для переключения кадров спрайта. Изначально в программном коде было задано зацикливание анимации. А в данном случае по истечению всех кадров необходимо будет удалить объект из графической сцены, что и реализовано в данном слоте.

Также присутствует небольшое изменение конструктора класса Sprite. В нём добавлен один аргумент, который задаёт положение взрыва на графической сцене. То есть пуля будет передавать в данный класс место взрыва.

#include "sprite.h"

Sprite::Sprite(QPointF point, QObject *parent) :
    QObject(parent), QGraphicsItem()
{
    this->setPos(point);    // Устанавливаем позицию взрыва
    currentFrame = 0;       /// Координату X начала взрыва пули
    spriteImage = new QPixmap(":/sprites/sprite_sheet.png");

    timer = new QTimer();   /// Инициализируем таймер анимации взрыва
    /// Подключаем сигнал от таймера к слоту анимации взрыва
    connect(timer, &QTimer::timeout, this, &Sprite::nextFrame);
    timer->start(25);   /// Стартуем таймер с частотой 25 милисекунд
}

QRectF Sprite::boundingRect() const
{
    return QRectF(-10,-10,20,20);
}

void Sprite::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    // Отрисовываем один из кадров взрыва
    painter->drawPixmap(-10,-10, *spriteImage, currentFrame, 0, 20,20);
    Q_UNUSED(option);
    Q_UNUSED(widget);
}

void Sprite::nextFrame()
{
    currentFrame += 20; // Продвигаем координату X для выбора следующего кадра
    if (currentFrame >= 300 ) {
        this->deleteLater();    // Если кадры закончились, то удаляем объект взрыва
    } else {
        this->update(-10,-10,20,20);    // В противном случае обновляем графический объект
    }
}

int Sprite::type() const {
    // Возвращаем тип объекта
    return Type;
}

bullet.h

В данном файле только подключаем класс Sprite.

bullet.cpp

А вот в классе пули изменяем всего несколько строк программного кода. Необходимо будет найти в функции slotTimerBullet() участок кода, который отвечает за проверку на факт столкновения с другими объектами.

Этим участком является цикл foreach. В нём Мы добавляем проверку на столкновение с другим взрывом, и если такого столкновения не существует, но присутствует столкновение с каким-либо другим объектом, то создаём взрыв и уничтожаем пулю.

Нюанс этого кода состоит в том, что мы не делаем qgraphicsitem_cast в объект класса sprite . Поскольку при создании данного объекта ему присваивается (UserType + 1) , а QGraphicsItem имеется функция type() , то этого вполне достаточно, чтобы определить в какой объект ударилась пуля.

void Bullet::slotTimerBullet()
{
    setPos(mapToParent(0, -10));

    /** Производим проверку на то, наткнулась ли пуля на какой-нибудь
     * элемент на графической сцене.
     * Для этого определяем небольшую область перед пулей,
     * в которой будем искать элементы
     * */
    QList<QGraphicsItem *> foundItems = scene()->items(QPolygonF()
                                                           << mapToScene(0, 0)
                                                           << mapToScene(-1, -1)
                                                           << mapToScene(1, -1));
    /** После чего проверяем все элементы.
     * Одними из них будут сама Пуля и Герой - с ними ничего не делаем.
     * А с остальными вызываем CallBack функцию
     * */
    foreach (QGraphicsItem *item, foundItems) {
        /* Добавляем в проверку ещё и сами взрывы,
         * чтобы пули их игнорировали и не взрывались
         * попав во взрвым от другой пули
         * */
        if (item == this || item == hero || item->type() == (UserType + 1))
            continue;
        // При попадании по цели или препятствию, вызываем взрыв
        scene()->addItem(new Sprite(this->pos()));
        callbackFunc(item);     // Вызываем CallBack функцию
        this->deleteLater();    // Уничтожаем пулю
    }

    /** Проверка выхода за границы поля
     * Если пуля вылетает за заданные границы, то пулю необходимо уничтожить
     * */
    if(this->x() < 0){
        this->deleteLater();
    }
    if(this->x() > 500){
        this->deleteLater();
    }

    if(this->y() < 0){
        this->deleteLater();
    }
    if(this->y() > 500){
        this->deleteLater();
    }
}

Итог - Взрыв от пули

В результате проделанной работы в местах удара пули будут образовываться взрывы, как показано на рисунке. А также Вы можете увидеть демонстрацию работы игры со взрывами от пуль в видеоуроке.

Ссылка на скачивание проекта: targetmotion.zip

Видеоурок

Реклама

Комментарии

Скажите, пожалуйста, а такой код void Widget::slotBullet(QPointF start, QPointF end) { /// Добавляем на сцену пулю Bullet *bullet = new Bullet(start, end, triangle); bullet->setCallbackFunc(slotHitTarget); scene->addItem(bullet); } Ведет к утечке памяти или нет? Ведь оператор new есть, а delete нет.

Извините, я не знаю по какой причине комментарий продублировался несколько раз. Еще и форматирование слетело.

void Widget::slotBullet(QPointF start, QPointF end)
{
    /// Добавляем на сцену пулю
    Bullet *bullet = new Bullet(start, end, triangle);
    bullet->setCallbackFunc(slotHitTarget);
    scene->addItem(bullet);
}

Кнопку комментировать несколько раз нажимали? или один? Всё ещё не получается отловить этот плавающий баг с повторной отправкой. Ну да ладно.

Вообще, при добавлении Item`а на графическую сцену, графическая сцена становится владельцем этого item`а, что сказано в документации на этот метод

This scene takes ownership of the item.

Следовательно, учитывая специфику Qt фреймворка, что parent удаляет своих children при разрушении, можно не беспокоиться об утечке памяти в данном моменте, поскольку графическая сцена при удалении должна будет удалить свои Item`ы.

Нажимал несколько раз, но страница перезагрузилась один раз. Спасибо за ответ.

Комментарии добавляются через AJAX, перезагрузка страницы не требуется. Впрочем, нужно будет учесть момент, когда соединение проходит не сразу. Спасибо.

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
  • Vadym
  • 26 сентября 2017 г. 18:10

C++ - Тест 005. Структуры и Классы

  • Результат - 83 баллов
  • Vadym
  • 26 сентября 2017 г. 18:05

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

  • Результат - 80 баллов
  • Vadym
  • 26 сентября 2017 г. 4:44

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

  • Результат - 78 баллов
Последние комментарии
  • EVILEG
  • 22 сентября 2017 г. 12:45

Qt/C++ - Урок 055. QSignalMapper VS лямбда функции

Вы используете стандартную практику замыканий, когда нет никакой необходимости объявлять функции в классе, поскольку они используются в одном единственном месте класса, а объявление всех эти л...

  • Damir
  • 22 сентября 2017 г. 2:35

Qt/C++ - Урок 055. QSignalMapper VS лямбда функции

Может и кривовато но чёрт побери работает и класс от ненужной больше ни где фигни не разбухает.

  • Damir
  • 22 сентября 2017 г. 2:29

Qt/C++ - Урок 055. QSignalMapper VS лямбда функции

Как вам такое enum { PROFILE_TOOLPATH_FORM, POCKET_TOOLPATH_FORM, DRILLING_TOOLPATH_FORM }; QToolBar* toolpathToolBar = addToolBar(tr("Toolpa...

  • Mr_lKl
  • 17 сентября 2017 г. 16:14

QML - Урок 031. Отключаем системное обрамление окна в QML и пишем код для обработки перемещения и ресайза окна

Спасибо! Этим и займусь. Ещё попробую скинуть проект другу, посмотрю, как QT будет справляться там.

  • EVILEG
  • 17 сентября 2017 г. 14:14

QML - Урок 031. Отключаем системное обрамление окна в QML и пишем код для обработки перемещения и ресайза окна

Тогда это однознано баг, я бы глянул на официальном багтрекере Qt, есть ли информация об этом баге, и возможно стоит создать таск с этим багом.

Сейчас обсуждают на форуме
  • EVILEG
  • 27 сентября 2017 г. 1:54

Сборка проекта в Qt под Android.

В общем я вас не обрадую, я сегодня сам поразбирался с этой проблемой. И ... (барабанная дробь) ... Qt Creator 4.4 с багом. Это не работает в принципе. Фикс будет в Qt Creator 4.5. ...

  • EVILEG
  • 26 сентября 2017 г. 18:03

Как дождаться выполнения функции

Именно, а самому писать скачивание файла - это вам не нужно. Поэтому нужно правильно написать обработку процесса скачивания. Для этого и потребуется делать либо буфер. Либо обновлять GUI когда...

  • EVILEG
  • 26 сентября 2017 г. 16:58

Virtual Keyboard

Больше похоже на какой-то баг с клавиатурой. Перекопал разные варианты, а результат такой же. Кроме Британской раскладки ничего не работает.

  • verside
  • 20 сентября 2017 г. 12:39

Qt и Visual Studio (32-битная версия)

Делал ровно так, и описано. Но что-то не подхватывает Qt. Есть идеи, что Visual Studio какие-то переменные в окружение не прописал, но какие, пока не удалось понять. Я про...

Проблема при компиляции WebKit для Qt 5.7.1

Здравствуйте! Еще есть некоторые пользователи которые остались на Win XP.