- 1. Source code of tutorial
- 2. apple.h
- 3. apple.cpp
- 4. triangle.h
- 5. triangle.cpp
- 6. widget.h
- 7. widget.cpp
- 8. Result
- 9. Video
Once the game has animated fly, which moves under the influence of the keyboard shortcuts, it is time to add meaning to the game. Add goal Fly, for example, it will eat the apples that will be counted. That is, you must configure the Flies interaction with other objects, in this case apples.
For apples, you must create a separate class, which will also be otnasledovan from QGraphicsItem . When creating the apples must have a random color, which will also be initialized in the class constructor using apples qrand() function.
When fly stumbles upon an apple, it is necessary to transmit the information to the core of the game, which is in the widget class. To do this, we analyze the motion flies objects that fall within its field of view, and which is necessary to work out the interaction.
/* Checks for whether the fly bumped into any graphic element on the stage. * To do this, we define a small area in front of the fly, which will search for items * */ QList<QGraphicsItem *> foundItems = scene()->items(QPolygonF() << mapToScene(0, 0) << mapToScene(-20, -20) << mapToScene(20, -20)); /* Then we check all the elements. One of them will fly itself - do not do anything with it. * And send signal to the game core with another objects * */ foreach (QGraphicsItem *item, foundItems) { if (item == this) continue; emit signalCheckItem(item); }
As soon as the fly has found the object, other than her own, then we send it to the core of the game, where the game will check that the object is an apple and remove it by increasing the score of the game at one.
void Widget::slotDeleteApple(QGraphicsItem *item) { /* Upon receiving the signal from the flies, * we check the entire list of apples and remove the apple found * */ foreach (QGraphicsItem *apple, apples) { if(apple == item){ scene->removeItem(apple); apples.removeOne(item); delete apple; ui->lcdNumber->display(count++); } } }
The creation of the apples, which will be the interaction should be administered with a certain frequency, it will be initialized to a special timer.
timerCreateApple = new QTimer(); connect(timerCreateApple, &QTimer::timeout, this, &Widget::slotCreateApple); timerCreateApple->start(1000); connect(triangle, &Triangle::signalCheckItem, this, &Widget::slotDeleteApple);
Creating an apple produced in function slotCreateApple()
void Widget::slotCreateApple() { Apple *apple = new Apple(); scene->addItem(apple); apple->setPos((qrand() % (251)) * ((qrand()%2 == 1)?1:-1), (qrand() % (251)) * ((qrand()%2 == 1)?1:-1)); apple->setZValue(-1); apples.append(apple); }
Source code of tutorial
In this lesson, make changes in the code previous of previous lessons.
apple.h
#ifndef APPLE_H #define APPLE_H #include <QObject> #include <QGraphicsItem> #include <QGraphicsScene> #include <QPainter> class Apple : public QObject, public QGraphicsItem { Q_OBJECT public: explicit Apple(QObject *parent = 0); ~Apple(); signals: public slots: protected: QRectF boundingRect() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); private: int color; }; #endif // APPLE_H
apple.cpp
#include "apple.h" Apple::Apple(QObject *parent) : QObject(parent), QGraphicsItem() { color = qrand() % ((3 + 1) - 1) + 1; } Apple::~Apple() { } QRectF Apple::boundingRect() const { return QRectF(-20,-20,40,40); } void Apple::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { QPainterPath path(QPointF(0,-10)); path.cubicTo(0,-10, -5,-14, -12,-8); path.cubicTo(-12,-8, -20,12, -10,15); path.cubicTo(-10,15, -5,20, 0,16); path.cubicTo(0,16, 5,20, 10,15); path.cubicTo(10,15, 20,12, 12,-8); path.cubicTo(12,-8, 5,-14, 0,-10); /* Choose an apple color randomly generated number * */ switch (color) { case 1: painter->setBrush(Qt::red); break; case 2: painter->setBrush(Qt::green); break; case 3: painter->setBrush(Qt::yellow); break; } painter->drawPath(path); // apple tail painter->setPen(QPen(Qt::black, 2)); QPainterPath path2(QPointF(0,-10)); path2.cubicTo(0,-10,4,-18,10,-20); painter->setBrush(Qt::NoBrush); painter->drawPath(path2); // apple leaf painter->setPen(QPen(Qt::black, 1)); QPainterPath path3(QPointF(0,-10)); path3.cubicTo(0,-10,-2,-20,-15,-20); path3.cubicTo(-15,-20,-14,-12,0,-10); painter->setBrush(Qt::green); painter->drawPath(path3); Q_UNUSED(option); Q_UNUSED(widget); }
triangle.h
You must also add the signal to the header flies, which will be transmitted in the core of the game objects that fly encountered.
signals: void signalCheckItem(QGraphicsItem *item);
triangle.cpp
In this class, only changes are made to the function slot, which is responsible for processing the signal from the game timer.
void Triangle::slotGameTimer() { /* The code from the previous lesson */ /* Checks for whether the fly bumped into any graphic element on the stage. * To do this, we define a small area in front of the fly, which will search for items * */ QList<QGraphicsItem *> foundItems = scene()->items(QPolygonF() << mapToScene(0, 0) << mapToScene(-20, -20) << mapToScene(20, -20)); /* Then we check all the elements. One of them will fly itself - do not do anything with it. * And send signal to the game core with another objects * */ foreach (QGraphicsItem *item, foundItems) { if (item == this) continue; emit signalCheckItem(item); } /* The code from the previous lesson */ }
widget.h
The core of the game evolves and becomes more complex. At this time you need to add a timer to periodically create apples in the game list, which will be stored all the apples and counter points. And also you need to add slots to create and delete apples.
private: QTimer *timerCreateApple; // Таймер для периодического создания яблок в игре QList<QGraphicsItem *> apples; // List of all the apples that are present in the game double count; // The variable that stores the score game private slots: void slotDeleteApple(QGraphicsItem * item); void slotCreateApple(); // The slot for the creation of apples, triggered by a timer
widget.cpp
#include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); this->resize(600,640); this->setFixedSize(600,640); count = 0; scene = new QGraphicsScene(); triangle = new Triangle(); ui->graphicsView->setScene(scene); ui->graphicsView->setRenderHint(QPainter::Antialiasing); ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scene->setSceneRect(-250,-250,500,500); scene->addItem(triangle); triangle->setPos(0,0); timer = new QTimer(); connect(timer, &QTimer::timeout, triangle, &Triangle::slotGameTimer); timer->start(1000 / 100); /* Once per second, sends a signal to create an apple in the game * */ timerCreateApple = new QTimer(); connect(timerCreateApple, &QTimer::timeout, this, &Widget::slotCreateApple); timerCreateApple->start(1000); /* Connect the signal from the flies, in which the transmitting entity, which stumbled Fly * */ connect(triangle, &Triangle::signalCheckItem, this, &Widget::slotDeleteApple); } Widget::~Widget() { delete ui; } void Widget::slotDeleteApple(QGraphicsItem *item) { /* Upon receiving the signal from the flies, * we check the entire list of apples and remove the apple found * */ foreach (QGraphicsItem *apple, apples) { if(apple == item){ scene->removeItem(apple); apples.removeOne(item); delete apple; ui->lcdNumber->display(count++); } } } void Widget::slotCreateApple() { Apple *apple = new Apple(); scene->addItem(apple); apple->setPos((qrand() % (251)) * ((qrand()%2 == 1)?1:-1), (qrand() % (251)) * ((qrand()%2 == 1)?1:-1)); apple->setZValue(-1); apples.append(apple); }
Result
As a result, you have to be a graphic scene of accident occur apples that would eat the fly, as well as the amount eaten apples will be counted in the counter.
A full list of articles in this series:
- Lesson 1. How to make game using Qt. Control of object
- Lesson 2. How to make game using Qt. Animation game hero (2D)
- Lesson 3. How to make game using Qt. Interaction with other objects
- Lesson 4. How to make game using Qt. Enemy – meaning in the survival
- Lesson 5. How to make game using Qt. Adding sound QMediaPlayer
Классно пишешь! Мне прям нравится правильная подача "логики" ООП! Легкая корректировка: apples.append(apple); // Добавляем яблоко в Список И вопрос: foreach (QGraphicsItem *item, foundItems) { if (item == this) continue; emit signalCheckItem(item); } где формируется указатель item?
В данном случае он формируется в следующем участке кода:
Класс Apple является наследником QGraphicsItem
И еще пару вопросов! =) Почему именно в .h файле устанавливается указатель на родителя предка? Почему ты использовал ключевое слово explicit?
Почему указатель на предка передается в QObject? Почему мы обделили QGraphicsItem?excplicit - убирает возможность неявного преобразования. Неявные преобразования вообще могут бед наделать, лучше отключать такую возможность для конструкторов сложных классов.
Установка указателя на предка в конструкторе - это стандартная практика для Qt фреймворка. Указатель на parent присутствует в конструкторах всех классов, которые наследованы от базового класса QObject.
QGraphicsItem - является одним из тех немногих классов, которые не наследованы от QObject. Поэтому приходится применять множественное наследование, чтобы использовать систему сигналов и слотов, которая работает только с теми классами, которые были наследованы от QObject. Соответственно parent передаётся в базовый класс QObject.
https://www.slideshare.net/IgorShkulipa/c-stl-qt-03 исходя из 6 слайда: "Инструментарий спроктирован так, что для QObject и всех его потомков конструктор копирования и оператор присваивания недоступны- они объявлены в раделе private через макрос Q_DISABLE_COPY;
Только вот зачем оно нужно? (или не заморачиваться и все время писать explicit?)
Если делать неявное преобразование, как вы его показали, то да, работать не будет:
А вот если использовать новомодный синтаксис из более свежих стандартов:
И Q_DISABLE_COPY здесь не спасает на данный момент, он условно только "отключает копирование". А excplicit выключит вариант неявного преобразования с новым синтаксисом.
А вот в чем смысл этих всех запретов?
На мой взгляд, неявные преобразования могут носить достаточно непредсказуемый характер в зависимости от компиляторов.
На Qt/С++ я конечно давно не сталкивался с проблемами неявных преобразований, видимо сказывается наличие практики этих самых запретов и ограничений. Но когда занимался разработкой на Си/С++ под AVR и STM32, то там эти преобразования крови много попортили.
Вообще, в чём смысл всех запретов? - Контроль и единообразие, чтобы снизить хаос в проекте.
Я все-таки не понимаю с item! В этой функции то понятно- item передается как аргумент.
А вот тут: Как foreach понимает какой из указателей QGraphicsItem он сейчас будет перебирать?Вопрос снят.
foreach перебирает все указатели, которые находятся в контейнере foundItems
foreach абсолютно всё равно, какие в контейнере классы, главное, чтобы у них один общий базовый класс, в данном случае - это QGraphicsItem . То есть: QGraphicsItem можно скастовать в Apple или в Triangle , поскольку они наследованы от QGraphicsItem, просто если класс изначально задавался как Apple, его не получится скастовать из QGraphicsItem в Trianle. Это возможно, благодаря парадигме полиморфизма
А сам по себе foreach можете перебирать все объекты в контейнере QList, поскольку данный контейнер имеет итератор. Не было бы итератора, то и не смог бы перебирать.
P/S/ для вставки программного кода в комментарий используйте, пожалуйста, специальный диалог для вставки кода. Он вызывается кнопочкой с иконкой "{}" на тулбаре редактора комментариев. Также рядом есть кнопочка предварительного просмотра комментария.
И еще вопрос: А когда именно вызывается метод paint? Ну понятно что по идее- при столкновении, по таймеру при перерисовке. Я то сначала думал, что он вызывается после метода setRotation() или setPos(). Стал смотреть в отладчике, а нифига!
Метод update() вызывает принудительную перерисовку, также вызывается по некоторым методам, таким как setX(), setY() во внутренностях Qt. Некоторые методы, наподобие paint, вызываются в порядке внутренней очереди. Здесь уже нужно смотреть исходники Qt.
у меня такая проблема .делал все как на видео все понятно .но выдает ошибку ошибка: 'Apple' was not declared in this scope ошибка: 'apple' was not declared in this scope ошибка: expected type-specifier before 'Apple' : ошибка: expected ';' before 'Apple' ругается на одну строчку 4 раза вот эта строчка Apple *apple = new Apple(); // Создаём яблоко
http://imglink.ru/show-image.php?id=709c66221545c26eec66d6586f2b7ff1 вот скриншот
Когда у меня подобного рода "лабуда" я делаю следующией действия: 1. пересобираю проект 2. Удаляю папку debug и релиз, все make и qmake файлы 3. Перезапускаю qt Таких вот "чудес" раз по 5 на день. + теневую сборку отключаю
Скорее всего заголовочный файл не подключён в файле widget.cpp.
Спасибо .чет не заметил что не подключил
При столкновении объектов отсылается сигнал signalCheckItem, который содержит указатель на объект. В ядре игры проводится проверка. Если объект == apple, то выполняется удаление объекта.
Зачем нам нужен список с указателями на все яблоки? Почему бы в ядре не выполнять код: Еще вопрос. Зачем вызывать функцию removeItem, если вызывается оператор delete?Подумайте немного над этим кодом, что вы привели, откуда вы возьмёте объект apple в вашем варианте кода, кроме как не из списка с указателями на яблоки? С данной архитектурой программы необходимо наличие списка с указателями на яблоки.
Что касается удаления, ну примените вы delete. И получите протухший указатель в самой графической сцене. Всё и везде чистить нужно.
А если применить приведение типов? Enemy01 *itemEnemy01 = dynamic_cast
(item);
Вот теперь, это будет правильнее. А теперь ответьте сами себе на вопрос. Много ли начинающих программистов, которые прочитали эту статью разбираются в приведении типов и множестве других нюансов? Вообще, можно было сделать удаление этих объектов и внутри графической сцены. И при этом стоит воспользоваться соответствующим кастом из Qt. То есть qgraphicsitem_cast и т.д. Или вообще создать специальный базовый класс, который будет иметь общий базовый метод, который будет возвращать тип объекта и уже по типу объекта принимать решение об удалении и т.д. Поскольку, если программа имеет слишком много кастов, то это тоже не очень хорошо и говорит о непродуманной архитектуре классов.