Evgenii Legotckoi
Evgenii Legotckoi19 жовтня 2015 р. 13:23

GameDev на Qt - Підручник 5. Вибух куль за допомогою зображень спрайтів

У попередніх статтях Ми навчилися малювати sprite картинку , а також застосовувати її в Qt за допомогою QPixmap так, щоб у нас вийшов анімований вибух. А тепер Нам необхідно цей вибух поміщати якраз у те місце, куди вдаряється куля. Тобто куля вибухне.

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

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

Проект піддається модифікації у тому плані, що додається новий клас із такими файлами:

  • спрайт.х
  • 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

Відеоурок

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

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

L
  • 16 травня 2017 р. 10:38

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

L
  • 16 травня 2017 р. 10:41

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

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

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

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

This scene takes ownership of the item.

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

L
  • 16 травня 2017 р. 13:52

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

Evgenii Legotckoi
  • 16 травня 2017 р. 14:27

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

Е
  • 01 травня 2018 р. 09:33

Добрый вечер! Скажите пожалуйста почему при отладке данного проекта компилятор находит ошибку в файле widget.cpp: "'targ' was not declared in this scope" ?

Добрый вечер!

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

Я скачал весь готовый проект и когда запустил вышла эта ошибка

Проверил. Там не должно быть такой ошибки, поскольку существует только одно место в проекте, где используется имя targ и в том коде ошибки быть не должно. Если бы переменная была удалена, тогда да, а так она присутствует.

Е
  • 02 травня 2018 р. 12:23

Добрый вечер! Я извиняюсь за очередное беспокойство, но у меня все проги с "QGraphicsItem *item" пишут что item не декларирован, может у кого-то была такая ошибка и как ее исправить?

Добрый... А вы какую IDE используете, какую версию Qt, под какой платформой собираете? Linux, windows? Проект под Linux так-то собираться не будет, там есть плафтормозависимый код.

Е
  • 02 травня 2018 р. 12:56

Windows 7 (64 бит), Qt Creator 4.5.0 (Community)

Ну... А какая конкретно версия Qt у вас используется. Я вот сейчас на работе собрал проект под Qt 5.9.3 компилятором MSVC 2015.
Код писался пару лет назад, возможно в новых библиотеках некоторые изменения произошли. Там в некоторых местах для есть макросы foreach, обычно их уже переписывают на новый стандарт, поскольку нужды в этом макросе уже нет.

Было
foreach (QGraphicsItem *targ, targets) 
{

}
Стало
for (QGraphicsItem *targ : targets) 
{

}
Попробуйте так переписать проблемные места.
Е
  • 03 травня 2018 р. 11:36

Опять ругается: "range-based 'for' loops are not allowed in C++98 mode". QT 5.5, компилятор mingw492_32

Е
  • 05 травня 2018 р. 01:06

Всё, проблема решена путём переустановки Qt на версию 5.9.3. Никаких проблем с "QGraphicsItem *item"))

a
  • 07 травня 2018 р. 01:36

Здравствуйте!

Скачал проект чтобы попробовать. Не собирается:
triangle.obj:-1: ошибка: LNK2019: unresolved external symbol __imp_GetAsyncKeyState referenced in function "private: void __cdecl Triangle::slotGameTimer(void)" (?slotGameTimer@Triangle@@AEAAXXZ)
Windows 10, Qt 5.8.0(MSVC 2015, 32 бита).

Подскажите, пожалуйста, что нужно сделать?
a
  • 07 травня 2018 р. 01:40

Вбил

win32-msvc*{
    LIBS += -luser32
}
Удалил файлы сборки, пересобрал, запустил qmake, запустил проект - запустилось.
Где-то писали, что работает на windows и есть проблемы с отлавливанием событий нажатий клавиш. Что код получается системно зависимым.

Правильно понимаю, что на Linux работать не будет?
Evgenii Legotckoi
  • 07 травня 2018 р. 05:59

Добрый день! Извиняюсь за поздний ответ. Выходные выдались довольно напряжёнными.

Да, вы правы, к сожалению данный код платформозависимый и конкретно данные примеры применимы только в рамках WinAPI. У меня так и не дошли руки до изучения отлавливания нажатий клавиш в рамках Linux. Увы...

C++98 mode - это режим стандарта 98, устаревший стандарт, нужно использовать минимум c++11 сейчас.

Ну да, переустановка видимо решила проблему с настройкой стандарта. Хорошо )) Извиняюсь за поздний ответ, забитые выходные были, некогда было даже глянуть активность на сайте.

a
  • 10 травня 2018 р. 00:46

Немножко не понял к чему относится упоминание про C++98 mode.


По поводу клавиш. Думал раньше, что Qt сам реализует платформонезависимые элементы, в т.ч. отслеживание нажатие клавиш. А тут как-то странно получилось.
Evgenii Legotckoi
  • 10 травня 2018 р. 03:47

У человека ошибка была

"range-based 'for' loops are not allowed in C++98 mode"
Это означает, что у него компилятор собирает проект со стандартом C++98, а range-based циклы были введены только в стандарте C++11. Поэтому проект и не собирался.

По поводу платформозависимых частей. Qt реализует очень много функционала кросплатформенно, но есть функционал, который он не реализует, приходится писать платформозависимый код и разруливать его через Pimpl.
Например, глобальные хоткеи или логирование нажатий клавиш, когда приложение находится в фоновом режиме. Это Qt не реализует, нужно лезть в библиотеки системы. Тоже самое касается и отслеживание входа пользователя через RDP под Windows. Нужно использовать WinAPI для этого, под Linux это придётся делать через DBus. Под Андроидом придётся писать немного Java кода, если нжуно работать с системными уведомлениями. В общем Qt предоставляет очень мощные кроссплатформенные средства для разработки, но и его возможности небезграничны.
a
  • 10 травня 2018 р. 05:35

спасибо за пояснение.

b
  • 26 листопада 2022 р. 15:01

Если вдруг кто-то прочитает....
Скачал проект, скомпилил, запустил. Всё красиво и объектно ориентировано, но вот FPS дико страдает, когда появляется 10+ врагов. Может есть какие-то надстройки? Ведь если нет, то в чём вообще практический смысл QGraphicView/Scene/Items, если всё то же самое и даже с более тяжелыми анимацией и физикой с отрисовкой на том же QPainter летает даже на самом дровяном ноутбуке?

Коментарі

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,>…

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