Evgenii Legotckoi
Evgenii LegotckoiSept. 20, 2015, 12:08 p.m.

How to make game using Qt - Lesson 3. Interaction with other objects

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:

Video

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Do you like it? Share on social networks!

ЛП
  • March 19, 2017, 5:44 p.m.

Классно пишешь! Мне прям нравится правильная подача "логики" ООП! Легкая корректировка: apples.append(apple); // Добавляем яблоко в Список И вопрос: foreach (QGraphicsItem *item, foundItems) { if (item == this) continue; emit signalCheckItem(item); } где формируется указатель item?

В данном случае он формируется в следующем участке кода:

void Widget::slotCreateApple()
{
    Apple *apple = new Apple(); // Создаём яблоко
    scene->addItem(apple);      // Помещаем его в сцену со случайной позицией
   
    ***
}

Класс Apple является наследником QGraphicsItem

ЛП
  • March 20, 2017, 1:42 a.m.

И еще пару вопросов! =) Почему именно в .h файле устанавливается указатель на родителя предка? Почему ты использовал ключевое слово explicit?

explicit Apple(QObject *parent = 0);
Почему указатель на предка передается в QObject? Почему мы обделили QGraphicsItem?
Apple::Apple(QObject *parent) : QObject(parent), QGraphicsItem()

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

Установка указателя на предка в конструкторе - это стандартная практика для Qt фреймворка. Указатель на parent присутствует в конструкторах всех классов, которые наследованы от базового класса QObject.

QGraphicsItem - является одним из тех немногих классов, которые не наследованы от QObject. Поэтому приходится применять множественное наследование, чтобы использовать систему сигналов и слотов, которая работает только с теми классами, которые были наследованы от QObject. Соответственно parent передаётся в базовый класс QObject.

ЛП
  • March 20, 2017, 4:36 a.m.
excplicit - убирает возможность неявного преобразования. Неявные преобразования вообще могут бед наделать, лучше отключать такую возможность для конструкторов сложных классов.

https://www.slideshare.net/IgorShkulipa/c-stl-qt-03 исходя из 6 слайда: "Инструментарий спроктирован так, что для QObject и всех его потомков конструктор копирования и оператор присваивания недоступны- они объявлены в раделе private через макрос Q_DISABLE_COPY;

MyClass ob1(1); 
MyClass ob2 = 10;  // Не прокатит! 

Только вот зачем оно нужно? (или не заморачиваться и все время писать explicit?)

Если делать неявное преобразование, как вы его показали, то да, работать не будет:

MyClass ob1(1); 
MyClass ob2 = 10; // Здесь ошибка

А вот если использовать новомодный синтаксис из более свежих стандартов:

MyClass ob1(1); 
MyClass ob2 = {10}; // Это будет работать

И Q_DISABLE_COPY здесь не спасает на данный момент, он условно только "отключает копирование". А excplicit выключит вариант неявного преобразования с новым синтаксисом.

А вот в чем смысл этих всех запретов?

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

На Qt/С++ я конечно давно не сталкивался с проблемами неявных преобразований, видимо сказывается наличие практики этих самых запретов и ограничений. Но когда занимался разработкой на Си/С++ под AVR и STM32, то там эти преобразования крови много попортили.

Вообще, в чём смысл всех запретов? - Контроль и единообразие, чтобы снизить хаос в проекте.

ЛП
  • March 20, 2017, 5:55 a.m.

Я все-таки не понимаю с item! В этой функции то понятно- 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++); /* Увеличиваем счёт на единицу * и отображаем на дисплее * */ } } }
А вот тут:
void Triangle::slotGameTimer(){ .... foreach (QGraphicsItem *item, foundItems) { if (item == this) continue; emit signalCheckItem(item); } ... }
Как foreach понимает какой из указателей QGraphicsItem он сейчас будет перебирать?

Вопрос снят.

foreach перебирает все указатели, которые находятся в контейнере foundItems

// На графической сцене забираем все контейнеры в определённой области, заданной с помощью QPolygonF
QList<QGraphicsItem *> foundItems = scene()->items(QPolygonF()
                                                           << mapToScene(0, 0)
                                                           << mapToScene(-20, -20)
                                                           << mapToScene(20, -20));

foreach абсолютно всё равно, какие в контейнере классы, главное, чтобы у них один общий базовый класс, в данном случае - это QGraphicsItem . То есть: QGraphicsItem можно скастовать в Apple или в Triangle , поскольку они наследованы от QGraphicsItem, просто если класс изначально задавался как Apple, его не получится скастовать из QGraphicsItem в Trianle. Это возможно, благодаря парадигме полиморфизма

А сам по себе foreach можете перебирать все объекты в контейнере QList, поскольку данный контейнер имеет итератор. Не было бы итератора, то и не смог бы перебирать.

P/S/ для вставки программного кода в комментарий используйте, пожалуйста, специальный диалог для вставки кода. Он вызывается кнопочкой с иконкой "{}" на тулбаре редактора комментариев. Также рядом есть кнопочка предварительного просмотра комментария.

ЛП
  • March 20, 2017, 11:29 a.m.

И еще вопрос: А когда именно вызывается метод paint? Ну понятно что по идее- при столкновении, по таймеру при перерисовке. Я то сначала думал, что он вызывается после метода setRotation() или setPos(). Стал смотреть в отладчике, а нифига!

Метод update() вызывает принудительную перерисовку, также вызывается по некоторым методам, таким как setX(), setY() во внутренностях Qt. Некоторые методы, наподобие paint, вызываются в порядке внутренней очереди. Здесь уже нужно смотреть исходники Qt.

v
  • May 4, 2017, 10:21 a.m.

у меня такая проблема .делал все как на видео все понятно .но выдает ошибку ошибка: '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(); // Создаём яблоко

v
  • May 4, 2017, 10:28 a.m.

http://imglink.ru/show-image.php?id=709c66221545c26eec66d6586f2b7ff1 вот скриншот

Когда у меня подобного рода "лабуда" я делаю следующией действия: 1. пересобираю проект 2. Удаляю папку debug и релиз, все make и qmake файлы 3. Перезапускаю qt Таких вот "чудес" раз по 5 на день. + теневую сборку отключаю

Evgenii Legotckoi
  • May 4, 2017, 10:52 a.m.

Скорее всего заголовочный файл не подключён в файле widget.cpp.

#include "apple.h"
v
  • May 4, 2017, 11:10 a.m.

Спасибо .чет не заметил что не подключил

L
  • May 24, 2017, 4:24 a.m.

При столкновении объектов отсылается сигнал signalCheckItem, который содержит указатель на объект. В ядре игры проводится проверка. Если объект == apple, то выполняется удаление объекта.

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::slotDeleteApple(QGraphicsItem *item)
{
        if(apple == item){
            scene->removeItem(apple);
            delete apple; 
            ui->lcdNumber->display(count++);  
        }
    }
Еще вопрос. Зачем вызывать функцию removeItem, если вызывается оператор delete?
Evgenii Legotckoi
  • May 24, 2017, 4:35 a.m.
void Widget::slotDeleteApple(QGraphicsItem *item) 
{ 
    if(apple == item)
    { 
        scene->removeItem(apple); 
        delete apple; 
        ui->lcdNumber->display(count++); 
    } 
}

Подумайте немного над этим кодом, что вы привели, откуда вы возьмёте объект apple в вашем варианте кода, кроме как не из списка с указателями на яблоки? С данной архитектурой программы необходимо наличие списка с указателями на яблоки.

Что касается удаления, ну примените вы delete. И получите протухший указатель в самой графической сцене. Всё и везде чистить нужно.

L
  • May 24, 2017, 4:58 a.m.

А если применить приведение типов? Enemy01 *itemEnemy01 = dynamic_cast (item);

void Widget::slotDeleteApple(QGraphicsItem *item) 
{ 
 Apple *check = dynamic_cast<Apple *>(item);
    if(check)
    { 
        scene->removeItem(check); 
        delete check; 
        ui->lcdNumber->display(count++); 
    } 
}

Evgenii Legotckoi
  • May 24, 2017, 5:12 a.m.

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

Comments

Only authorized users can post comments.
Please, Log in or Sign up
AD

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:50points,
  • Rating points-4
m

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:80points,
  • Rating points4
m

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:20points,
  • Rating points-10
Last comments
ИМ
Игорь МаксимовNov. 22, 2024, 11:51 a.m.
Django - Tutorial 017. Customize the login page to Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii LegotckoiOct. 31, 2024, 2:37 p.m.
Django - Lesson 064. How to write a Python Markdown extension Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZEOct. 19, 2024, 8:19 a.m.
Fb3 file reader on Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь МаксимовOct. 5, 2024, 7:51 a.m.
Django - Lesson 064. How to write a Python Markdown extension Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas5July 5, 2024, 11:02 a.m.
QML - Lesson 016. SQLite database and the working with it in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Now discuss on the forum
Evgenii Legotckoi
Evgenii LegotckoiJune 24, 2024, 3:11 p.m.
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey1Nov. 15, 2024, 6:04 a.m.
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProjectJune 4, 2022, 3:49 a.m.
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9AnonimOct. 25, 2024, 9:10 a.m.
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Follow us in social networks