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

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

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

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

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

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

  • спрайт.х
  • sprite.cpp

У даному класі буде викликатись анімований спрайт вибуху, після закінчення кадрів якого цей об'єкт буде видалятися з графічної сцени.


sprite.h

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

  1. #ifndef SPRITE_H
  2. #define SPRITE_H
  3.  
  4. #include <QObject>
  5. #include <QGraphicsItem>
  6. #include <QTimer>
  7. #include <QPixmap>
  8. #include <QPainter>
  9.  
  10. class Sprite : public QObject, public QGraphicsItem
  11. {
  12. Q_OBJECT
  13. public:
  14. explicit Sprite(QPointF point, QObject *parent = 0);
  15.  
  16. /* Переопределяем тип Графического объекта взрыва,
  17. * чтобы пуля могла данный объект игнорировать
  18. * */
  19. enum { Type = UserType + 1 };
  20.  
  21. // Также переопределяем функцию для получения типа объекта
  22. int type() const;
  23.  
  24. signals:
  25.  
  26. public slots:
  27.  
  28. private slots:
  29. void nextFrame(); /// Слот для перелистывания кадров
  30.  
  31. private:
  32. void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
  33. QRectF boundingRect() const;
  34.  
  35. private:
  36. QTimer *timer; /// Таймер для анимации взрыва
  37. QPixmap *spriteImage; /// QPixmap для спрайта со взрывом
  38. int currentFrame; /// Координата текущего кадра в спрайте
  39. };
  40.  
  41. #endif // SPRITE_H

sprite.cpp

У вихідних кодах також буде зміна. Крім того, що ми перевизначаємо функцію type(). Ми також вносимо зміну до слоту для перемикання кадрів спрайту. Спочатку в програмному коді було задано зациклювання анімації. А в даному випадку після закінчення всіх кадрів необхідно буде видалити об'єкт із графічної сцени, що й реалізовано в цьому слоті.

Також є невелика зміна конструктора класу Sprite. У ньому додано один аргумент, який задає положення вибуху на графічній сцені. Тобто куля передаватиме до цього класу місце вибуху.

  1. #include "sprite.h"
  2.  
  3. Sprite::Sprite(QPointF point, QObject *parent) :
  4. QObject(parent), QGraphicsItem()
  5. {
  6. this->setPos(point); // Устанавливаем позицию взрыва
  7. currentFrame = 0; /// Координату X начала взрыва пули
  8. spriteImage = new QPixmap(":/sprites/sprite_sheet.png");
  9.  
  10. timer = new QTimer(); /// Инициализируем таймер анимации взрыва
  11. /// Подключаем сигнал от таймера к слоту анимации взрыва
  12. connect(timer, &QTimer::timeout, this, &Sprite::nextFrame);
  13. timer->start(25); /// Стартуем таймер с частотой 25 милисекунд
  14. }
  15.  
  16. QRectF Sprite::boundingRect() const
  17. {
  18. return QRectF(-10,-10,20,20);
  19. }
  20.  
  21. void Sprite::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
  22. {
  23. // Отрисовываем один из кадров взрыва
  24. painter->drawPixmap(-10,-10, *spriteImage, currentFrame, 0, 20,20);
  25. Q_UNUSED(option);
  26. Q_UNUSED(widget);
  27. }
  28.  
  29. void Sprite::nextFrame()
  30. {
  31. currentFrame += 20; // Продвигаем координату X для выбора следующего кадра
  32. if (currentFrame >= 300 ) {
  33. this->deleteLater(); // Если кадры закончились, то удаляем объект взрыва
  34. } else {
  35. this->update(-10,-10,20,20); // В противном случае обновляем графический объект
  36. }
  37. }
  38.  
  39. int Sprite::type() const {
  40. // Возвращаем тип объекта
  41. return Type;
  42. }

bullet.h

У цьому файлі тільки підключаємо клас Sprite.

bullet.cpp

А ось у класі кулі змінюємо лише кілька рядків програмного коду. Необхідно буде знайти у функції slotTimerBullet() ділянку коду, яка відповідає за перевірку на факт зіткнення з іншими об'єктами.

Цією ділянкою є цикл foreach. У ньому Ми додаємо перевірку на зіткнення з іншим вибухом, і якщо такого зіткнення не існує, але є зіткнення з будь-яким іншим об'єктом, то створюємо вибух і знищуємо кулю.

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

  1. void Bullet::slotTimerBullet()
  2. {
  3. setPos(mapToParent(0, -10));
  4.  
  5. /** Производим проверку на то, наткнулась ли пуля на какой-нибудь
  6. * элемент на графической сцене.
  7. * Для этого определяем небольшую область перед пулей,
  8. * в которой будем искать элементы
  9. * */
  10. QList<QGraphicsItem *> foundItems = scene()->items(QPolygonF()
  11. << mapToScene(0, 0)
  12. << mapToScene(-1, -1)
  13. << mapToScene(1, -1));
  14. /** После чего проверяем все элементы.
  15. * Одними из них будут сама Пуля и Герой - с ними ничего не делаем.
  16. * А с остальными вызываем CallBack функцию
  17. * */
  18. foreach (QGraphicsItem *item, foundItems) {
  19. /* Добавляем в проверку ещё и сами взрывы,
  20. * чтобы пули их игнорировали и не взрывались
  21. * попав во взрвым от другой пули
  22. * */
  23. if (item == this || item == hero || item->type() == (UserType + 1))
  24. continue;
  25. // При попадании по цели или препятствию, вызываем взрыв
  26. scene()->addItem(new Sprite(this->pos()));
  27. callbackFunc(item); // Вызываем CallBack функцию
  28. this->deleteLater(); // Уничтожаем пулю
  29. }
  30.  
  31. /** Проверка выхода за границы поля
  32. * Если пуля вылетает за заданные границы, то пулю необходимо уничтожить
  33. * */
  34. if(this->x() < 0){
  35. this->deleteLater();
  36. }
  37. if(this->x() > 500){
  38. this->deleteLater();
  39. }
  40.  
  41. if(this->y() < 0){
  42. this->deleteLater();
  43. }
  44. if(this->y() > 500){
  45. this->deleteLater();
  46. }
  47. }

Підсумок - Вибух від кулі

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

Посилання на завантаження проекту: targetmotion.zip

Відеоурок

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

L
  • 16 травня 2017 р. 20: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 р. 20: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 р. 21:13

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

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

This scene takes ownership of the item.

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

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

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

Evgenii Legotckoi
  • 17 травня 2017 р. 00:27

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

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

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

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

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

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

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

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

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

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

Е
  • 02 травня 2018 р. 22: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 р. 21:36

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

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

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

a
  • 07 травня 2018 р. 11: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 р. 11:40

Вбил

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

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

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

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

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

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

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

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


По поводу клавиш. Думал раньше, что Qt сам реализует платформонезависимые элементы, в т.ч. отслеживание нажатие клавиш. А тут как-то странно получилось.
Evgenii Legotckoi
  • 10 травня 2018 р. 13: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 р. 15:35

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

b
  • 27 листопада 2022 р. 02:01

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

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
  • Останні коментарі
  • Evgenii Legotckoi
    16 квітня 2025 р. 17:08
    Благодарю за отзыв. И вам желаю всяческих успехов!
  • IscanderChe
    12 квітня 2025 р. 17:12
    Добрый день. Спасибо Вам за этот проект и отдельно за ответы на форуме, которые мне очень помогли в некоммерческих пет-проектах. Профессиональным программистом я так и не стал, но узнал мно…
  • AK
    01 квітня 2025 р. 11:41
    Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
  • Evgenii Legotckoi
    09 березня 2025 р. 21:02
    К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
  • VP
    09 березня 2025 р. 16:14
    Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…