Реклама

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

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

В предыдущих статьях Мы научились рисовать 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

Видеоурок

 

@EVILEG 19 октября 2015 г. 23:23

Реклама

Реклама

Комментарии

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь

Реклама

Реклама