- 1. Programmiercode der Lektion
- 2. apfel.h
- 3. apple.cpp
- 4. dreieck.h
- 5. Dreieck.cpp
- 6. widget.h
- 7. widget.cpp
- 8. Ergebnis
- 9. Videoanleitung
Nachdem das Spiel eine animierte Fly hat, die sich unter dem Einfluss von Tastaturtasten bewegt, ist es an der Zeit, dem Spiel Bedeutung zu verleihen. Fügen wir Fly ein Ziel hinzu, zum Beispiel frisst es Äpfel, die gezählt werden. Das heißt, es ist notwendig, die Interaktion von Flies mit anderen Objekten, in diesem Fall mit Äpfeln, einzurichten.
Für Äpfel müssen Sie eine separate Klasse erstellen, die auch von QGraphicsItem erbt. Äpfel müssen beim Erstellen eine zufällige Farbe haben, die auch im Konstruktor der Apfelklasse mit dem qrand ( ) Funktion.
Wenn die Fliege über einen Apfel stolpert, ist es notwendig, diese Informationen in den Kern des Spiels zu übertragen, der in der Widget-Klasse liegt. Um dies zu tun, wenn Fliege sich bewegt, wir analysiert Objekte, die in sein Blickfeld fallen und aus denen die Interaktion herausgearbeitet werden muss.
/* Производим проверку на то, наткнулась ли муха на какой-нибудь * элемент на графической сцене. * Для этого определяем небольшую область перед мухой, * в которой будем искать элементы * */ 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); }
Sobald Fly ein Objekt gefunden hat, das sich von ihm unterscheidet, übertragen wir es in den Kern des Spiels, wo das Spiel überprüft, ob das Objekt ein Apfel ist, und es entfernt, wodurch der Spielstand um eins erhöht wird .
void Widget::slotDeleteApple(QGraphicsItem *item) { /* Получив сигнал от Мухи * Перебираем весь список яблок и удаляем найденное яблоко * */ foreach (QGraphicsItem *apple, apples) { if(apple == item){ scene->removeItem(apple); // Удаляем со сцены apples.removeOne(item); // Удаляем из списка delete apple; // Вообще удаляем ui->lcdNumber->display(count++); /* Увеличиваем счёт на единицу * и отображаем на дисплее * */ } } }
Die Erzeugung von Äpfeln, mit denen die Interaktion stattfindet, muss in einer bestimmten Häufigkeit erfolgen, die durch einen speziellen Timer initialisiert wird.
/* Раз в секунду отсылаем сигнал на создание яблока в игре * */ timerCreateApple = new QTimer(); connect(timerCreateApple, &QTimer::timeout, this, &Widget::slotCreateApple); timerCreateApple->start(1000); /* Подключаем сигнал от Мухи, в котором передаются Объекты, на которые * наткнулась Муха * */ connect(triangle, &Triangle::signalCheckItem, this, &Widget::slotDeleteApple);
Der Apfel wird in der slotCreateApple() Funktion erstellt
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); // Добавляем Яблоко в Список }
Programmiercode der Lektion
In dieser Lektion werden Änderungen am Programmcode der vorherigen Lektionen aus den vorherigen Lektionen vorgenommen.
Nachdem der Interaktionsprozess beschrieben ist, präsentiere ich den kompletten Programmcode der Klasse, die für das Erstellen von Äpfeln verantwortlich ist.
apfel.h
In dieser Datei wurde nur eine Variable hinzugefügt, die verantwortlich ist für
#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); }
dreieck.h
Es ist auch erforderlich, der Header-Datei Fly ein Signal hinzuzufügen, das Objekte, mit denen Fly kollidierte, in den Kern des Spiels überträgt.
signals: /* Сигнал, который передаётся в ядро игры с элементом QGraphicsItem, * на который наткнулась муха, и требуется принять решение о том, * что с этим элементом делать. * */ void signalCheckItem(QGraphicsItem *item);
Dreieck.cpp
In dieser Klasse werden nur Änderungen an der Slot-Funktion vorgenommen, die für die Verarbeitung des Signals des Spieltimers zuständig ist.
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); } /* Проверка выхода за границы поля * Если объект выходит за заданные границы, то возвращаем его назад * */ /* Программный код из прошлого урока */ }
widget.h
Der Kern des Spiels entwickelt sich weiter und wird komplexer. Diesmal müssen Sie einen Timer für das regelmäßige Erstellen von Äpfeln im Spiel, eine Liste, in der alle Äpfel gespeichert werden, und einen Punktezähler hinzufügen. Außerdem müssen Sie Slots zum Erstellen und Entfernen von Äpfeln hinzufügen.
private: QTimer *timerCreateApple; // Таймер для периодического создания яблок в игре QList<QGraphicsItem *> apples; // Список со всеми яблоками, присутствующими в игре double count; // Переменная, которая хранит счёт игре private slots: // Слот для удаления яблок если Муха наткнулая на это яблоко void slotDeleteApple(QGraphicsItem * item); void slotCreateApple(); // Слот для создания яблок, срабатывает по таймеру
widget.cpp
Wir implementieren Slot-Funktionen und richten auch Verbindungen zum Arbeiten mit Objekten im Spiel ein.
#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); // Добавляем Яблоко в Список }
Ergebnis
Als Ergebnis erscheinen zufällig Äpfel in der Grafikszene, die die Fliege frisst, und die Anzahl der gegessenen Äpfel wird im Zähler gezählt.
Auch im Video-Tutorial zu diesem Artikel werden zusätzliche Kommentare gegeben und die Bedienung des Spiels demonstriert.
Vollständige Liste der Artikel dieser Serie:
- Lektion 1. Wie man ein Spiel in Qt schreibt. Objektverwaltung
- Lektion 2. Wie man ein Spiel in Qt schreibt. Animation des Spielhelden (2D)
- Lektion 3. Wie man ein Spiel in Qt schreibt. Interaktion mit anderen Objekten
- Lektion 4. Wie man ein Spiel in Qt schreibt. Der Feind ist der Sinn des Überlebens
- Lektion 5. Wie man ein Spiel in Qt schreibt. Ton von QMediaPlayer hinzufügen
Классно пишешь! Мне прям нравится правильная подача "логики" ООП! Легкая корректировка: 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 и т.д. Или вообще создать специальный базовый класс, который будет иметь общий базовый метод, который будет возвращать тип объекта и уже по типу объекта принимать решение об удалении и т.д. Поскольку, если программа имеет слишком много кастов, то это тоже не очень хорошо и говорит о непродуманной архитектуре классов.