Evgenii Legotckoi
05 жовтня 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;
}

віджет.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; // реализация списка

Підсумок

В результаті у Вас на ігровому полі будуть випадково розміщуватися мішені з випадковим розміром здоров'я, а кулі, що випускаються головним героєм, будуть поступово їх знищувати. У відеоуроці продемонстровано роботу програми, а також додатково подано коментарі за програмним кодом.

Відеоурок

Вам це подобається? Поділіться в соціальних мережах!

A
  • 06 січня 2017 р. 02:58

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

A
  • 06 січня 2017 р. 03: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
  • 06 січня 2017 р. 03:22

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

A
  • 06 січня 2017 р. 03:25

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

Evgenii Legotckoi
  • 06 січня 2017 р. 09:44

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

S
  • 09 липня 2017 р. 01: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
  • 09 липня 2017 р. 01:17

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

S
  • 09 липня 2017 р. 01:29

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

Evgenii Legotckoi
  • 09 липня 2017 р. 02:04

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

Evgenii Legotckoi
  • 09 липня 2017 р. 02:07

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

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

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

Evgenii Legotckoi
  • 26 жовтня 2018 р. 03:17

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

VB
  • 25 червня 2020 р. 22:35

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

Evgenii Legotckoi
  • 26 червня 2020 р. 00:36

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

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up