Evgenii Legotckoi
Evgenii Legotckoi5 октября 2015 г. 10: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
  • 5 января 2017 г. 15:58

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

A
  • 5 января 2017 г. 16: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
  • 5 января 2017 г. 16:22

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

A
  • 5 января 2017 г. 16:25

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

Evgenii Legotckoi
  • 5 января 2017 г. 22:44

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

S
  • 8 июля 2017 г. 15: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
  • 8 июля 2017 г. 15:17

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

S
  • 8 июля 2017 г. 15:29

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

Evgenii Legotckoi
  • 8 июля 2017 г. 16:04

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

Evgenii Legotckoi
  • 8 июля 2017 г. 16:07

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

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

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

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

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

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

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

Evgenii Legotckoi
  • 25 июня 2020 г. 14:36

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

Комментарии

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

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

  • Результат:50баллов,
  • Очки рейтинга-4
m
  • molni99
  • 26 октября 2024 г. 15:37

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

  • Результат:80баллов,
  • Очки рейтинга4
m
  • molni99
  • 26 октября 2024 г. 15:29

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

  • Результат:20баллов,
  • Очки рейтинга-10
Последние комментарии
ИМ
Игорь Максимов23 ноября 2024 г. 2:51
Django - Урок 017. Кастомизированная страница авторизации на Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi1 ноября 2024 г. 4:37
Django - Урок 064. Как написать расширение для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 октября 2024 г. 22:19
Читалка fb3-файлов на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов5 октября 2024 г. 21:51
Django - Урок 064. Как написать расширение для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas56 июля 2024 г. 1:02
QML - Урок 016. База данных SQLite и работа с ней в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Сейчас обсуждают на форуме
Evgenii Legotckoi
Evgenii Legotckoi25 июня 2024 г. 5:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 ноября 2024 г. 21:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject4 июня 2022 г. 17:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9Anonim25 октября 2024 г. 23:10
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

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