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

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

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

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

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


target.h

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

  1. #ifndef TARGET_H
  2. #define TARGET_H
  3.  
  4. #include <QObject>
  5. #include <QGraphicsItem>
  6. #include <QPainter>
  7.  
  8. class Target : public QObject, public QGraphicsItem
  9. {
  10. Q_OBJECT
  11. public:
  12. explicit Target(QObject *parent = 0);
  13. ~Target();
  14. /* Функция по нанесению урона,
  15. * величина урона передаётся в качестве аргумента функции
  16. * */
  17. void hit(int damage);
  18.  
  19. signals:
  20.  
  21. public slots:
  22.  
  23. protected:
  24. QRectF boundingRect() const;
  25. void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
  26.  
  27. private:
  28. int health; // Текущий запас здоровья мишени
  29. int maxHealth; // Максимальный запас здоровья мишени
  30. };
  31.  
  32. #endif // TARGET_H

target.cpp

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

  1. #include "target.h"
  2.  
  3. /* Функция для получения рандомного числа
  4. * в диапазоне от минимального до максимального
  5. * */
  6. static int randomBetween(int low, int high)
  7. {
  8. return (qrand() % ((high + 1) - low) + low);
  9. }
  10.  
  11. Target::Target(QObject *parent) :
  12. QObject(parent), QGraphicsItem()
  13. {
  14. health = randomBetween(1,15); // Задаём случайное значение здоровья
  15. maxHealth = health; // Устанавливаем максимальное здоровье равным текущему
  16. }
  17.  
  18. Target::~Target()
  19. {
  20.  
  21. }
  22.  
  23. QRectF Target::boundingRect() const
  24. {
  25. return QRectF(-20,-20,40,40); // Ограничиваем область, в которой лежит цель
  26. }
  27.  
  28. void Target::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
  29. {
  30. /* Отрисовываем зеленый квадрат
  31. * */
  32. painter->setPen(Qt::black);
  33. painter->setBrush(Qt::green);
  34. painter->drawRect(-20,-10,40,30);
  35.  
  36. /* Отрисовываем полоску жизни
  37. * соизмеримо текущему здоровью
  38. * относительно максимального здоровья
  39. * */
  40. painter->setPen(Qt::NoPen);
  41. painter->setBrush(Qt::red);
  42. painter->drawRect(-20,-20, (int) 40*health/maxHealth,3);
  43.  
  44. Q_UNUSED(option);
  45. Q_UNUSED(widget);
  46. }
  47.  
  48. void Target::hit(int damage)
  49. {
  50. health -= damage; // Уменьшаем здоровье мишени
  51. this->update(QRectF(-20,-20,40,40)); // Перерисовываем мишень
  52. // Если здоровье закончилось, то инициируем смерть мишени
  53. if(health <= 0) this->deleteLater();
  54. }

bullet.h

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

  1. public:
  2. // Установка CallBack функции
  3. void setCallbackFunc(void (*func) (QGraphicsItem * item));
  4.  
  5. private:
  6. // Объявляем сигнатуру CallBack функции
  7. void (*callbackFunc)(QGraphicsItem * item);

bullet.cpp

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

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

  1. void Bullet::slotTimerBullet()
  2. {
  3. setPos(mapToParent(0, -10));
  4.  
  5. /* Производим проверку на то, наткнулась ли пуля на какой-нибудь
  6. * элемент на графической сцене.
  7. * Для этого определяем небольшую область перед пулей,
  8. * в которой будем искать элементы
  9. * */
  10. QList<QGraphicsItem *> foundItems = scene()->items(QPolygonF()
  11. << mapToScene(0, 0)
  12. << mapToScene(-1, -1)
  13. << mapToScene(1, -1));
  14. /* После чего проверяем все элементы.
  15. * Одними из них будут сама Пуля и Герой - с ними ничего не делаем.
  16. * А с остальными вызываем CallBack функцию
  17. * */
  18. foreach (QGraphicsItem *item, foundItems) {
  19. if (item == this || item == hero)
  20. continue;
  21. callbackFunc(item); // Вызываем CallBack функцию
  22. this->deleteLater(); // Уничтожаем пулю
  23. }
  24.  
  25. /* Проверка выхода за границы поля
  26. * Если пуля вылетает за заданные границы, то пулю необходимо уничтожить
  27. * */
  28. if(this->x() < 0){
  29. this->deleteLater();
  30. }
  31. if(this->x() > 500){
  32. this->deleteLater();
  33. }
  34.  
  35. if(this->y() < 0){
  36. this->deleteLater();
  37. }
  38. if(this->y() > 500){
  39. this->deleteLater();
  40. }
  41. }
  42.  
  43. void Bullet::setCallbackFunc(void (*func)(QGraphicsItem *))
  44. {
  45. callbackFunc = func;
  46. }

widget.h

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

  1. private:
  2. QTimer *timerTarget; // Таймер для создания мишеней
  3. static QList<QGraphicsItem *> targets; // Список мишеней
  4.  
  5. static void slotHitTarget(QGraphicsItem *item); // CallBack Функция
  6.  
  7. private slots:
  8. void slotCreateTarget(); // Слот для создания мишеней

widget.cpp

  1. #include "widget.h"
  2. #include "ui_widget.h"
  3.  
  4. /* Функция для получения рандомного числа
  5. * в диапазоне от минимального до максимального
  6. * */
  7. static int randomBetween(int low, int high)
  8. {
  9. return (qrand() % ((high + 1) - low) + low);
  10. }
  11.  
  12. Widget::Widget(QWidget *parent) :
  13. QWidget(parent),
  14. ui(new Ui::Widget)
  15. {
  16. /* Программный код из предыдущих статей
  17. * */
  18.  
  19. // Инициализируем таймер для создания мишеней
  20. timerTarget = new QTimer();
  21. connect(timerTarget, &QTimer::timeout, this, &Widget::slotCreateTarget);
  22. timerTarget->start(1500);
  23. }
  24.  
  25. Widget::~Widget()
  26. {
  27. delete ui;
  28. }
  29.  
  30. void Widget::slotBullet(QPointF start, QPointF end)
  31. {
  32. /* Программный код из предыдущих уроков
  33. * */
  34. }
  35.  
  36. void Widget::slotCreateTarget()
  37. {
  38. Target *target = new Target(); // Создаём цель
  39. scene->addItem(target); // Помещаем цель в сцену со случайной позицией
  40. target->setPos(qrand() % ((500 - 40 + 1) - 40) + 40,
  41. qrand() % ((500 - 40 + 1) - 40) + 40);
  42. target->setZValue(-1); // Помещаем цель ниже Героя
  43. targets.append(target); // Добавляем цель в список
  44. }
  45.  
  46. void Widget::slotHitTarget(QGraphicsItem *item)
  47. {
  48. /* Получив сигнал от Пули
  49. * Перебираем весь список целей и наносим ему случайный урон
  50. * */
  51. foreach (QGraphicsItem *targ, targets) {
  52. if(targ == item){
  53. // Кастуем объект из списка в класс Target
  54. Target *t = qgraphicsitem_cast <Target *> (targ);
  55. t->hit(randomBetween(1,3)); // Наносим урон
  56. }
  57. }
  58.  
  59. }
  60.  
  61. QList<QGraphicsItem *> Widget::targets; // реализация списка

Итог

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

Видеоурок

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

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-й части есть полный код, думаю, что там найдёте ))

Комментарии

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