Evgenii Legotckoi
Evgenii Legotckoi19. Oktober 2015 13:23

GameDev on Qt - Tutorial 5. Die Explosion von Kugeln mit Sprite-Bildern

In früheren Artikeln haben wir gelernt, wie man ein Sprite-Bild zeichnet sowie es in Qt mit QPixmap anwendet , damit wir eine animierte Explosion erhalten. Und jetzt müssen wir genau diese Explosion genau dort platzieren, wo die Kugel einschlägt. Das heißt, die Kugel wird explodieren.

Um diese Idee zu implementieren, fügen wir eine Sprite-Klasse aus der letzten Lektion hinzu und ändern sie. Tatsache ist, dass dieses Sprite-Objekt eine von QGraphicsItem geerbte Klasse ist, und daher verhält sich eine Kugel, wenn sie mit einer Explosion einer anderen Kugel kollidiert, genauso wie bei Kollision mit einem Hindernis oder Ziel. Daher müssen die Kugeln Explosionen anderer Kugeln ignorieren und direkt durchfliegen.

Projektstruktur

Das Projekt wird dahingehend geändert, dass eine neue Klasse mit den folgenden Dateien hinzugefügt wird:

  • sprite.h
  • sprite.cpp

In dieser Klasse wird ein animiertes Explosions-Sprite aufgerufen, nach dessen Frames dieses Objekt aus der Grafikszene entfernt wird.


sprite.h

Im Vergleich zum Code aus der Lektion zum Verbinden von animierten Sprites in Qt ist es in dieser Lektion erforderlich, die virtuelle Funktion type(), zu überschreiben und auch die Typkennung unseres Grafikobjekts zu ersetzen, damit es unterscheidet sich von den Typkennungen anderer Grafikobjekte. Typ entspricht beispielsweise Benutzertyp + 1 anstelle von Benutzertyp.

#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

Es wird auch eine Änderung in den Quellcodes geben. Zusätzlich zum Überschreiben der type()-Funktion Wir nehmen auch eine Änderung am Slot zum Wechseln von Sprite-Frames vor. Anfangs wurde die Animationsschleife im Programmcode festgelegt. Und in diesem Fall muss nach Ablauf aller Frames das Objekt aus der Grafikszene entfernt werden, die in diesem Slot implementiert ist.

Es gibt auch eine kleine Änderung am Konstruktor der Sprite-Klasse Er fügt ein Argument hinzu, das die Position der Explosion in der Grafikszene festlegt. Das heißt, die Kugel überträgt die Explosionsstelle auf diese Klasse.

#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

In dieser Datei enthalten wir nur die Sprite-Klasse.

bullet.cpp

In der Klasse Bullet ändern wir jedoch nur einige Codezeilen. Sie müssen einen Code in der Funktion slotTimerBullet () finden, der für die Überprüfung auf Kollisionen mit anderen Objekten verantwortlich ist.

Dieser Abschnitt stellt die foreach-Schleife dar. Darin fügen wir eine Prüfung auf Kollision mit einer anderen Explosion hinzu, und wenn eine solche Kollision nicht existiert, aber eine Kollision mit einem anderen Objekt besteht, erzeugen wir eine Explosion und zerstören die Patrone.

Die Nuance dieses Codes besteht darin, dass wir qgraphicsitem_cast nicht in ein Objekt der Klasse sprite umwandeln. Da diesem Objekt beim Erstellen (UserType + 1) zugewiesen wird und QGraphicsItem eine type() Funktion hat, reicht dies völlig aus, um zu bestimmen, welches Objekt die Kugel getroffen hat.

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();
    }
}

Ergebnis - Kugelexplosion

Als Ergebnis der geleisteten Arbeit bilden sich Explosionen an den Stellen, an denen die Kugel auftrifft, wie in der Abbildung gezeigt. Außerdem können Sie im Video-Tutorial eine Demonstration sehen, wie das Spiel mit Explosionen von Kugeln funktioniert.

Link zum Projekt-Download: targetmotion.zip

Videoanleitung

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Stabiles Hosting des sozialen Netzwerks EVILEG. Wir empfehlen VDS-Hosting für Django-Projekte.

Magst du es? In sozialen Netzwerken teilen!

L
  • 16. Mai 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. Mai 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. Mai 2017 11:13

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

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

This scene takes ownership of the item.

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

L
  • 16. Mai 2017 13:52

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

Evgenii Legotckoi
  • 16. Mai 2017 14:27

Комментарии добавляются через 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, обычно их уже переписывают на новый стандарт, поскольку нужды в этом макросе уже нет.

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

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

}
Попробуйте так переписать проблемные места.

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

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

a
  • 7. Mai 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
  • 7. Mai 2018 01:40

Вбил

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

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

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

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

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

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

a
  • 10. Mai 2018 00:46

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


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

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

b
  • 26. November 2022 15:01

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

Kommentare

Nur autorisierte Benutzer können Kommentare posten.
Bitte Anmelden oder Registrieren
Letzte Kommentare
ИМ
Игорь Максимов5. Oktober 2024 07:51
Django – Lektion 064. So schreiben Sie eine Python-Markdown-Erweiterung Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55. Juli 2024 11:02
QML - Lektion 016. SQLite-Datenbank und das Arbeiten damit in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssr8. Februar 2024 18:43
Qt Linux - Lektion 001. Autorun Qt-Anwendung unter Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Qt WinAPI - Lektion 007. Arbeiten mit ICMP-Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25. Dezember 2023 10:30
Boost - statisches Verknüpfen im CMake-Projekt unter Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
Jetzt im Forum diskutieren
J
JacobFib17. Oktober 2024 03:27
добавить qlineseries в функции Пользователь может получить любые разъяснения по интересующим вопросам, касающимся обработки его персональных данных, обратившись к Оператору с помощью электронной почты https://topdecorpro.ru…
JW
Jhon Wick1. Oktober 2024 15:52
Indian Food Restaurant In Columbus OH| Layla’s Kitchen Indian Restaurant If you're looking for a truly authentic https://www.laylaskitchenrestaurantohio.com/ , Layla’s Kitchen Indian Restaurant is your go-to destination. Located at 6152 Cleveland Ave, Colu…
КГ
Кирилл Гусарев27. September 2024 09:09
Не запускается программа на Qt: точка входа в процедуру не найдена в библиотеке DLL Написал программу на C++ Qt в Qt Creator, сбилдил Release с помощью MinGW 64-bit, бинарнику напихал dll-ки с помощью windeployqt.exe. При попытке запуска моей сбилженной программы выдаёт три оши…
F
Fynjy22. Juli 2024 04:15
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …

Folgen Sie uns in sozialen Netzwerken