Evgenii Legotckoi
Evgenii LegotckoiҚаз. 19, 2015, 1:23 Т.Қ.

GameDev on Qt – 5-сабақ. Спрайт кескінін пайдаланып, оқтардың жарылысы

Алдыңғы мақалаларда біз спрайт кескінін салуды және оны QPixmap көмегімен Qt жүйесінде қолдануды үйрендік. пост /92/), осылайша біз анимациялық жарылыс аламыз. Ал енді дәл осы жарылысты оқ тиген жерге қою керек. Яғни, оқ жарылып кетеді.

Бұл идеяны жүзеге асыру үшін өткен сабақтағы спрайт класын қосып, оны өзгертейік. Бұл спрайт нысаны QGraphicsItem ішінен мұраланған сынып, сондықтан басқа оқтың жарылысымен соқтығысқан кезде оқ бірдей әрекет етеді. кедергіге немесе нысанаға соғу сияқты. Сондықтан оқтарды басқа оқтардың жарылыстарын елемей, ұшып өтуі қажет болады.

Жоба құрылымы

Жоба келесі файлдармен жаңа сынып қосылған деген мағынада өзгертілген:

  • sprite.h
  • sprite.cpp

Бұл сыныпта анимациялық жарылыс спрайты шақырылады, содан кейін берілген нысан графикалық көріністен жойылады.


sprite.h

Осы сабақта Qt ішіндегі анимациялық спрайттарды қосу сабағындағы кодпен салыстырғанда, біз type() виртуалды функциясын қайта анықтауымыз керек, сонымен қатар графикалық объектіміздің тип идентификаторын ол басқаша болатындай етіп ауыстыруымыз керек. басқа графикалық объектілердің тип идентификаторларынан. Мысалы, Түр UserType. орнына UserType + 1 мәніне тең болады.

#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, 1:52 Т.Қ.

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

Evgenii Legotckoi
  • Мамыр 16, 2017, 2:27 Т.Қ.

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

Е
  • Мамыр 1, 2018, 9:33 Т.Ж.

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

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

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

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

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

Е
  • Мамыр 2, 2018, 12:23 Т.Қ.

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

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

Е
  • Мамыр 2, 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) 
{

}
Попробуйте так переписать проблемные места.
Е
  • Мамыр 3, 2018, 11:36 Т.Ж.

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

Е
  • Мамыр 5, 2018, 1:06 Т.Ж.

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

a
  • Мамыр 7, 2018, 1: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, 2018, 1:40 Т.Ж.

Вбил

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

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

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

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

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

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

a
  • Мамыр 10, 2018, 12:46 Т.Ж.

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


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

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

b
  • Қар. 26, 2022, 3:01 Т.Қ.

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

Пікірлер

Тек рұқсаты бар пайдаланушылар ғана пікір қалдыра алады.
Кіріңіз немесе Тіркеліңіз
Г

C++ - Тест 001. Первая программа и типы данных

  • Нәтиже:66ұпай,
  • Бағалау ұпайлары-1
t

C++ - Тест 001. Первая программа и типы данных

  • Нәтиже:33ұпай,
  • Бағалау ұпайлары-10
t

Qt - Тест 001. Сигналы и слоты

  • Нәтиже:52ұпай,
  • Бағалау ұпайлары-4
Соңғы пікірлер
G
GoattRockҚыр. 3, 2024, 1:50 Т.Қ.
Linux жүйесінде файлдарды қалай көшіруге болады Задумывались когда-нибудь о том, как мы привыкли доверять свои вещи службам грузоперевозок? Сейчас такие услуги стали неотъемлемой частью нашей жизни, особенно когда речь идет о переездах между …
d
dblas5Шілде 5, 2024, 11:02 Т.Ж.
QML - Сабақ 016. SQLite деректер қоры және онымен QML Qt-та жұмыс істеу Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssrАқп. 8, 2024, 6:43 Т.Қ.
Qt Linux - Сабақ 001. Linux астында Autorun Qt қолданбасы как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий КононенкоАқп. 5, 2024, 1:50 Т.Ж.
Qt WinAPI - Сабақ 007. Qt ішінде ICMP Ping арқылы жұмыс істеу Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
Енді форумда талқылаңыз
Evgenii Legotckoi
Evgenii LegotckoiМаусым 24, 2024, 3:11 Т.Қ.
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
F
FynjyШілде 22, 2024, 4:15 Т.Ж.
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …
BlinCT
BlinCTМаусым 25, 2024, 1 Т.Ж.
Нарисовать кривую в qml Всем привет. Имеется Лист листов с тосками, точки получаны интерполяцией Лагранжа. Вопрос, как этими точками нарисовать кривую? ChartView отпадает сразу, в qt6.7 появился новый элемент…
BlinCT
BlinCTМамыр 5, 2024, 5:46 Т.Ж.
Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
Evgenii Legotckoi
Evgenii LegotckoiМамыр 2, 2024, 2:07 Т.Қ.
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.

Бізді әлеуметтік желілерде бақылаңыз