У попередніх статтях Ми навчилися малювати 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
Скажите, пожалуйста, а такой код void Widget::slotBullet(QPointF start, QPointF end) { /// Добавляем на сцену пулю Bullet *bullet = new Bullet(start, end, triangle); bullet->setCallbackFunc(slotHitTarget); scene->addItem(bullet); } Ведет к утечке памяти или нет? Ведь оператор new есть, а delete нет.
Извините, я не знаю по какой причине комментарий продублировался несколько раз. Еще и форматирование слетело.
Кнопку комментировать несколько раз нажимали? или один? Всё ещё не получается отловить этот плавающий баг с повторной отправкой. Ну да ладно.
Вообще, при добавлении Item`а на графическую сцену, графическая сцена становится владельцем этого item`а, что сказано в документации на этот метод
Следовательно, учитывая специфику Qt фреймворка, что parent удаляет своих children при разрушении, можно не беспокоиться об утечке памяти в данном моменте, поскольку графическая сцена при удалении должна будет удалить свои Item`ы.
Нажимал несколько раз, но страница перезагрузилась один раз. Спасибо за ответ.
Комментарии добавляются через AJAX, перезагрузка страницы не требуется. Впрочем, нужно будет учесть момент, когда соединение проходит не сразу. Спасибо.
Добрый вечер! Скажите пожалуйста почему при отладке данного проекта компилятор находит ошибку в файле widget.cpp: "'targ' was not declared in this scope" ?
Добрый вечер!
Я скачал весь готовый проект и когда запустил вышла эта ошибка
Проверил. Там не должно быть такой ошибки, поскольку существует только одно место в проекте, где используется имя targ и в том коде ошибки быть не должно. Если бы переменная была удалена, тогда да, а так она присутствует.
Добрый вечер! Я извиняюсь за очередное беспокойство, но у меня все проги с "QGraphicsItem *item" пишут что item не декларирован, может у кого-то была такая ошибка и как ее исправить?
Добрый... А вы какую IDE используете, какую версию Qt, под какой платформой собираете? Linux, windows? Проект под Linux так-то собираться не будет, там есть плафтормозависимый код.
Windows 7 (64 бит), Qt Creator 4.5.0 (Community)
Ну... А какая конкретно версия Qt у вас используется. Я вот сейчас на работе собрал проект под Qt 5.9.3 компилятором MSVC 2015.
Код писался пару лет назад, возможно в новых библиотеках некоторые изменения произошли. Там в некоторых местах для есть макросы foreach, обычно их уже переписывают на новый стандарт, поскольку нужды в этом макросе уже нет.
Опять ругается: "range-based 'for' loops are not allowed in C++98 mode". QT 5.5, компилятор mingw492_32
Всё, проблема решена путём переустановки Qt на версию 5.9.3. Никаких проблем с "QGraphicsItem *item"))
Здравствуйте!
Вбил
Удалил файлы сборки, пересобрал, запустил qmake, запустил проект - запустилось.Добрый день! Извиняюсь за поздний ответ. Выходные выдались довольно напряжёнными.
C++98 mode - это режим стандарта 98, устаревший стандарт, нужно использовать минимум c++11 сейчас.
Ну да, переустановка видимо решила проблему с настройкой стандарта. Хорошо )) Извиняюсь за поздний ответ, забитые выходные были, некогда было даже глянуть активность на сайте.
Немножко не понял к чему относится упоминание про C++98 mode.
У человека ошибка была
спасибо за пояснение.
Если вдруг кто-то прочитает....
Скачал проект, скомпилил, запустил. Всё красиво и объектно ориентировано, но вот FPS дико страдает, когда появляется 10+ врагов. Может есть какие-то надстройки? Ведь если нет, то в чём вообще практический смысл QGraphicView/Scene/Items, если всё то же самое и даже с более тяжелыми анимацией и физикой с отрисовкой на том же QPainter летает даже на самом дровяном ноутбуке?