- 1. Програмний код уроку
- 2. apple.h
- 3. apple.cpp
- 4. трикутник.h
- 5. triangle.cpp
- 6. віджет.h
- 7. widget.cpp
- 8. Підсумок
- 9. Відеоурок
Після того, як у грі є анімована Муха , яка пересувається під впливом клавіш клавіатури, настав час додати сенс у гру. Додамо мету Мусі , наприклад, вона поїдатиме яблука, які будуть підраховуватися. Тобто необхідно налаштувати взаємодію Мухи з іншими об'єктами, у разі з яблуками.
Для яблук необхідно створити окремий клас, який також буде успадкований від QGraphicsItem. При створенні яблука повинні мати випадковий колір, що також ініціалізуватиметься в конструкторі класу яблук за допомогою функції qrand() .
Коли Муха натикається на яблуко, необхідно передавати цю інформацію в ядро гри, яке знаходиться в класі widget. Для цього будемо під час руху Мухи аналізувати об'єкти, які потрапляють у поле її зору, та з якими необхідно відпрацювати взаємодію.
- /* Производим проверку на то, наткнулась ли муха на какой-нибудь
- * элемент на графической сцене.
- * Для этого определяем небольшую область перед мухой,
- * в которой будем искать элементы
- * */
- QList<QGraphicsItem *> foundItems = scene()->items(QPolygonF()
- << mapToScene(0, 0)
- << mapToScene(-20, -20)
- << mapToScene(20, -20));
- /* После чего проверяем все элементы.
- * Один из них будет сама Муха - с ней ничего не делаем.
- * А с остальными высылаем сигнал в ядро игры
- * */
- foreach (QGraphicsItem *item, foundItems) {
- if (item == this)
- continue;
- emit signalCheckItem(item);
- }
Як тільки Муха виявила об'єкт, відмінний від неї самої, передаємо його в ядро гри, де гра перевірить, що об'єкт є яблуком і видалить його, збільшивши рахунок гри на одиницю.
- void Widget::slotDeleteApple(QGraphicsItem *item)
- {
- /* Получив сигнал от Мухи
- * Перебираем весь список яблок и удаляем найденное яблоко
- * */
- foreach (QGraphicsItem *apple, apples) {
- if(apple == item){
- scene->removeItem(apple); // Удаляем со сцены
- apples.removeOne(item); // Удаляем из списка
- delete apple; // Вообще удаляем
- ui->lcdNumber->display(count++); /* Увеличиваем счёт на единицу
- * и отображаем на дисплее
- * */
- }
- }
- }
Створення ж яблук, із якими відбуватиметься взаємодія має здійснюватися з певною періодичністю, що ініціалізуватиметься спеціальним таймером.
- /* Раз в секунду отсылаем сигнал на создание яблока в игре
- * */
- timerCreateApple = new QTimer();
- connect(timerCreateApple, &QTimer::timeout, this, &Widget::slotCreateApple);
- timerCreateApple->start(1000);
- /* Подключаем сигнал от Мухи, в котором передаются Объекты, на которые
- * наткнулась Муха
- * */
- connect(triangle, &Triangle::signalCheckItem, this, &Widget::slotDeleteApple);
Створення яблука виконується у функції 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); // Добавляем Яблоко в Список
- }
Програмний код уроку
У цьому уроці вносяться зміни до програмного коду попередніх уроків.
Після того, як описаний процес взаємодії, наводжу повний програмний код класу, який відповідає за створення яблук.
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);
- /* Выбираем цвет яблока по случайно сгенерированному номеру
- * */
- 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);
- // Хвостик яблока
- 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);
- //Листик яблока
- 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);
- }
трикутник.h
Також необхідно додати сигнал до заголовкового файлу Мухи , який передаватиме в ядро гри об'єкти, на які натрапила Муха.
- signals:
- /* Сигнал, который передаётся в ядро игры с элементом QGraphicsItem,
- * на который наткнулась муха, и требуется принять решение о том,
- * что с этим элементом делать.
- * */
- void signalCheckItem(QGraphicsItem *item);
triangle.cpp
У цьому класі зміни вносяться лише у функцію-слот, що відповідає обробку сигналу від ігрового таймера.
- void Triangle::slotGameTimer()
- {
- /* Проверяем, нажата ли была какая-либо из кнопок управления объектом.
- * Прежде чем считать шажки
- * */
- /* Программный код из прошлого урока */
- /* Производим проверку на то, наткнулась ли муха на какой-нибудь
- * элемент на графической сцене.
- * Для этого определяем небольшую область перед мухой,
- * в которой будем искать элементы
- * */
- QList<QGraphicsItem *> foundItems = scene()->items(QPolygonF()
- << mapToScene(0, 0)
- << mapToScene(-20, -20)
- << mapToScene(20, -20));
- /* После чего проверяем все элементы.
- * Один из них будет сама Муха - с ней ничего не делаем.
- * А с остальными высылаем сигнал в ядро игры
- * */
- foreach (QGraphicsItem *item, foundItems) {
- if (item == this)
- continue;
- emit signalCheckItem(item);
- }
- /* Проверка выхода за границы поля
- * Если объект выходит за заданные границы, то возвращаем его назад
- * */
- /* Программный код из прошлого урока */
- }
віджет.h
Ядро гри розвивається та ускладнюється. На цей раз необхідно додати таймер для періодичного створення яблук у грі, Список, в якому зберігатимуться всі яблука та лічильник очок. А також необхідно додати слоти для створення та видалення яблук.
- private:
- QTimer *timerCreateApple; // Таймер для периодического создания яблок в игре
- QList<QGraphicsItem *> apples; // Список со всеми яблоками, присутствующими в игре
- double count; // Переменная, которая хранит счёт игре
- private slots:
- // Слот для удаления яблок если Муха наткнулая на это яблоко
- void slotDeleteApple(QGraphicsItem * item);
- void slotCreateApple(); // Слот для создания яблок, срабатывает по таймеру
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); /// Устанавливаем графическую сцену в graphicsView
- 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); /// Устанавливаем треугольник в центр сцены
- /* Инициализируем таймер и вызываем слот обработки сигнала таймера
- * у Треугольника 100 раз в секунду.
- * Управляя скоростью отсчётов, соответственно управляем скоростью
- * изменения состояния графической сцены
- * */
- timer = new QTimer();
- connect(timer, &QTimer::timeout, triangle, &Triangle::slotGameTimer);
- timer->start(1000 / 100);
- /* Раз в секунду отсылаем сигнал на создание яблока в игре
- * */
- timerCreateApple = new QTimer();
- connect(timerCreateApple, &QTimer::timeout, this, &Widget::slotCreateApple);
- timerCreateApple->start(1000);
- /* Подключаем сигнал от Мухи, в котором передаются Объекты, на которые
- * наткнулась Муха
- * */
- connect(triangle, &Triangle::signalCheckItem, this, &Widget::slotDeleteApple);
- }
- Widget::~Widget()
- {
- delete ui;
- }
- void Widget::slotDeleteApple(QGraphicsItem *item)
- {
- /* Получив сигнал от Мухи
- * Перебираем весь список яблок и удаляем найденное яблоко
- * */
- 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); // Добавляем Яблоко в Список
- }
Підсумок
В результаті у Вас на графічній сцені будуть випадково виникати яблука, які поїдатиме Муха , а також кількість з'їдених яблук враховуватиметься в лічильнику.
Також у відеоуроці за цією статтею дано додаткові коментарі та продемонстровано роботу гри.
Повний список статей цього циклу:
- Урок 1. Як написати гру на Qt. Управління об'єктом
- Урок 2. Як написати гру на Qt. Анімація героя ігри (2D)
- Урок 3. Як написати гру на Qt. Взаємодія з іншими об'єктами
- Урок 4. Як написати гру на Qt. Ворог - сенс у виживанні
- Урок 5. Як написати гру на Qt. Додаємо звук з 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 и т.д. Или вообще создать специальный базовый класс, который будет иметь общий базовый метод, который будет возвращать тип объекта и уже по типу объекта принимать решение об удалении и т.д. Поскольку, если программа имеет слишком много кастов, то это тоже не очень хорошо и говорит о непродуманной архитектуре классов.