- 1. Уничтожение на основе CallBack функции
- 2. target.h
- 3. target.cpp
- 4. bullet.h
- 5. bullet.cpp
- 6. widget.h
- 7. widget.cpp
- 8. Итог
- 9. Видеоурок
В двух предыдущих статьях, где мы научили героя отслеживать перемещения курсора и стрелять в направлении цели , настало время добавить в игру мишени и начать их уничтожать. Уничтожение мишеней будет происходить тогда, когда у мишеней закончится жизнь. При этом у каждой из мишеней будет случайное количество очков жизни, а каждая пуля будет наносить случайное количество урона. Также у каждой мишени будет находиться полоска жизни, которая будет уменьшаться при нанесении урона.
Уничтожение на основе 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; // реализация списка
Итог
В результате у Вас на игровом поле будут случайно размещаться мишени со случайным размером здоровья, а выпускаемые главным героем пули будут постепенно их уничтожать. В видеоуроке продемонстрирована работа приложения, а также дополнительно даны комментарии по программному коду.
Почему то у меня, на строке вызова callback функции, вылетает программа. ((( п.с. Qt 5.7 MSVC 64
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 вылетает при нажатии мышки (то есть при стрельбе)
Нашел в чем проблема. Вообще в этих уроках, лучше выкладывать весь код каждого файла, а не только ту часть, которая отличается от предыдущего урока. В определении отличий кода между уроками, Вы делаете ошибки)))) Вот и приходится самому думать, чего еще не хватает в коде=)
И так, главный вопрос по этому уроку у меня такой: зачем мы используем callback-функцию, вместо слота+сигнала?
Наверное, это прозвучит странно, но просто так. Чтобы сделать через callback-функцию . Чтобы показать один из возможных вариантов работы в Qt/C++. Случается же так, что те, кто изучает Qt и даже работают с ним некоторое время, не имеют представления о callback-функциях.
И еще для чего нужна конструкция: foreach если есть эквивалент for( : )
И к верхнему посту AndreyHudz → AndreyHudz не надо весь код выкладывать, а лучше сделать преднамеренные ошибки.
да, foreach - это Qt-шный макрос, который эквивалентен for, который появился позже чем foreach.
Я длительное время работал с foreach, пока не решил заняться плотнее новыми стандартами C++ :-)
Поэтому в пятом уроке есть исходники всего проекта )))).
Не подскажите в чем проблема. При нажатии аварийно выходит из программы. Не могу додуматься.
Скачайте просто из пятого урока полностью готовый пример.
А откуда взялся hero? Никак не могу понят секрет его происхождения...
Сам уже не помню. 5 лет назад говнокодил это )) В 5-й части есть полный код, думаю, что там найдёте ))