- 1. Сабақтың бағдарламалық коды
- 2. алма.сағ
- 3. apple.cpp
- 4. үшбұрыш.сағ
- 5. triangle.cpp
- 6. виджет.h
- 7. widget.cpp
- 8. Барлығы
- 9. Бейне оқулық
Енді ойында пернетақта пернелерінің әсерінен қозғалатын анимациялық Fly бар болғандықтан, ойынға мән қосатын кез келді. Нысананы Ұшуға қосайық, мысалы, ол алма жейді, ол есептеледі. Яғни, Шыбындардың басқа объектілермен, бұл жағдайда алмалармен әрекеттесуін орнату қажет.
Алма үшін бөлек класс жасау керек, ол да QGraphicsItem ішінен алынады. Жасалған кезде алманың кездейсоқ түсі болуы керек, ол да qrand() көмегімен алма сыныбының конструкторында инициализацияланады. функциясы.
Fly алмаға тап болғанда, бұл ақпаратты виджет класындағы ойынның өзегіне көшіру керек. Бұл әрекетті орындау үшін, Fly қозғалғанда, біз оның қарау өрісіне түсетін және өзара әрекеттесуін пысықтау қажет объектілерді талдаймыз.
/* Производим проверку на то, наткнулась ли муха на какой-нибудь * элемент на графической сцене. * Для этого определяем небольшую область перед мухой, * в которой будем искать элементы * */ 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); // Добавляем Яблоко в Список }
Сабақтың бағдарламалық коды
Бұл сабақта өткен сабақтардың бағдарламалық кодына өзгертулер енгізіледі.
Өзара әрекеттесу процесі сипатталғаннан кейін мен алма жасауға жауапты сыныптың толық бағдарлама кодын беремін.
алма.сағ
Бұл файлға жауапты болатын айнымалы ғана қосылды
#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); }
үшбұрыш.сағ
Сондай-ақ Flies тақырып файлына сигнал қосу керек, ол Fly. кездескен ойын өзегіне нысандарды жібереді.
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 и т.д. Или вообще создать специальный базовый класс, который будет иметь общий базовый метод, который будет возвращать тип объекта и уже по типу объекта принимать решение об удалении и т.д. Поскольку, если программа имеет слишком много кастов, то это тоже не очень хорошо и говорит о непродуманной архитектуре классов.