Evgenii Legotckoi
Evgenii Legotckoi5 октября 2015 г. 20:58

GameDev на Qt - Урок 3. Уничтожение противников

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

Уничтожение на основе CallBack функции

Для реализации данного алгоритма создадим класс мишени Target , а также добавим в класс Bullet возможность вызова CallBack функции , которая будет реализована в классе главного окна приложения и будет наносить урон мишеням.


target.h

В заголовочном файле необходимо объявить функцию, которая будет наносить урон мишени. А также объявим две переменных, которые будут отвечать за здоровье мишеней. Первая переменная - это будет текущее здоровье, а вторая переменная - это будет максимальное здоровье. Когда здоровье заканчивается, происходит уничтожение мишени.

#ifndef TARGET_H
#define TARGET_H

#include <QObject>
#include <QGraphicsItem>
#include <QPainter>

class Target : public QObject, public QGraphicsItem
{
    Q_OBJECT
public:
    explicit Target(QObject *parent = 0);
    ~Target();
    /* Функция по нанесению урона,
     * величина урона передаётся в качестве аргумента функции
     * */
    void hit(int damage);

signals:

public slots:

protected:
    QRectF boundingRect() const;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);

private:
    int health;         // Текущий запас здоровья мишени
    int maxHealth;      // Максимальный запас здоровья мишени
};

#endif // TARGET_H

target.cpp

В конструкторе класса происходит установка параметров здоровья. В функции нанесения урона происходит уменьшение здоровья на переданную в данную функцию величину. А как только здоровье падает до нуля и ниже, то мишень уничтожается.

#include "target.h"

/* Функция для получения рандомного числа
 * в диапазоне от минимального до максимального
 * */
static int randomBetween(int low, int high)
{
    return (qrand() % ((high + 1) - low) + low);
}

Target::Target(QObject *parent) :
    QObject(parent), QGraphicsItem()
{
    health = randomBetween(1,15);   // Задаём случайное значение здоровья
    maxHealth = health;             // Устанавливаем максимальное здоровье равным текущему
}

Target::~Target()
{

}

QRectF Target::boundingRect() const
{
    return QRectF(-20,-20,40,40);   // Ограничиваем область, в которой лежит цель
}

void Target::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    /* Отрисовываем зеленый квадрат
     * */
    painter->setPen(Qt::black);
    painter->setBrush(Qt::green);
    painter->drawRect(-20,-10,40,30);

    /* Отрисовываем полоску жизни
     * соизмеримо текущему здоровью
     * относительно максимального здоровья
     * */
    painter->setPen(Qt::NoPen);
    painter->setBrush(Qt::red);
    painter->drawRect(-20,-20, (int) 40*health/maxHealth,3);

    Q_UNUSED(option);
    Q_UNUSED(widget);
}

void Target::hit(int damage)
{
    health -= damage;   // Уменьшаем здоровье мишени
    this->update(QRectF(-20,-20,40,40));    // Перерисовываем мишень
    // Если здоровье закончилось, то инициируем смерть мишени
    if(health <= 0) this->deleteLater();
}

bullet.h

В класс пули из прошлого урока необходимо добавить объявления сигнатуры CallBack функции, а также функцию по установке CallBack функции.

public:
    // Установка CallBack функции
    void setCallbackFunc(void (*func) (QGraphicsItem * item));

private:
    // Объявляем сигнатуру CallBack функции
    void (*callbackFunc)(QGraphicsItem * item);

bullet.cpp

Также необходимо модифицировать функцию slotTimerBullet , в которой будет происходить поиск всех объектов, на которые наткнулась пуля. Если пуля наткнулась на объект, то уничтожаем пулю и вызываем CallBack функцию, которая будет наносить урон Мишеням, если пуля наткнулась на мишень.

Также реализуем функцию setCallbackFunc , которая произведёт установку указателя функцию на CallBack функцию .

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

void Bullet::setCallbackFunc(void (*func)(QGraphicsItem *))
{
    callbackFunc = func;
}

widget.h

В заголовочный файл класса главного окна необходимо добавить объявление таймера для создания мишеней, а также слота для обработки данного таймера, в котором и будут создавать мишени. Также объявляем static список мишеней, который мы будем проверять на попадание пули. А проверку попадания будем производить в CallBack функции slotHitTarget. В качестве аргумента будет передаваться графический объект, на который натолкнулась пуля.

private:
    QTimer *timerTarget;        // Таймер для создания мишеней
    static QList<QGraphicsItem *> targets;  // Список мишеней

    static void slotHitTarget(QGraphicsItem *item); // CallBack Функция

private slots:
    void slotCreateTarget(); // Слот для создания мишеней

widget.cpp

#include "widget.h"
#include "ui_widget.h"

/* Функция для получения рандомного числа
 * в диапазоне от минимального до максимального
 * */
static int randomBetween(int low, int high)
{
    return (qrand() % ((high + 1) - low) + low);
}

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    /* Программный код из предыдущих статей
     * */

    // Инициализируем таймер для создания мишеней
    timerTarget = new QTimer();
    connect(timerTarget, &QTimer::timeout, this, &Widget::slotCreateTarget);
    timerTarget->start(1500);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::slotBullet(QPointF start, QPointF end)
{
    /* Программный код из предыдущих уроков
     * */
}

void Widget::slotCreateTarget()
{
    Target *target = new Target();  // Создаём цель
    scene->addItem(target);         // Помещаем цель в сцену со случайной позицией
    target->setPos(qrand() % ((500 - 40 + 1) - 40) + 40,
                  qrand() % ((500 - 40 + 1) - 40) + 40);
    target->setZValue(-1);          // Помещаем цель ниже Героя
    targets.append(target);         // Добавляем цель в список
}

void Widget::slotHitTarget(QGraphicsItem *item)
{
    /* Получив сигнал от Пули
     * Перебираем весь список целей и наносим ему случайный урон
     * */
    foreach (QGraphicsItem *targ, targets) {
        if(targ == item){
            // Кастуем объект из списка в класс Target
            Target *t = qgraphicsitem_cast <Target *> (targ);
            t->hit(randomBetween(1,3)); // Наносим урон
        }
    }

}

QList<QGraphicsItem *> Widget::targets; // реализация списка

Итог

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

Видеоурок

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

Вам это нравится? Поделитесь в социальных сетях!

A
  • 6 января 2017 г. 2:58

Почему то у меня, на строке вызова callback функции, вылетает программа. ((( п.с. Qt 5.7 MSVC 64

A
  • 6 января 2017 г. 3:04

e:\work\qt_work\gamedev\les_2\mygame2\bullet.cpp:85: ошибка: Exception at 0x7ff6d35d80b3, code: 0xc0000005: read access violation at: 0x0, flags=0x0 (first chance) e:\work\qt_work\gamedev\les_2\mygame2\bullet.cpp:85: ошибка: Exception at 0x7ff6d35d80b3, code: 0xc0000005: read access violation at: 0x0, flags=0x0 Вот такой exeption вылетает при нажатии мышки (то есть при стрельбе)

A
  • 6 января 2017 г. 3:22

Нашел в чем проблема. Вообще в этих уроках, лучше выкладывать весь код каждого файла, а не только ту часть, которая отличается от предыдущего урока. В определении отличий кода между уроками, Вы делаете ошибки)))) Вот и приходится самому думать, чего еще не хватает в коде=)

A
  • 6 января 2017 г. 3:25

И так, главный вопрос по этому уроку у меня такой: зачем мы используем callback-функцию, вместо слота+сигнала?

Evgenii Legotckoi
  • 6 января 2017 г. 9:44

Наверное, это прозвучит странно, но просто так. Чтобы сделать через callback-функцию . Чтобы показать один из возможных вариантов работы в Qt/C++. Случается же так, что те, кто изучает Qt и даже работают с ним некоторое время, не имеют представления о callback-функциях.

S
  • 9 июля 2017 г. 1:14
  1. /* После чего проверяем все элементы.
  2. * Одними из них будут сама Пуля и Герой - с ними ничего не делаем.
  3. * А с остальными вызываем CallBack функцию
  4. * */
  5. foreach (QGraphicsItem *item, foundItems) {
  6. if (item == this || item == hero)
  7. continue;
  8. callbackFunc(item); // Вызываем CallBack функцию
  9. this->deleteLater(); // Уничтожаем пулю
  10. }
Проработав Ваш код появился вопрос:  кто такой - hero - в данном исполнителе. Не подключали вроде в предыдущих уроках класс треугольника...решил вопрос с помощью RTTI.
S
  • 9 июля 2017 г. 1:17

И еще для чего нужна конструкция: foreach если есть эквивалент for( : )

S
  • 9 июля 2017 г. 1:29

И к верхнему посту AndreyHudz не надо весь код выкладывать, а лучше сделать преднамеренные ошибки.

Evgenii Legotckoi
  • 9 июля 2017 г. 2:04

да, foreach - это Qt-шный макрос, который эквивалентен for, который появился позже чем foreach.
Я длительное время работал с foreach, пока не решил заняться плотнее новыми стандартами C++ :-)

Evgenii Legotckoi
  • 9 июля 2017 г. 2:07

Поэтому в пятом уроке есть исходники всего проекта )))).

Вообще, все эти материалы были не предыдущей версии сайта, которая на WordPress. Во время переноса мог что-то потерять.
Д
  • 25 октября 2018 г. 20:10

Не подскажите в чем проблема. При нажатии аварийно выходит из программы. Не могу додуматься.

Evgenii Legotckoi
  • 26 октября 2018 г. 3:17

Скачайте просто из пятого урока полностью готовый пример.

VB
  • 25 июня 2020 г. 22:35

А откуда взялся hero? Никак не могу понят секрет его происхождения...

Evgenii Legotckoi
  • 26 июня 2020 г. 0:36

Сам уже не помню. 5 лет назад говнокодил это )) В 5-й части есть полный код, думаю, что там найдёте ))

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
ДЛ

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:60баллов,
  • Очки рейтинга-1
СЦ

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50баллов,
  • Очки рейтинга-4
AT

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

  • Результат:73баллов,
  • Очки рейтинга1
Последние комментарии
J
JonnyJo30 марта 2023 г. 11:57
Qt/C++ - Урок 021. Рисование мышью в Qt Евгений, здравствуйте! Только начал изучение Qt и возник вопрос по 21ому уроку. После написания кода, выдаёт следующие ошибки В чём может быть проблема?
АН
Алексей Николаев26 марта 2023 г. 9:10
Qt/C++ - Урок 042. PopUp уведомление в стиле Gnome с помощью Qt Добрый день, взял за основу ваш PopUp notification , и немного доработал его под свои нужды. Добавил в отдельном eventloop'e всплывающую очередь уведомлений с анимацией и таймеро…
АН
Алексей Николаев26 марта 2023 г. 9:04
Qt/C++ - Урок 042. PopUp уведомление в стиле Gnome с помощью Qt Включите прозрачность в композит менеджере fly-admin-theme : fly-admin-theme ->Эффекты и всё заработает.
Evgenii Legotckoi
Evgenii Legotckoi24 марта 2023 г. 10:09
Django - Урок 062. Как написать блочный шаблонный тег tabbar наподобие тега blocktranslate Почитайте эту статью про "хлебные крошки"
Сейчас обсуждают на форуме
BlinCT
BlinCT1 апреля 2023 г. 5:16
Нужен совет по работе с ListView и несколькими моделями Спасибо, сейчас займусь этим.
NSProject
NSProject31 марта 2023 г. 2:55
Проверка комментария принадлежит он пользователю или нет DRF (Django Rest Framework) Здравствуйте! Сегодня я столкнулся с такой проблеммой. Существует модель комметариев. Где их соответственно достаточное количество. Все они выводятся при помощи запроса ajax (axios). Так ка…
P
Pisych30 марта 2023 г. 2:50
Как подсчитать количество по условию? Да! Вот так работает! Огромное Вам спасибо! ........
Evgenii Legotckoi
Evgenii Legotckoi29 марта 2023 г. 4:11
Замена поля ManyToMany Картинки точно нужно хранить в медиа директории на сервере, а для обращения использовать ImageField. Который будет хранить только путь к изображению на сервере. Хранить изображения в базе данных…
ВА
Виталий Анисимов29 января 2023 г. 15:17
Как добавить виртуальную клавиатура с Т9 в своей проект на QML. Добрый день. Прошу помочь, пишу небольше приложение в Qt. Добвил в свой проект виртуальную клавиатуру от Qt. Но как добавить в него возможность изменения Т9 никак не могу понять.

Следите за нами в социальных сетях