C
Creator2 января 2017 г. 18:13

Коллизия объектов

Коллизия, qt

Пишу игру bomberman. Проблема вот в чем. У меня есть игрок, он ходит, устанавливает бомбы, но нужно создать механизм обработки этих взрывов, что бы объекты, которые попадают под данный радиус взрыва уничтожались. Как обнаружить коллизию при создании взрыва, происходит ошибка если проверять коллизию в конструкторе класса? Когда это лучше всего сделать проверку на коллизию. У меня есть класс взрыв и класс стена, стена должна уничтожится, если она попадает под взрыв.

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

8
C
  • 2 января 2017 г. 19:40

Поправлю вопрос. Как проверить на коллизию, сразу при добавлении объекта на сцену.

    Evgenii Legotckoi
    • 3 января 2017 г. 6:02

    Добрый день.
    Я накидал пример по коллизиям с перемещением игрока и бомбочкой. Не совсем бомбермэн, но идея будет похожая. Игрок может проходит сквозь бомбочки, но не может проходить сквозь стены. Взрыв реализовывать я не стал, поскольку это будет лишь анимация в данном примере. Все стены вокруг бомбы будут уничтожаться в определённом радиусе. Тогда как другие бомбочки и игрок будут оставаться на месте.
    Итак, перейдём к коду.
    В MainWindow будет только кастомизированная игровая сцена, это понадобится, чтобы обрабатывать клики по игровой сцене и создавать стены и бомбочки.

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    #include <customscene.h>
    
    namespace Ui {
    class MainWindow;
    }
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
    
    private:
        Ui::MainWindow *ui;
        CustomScene *scene;
    };
    
    #endif // MAINWINDOW_H
    
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include "player.h"
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        resize(600, 600);
        scene = new CustomScene(this);
        ui->graphicsView->setSceneRect(0, 0, 500, 500);
        ui->graphicsView->setScene(scene);
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    

    Посмотрим игровую сцену. В ней объявлен игрок и реализована логика по передаче положения, к которому должен прийти игрок, с логикой добавления бомбочек по клику правой кнопкой мыши на графической сцене

    #ifndef CUSTOMSCENE_H
    #define CUSTOMSCENE_H
    
    #include <QGraphicsScene>
    #include <QTimer>
    #include "player.h"
    
    class CustomScene : public QGraphicsScene
    {
        Q_OBJECT
    public:
        explicit CustomScene(QObject *parent = nullptr);
    
    protected:
        virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
    
    signals:
    
    public slots:
    
    private:
        QTimer m_createWallsTimer;
        Player *player;             // Игрок
    };
    
    #endif // CUSTOMSCENE_H
    
    #include "customscene.h"
    
    #include <QGraphicsSceneMouseEvent>
    #include "bomb.h"
    #include "wall.h"
    #include <QTimer>
    #include <QDebug>
    
    static int randomBetween(int low, int high)
    {
        return (qrand() % ((high + 1) - low) + low);
    }
    
    CustomScene::CustomScene(QObject *parent) : QGraphicsScene(parent)
    {
        // Добавляем на игровую сцену игрока
        player = new Player();
        player->setPos(250, 250);
        player->setZValue(1000);
        addItem(player);
    
        // Периодическое создание стен на игровом поле
        connect(&m_createWallsTimer, &QTimer::timeout, [this]()
        {
            Wall* wall = new Wall();
            wall->setPos(randomBetween(15, 485), randomBetween(15, 485));
            addItem(wall);
        });
        m_createWallsTimer.start(1000);
    }
    
    void CustomScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
    {
        switch (event->button()) {
        case Qt::RightButton:
        {
            // При клике по сцене правой кнопкой мыши, создаём бомбу
            Bomb* bomb = new Bomb();
            bomb->setPos(event->scenePos());
            addItem(bomb);
            break;
        }
        case Qt::LeftButton:
        {
            // При клике левой кнопкой мыши, указываем, куда идти игроку
            player->slotTarget(event->scenePos());
            break;
        }
        default:
            break;
        }
    }
    

    Все игровые объекты должны быть наследованы от одного абстрактного класса, в котором будут объявлены все возможные типы графических объектов. Это упростит определение типа объекта на графической сцене при обработке взрывов и перемещении игрока. Это возможно благодаря тому, что базовый класс абстрактного игрового объекта является QGraphicsItem , а он в свою очередь имеет метод type(), при перегрузке которого мы и будем получать тип объекта

    Реализация подобного подхода возможна благодаря парадигме полиморфизма в C++ .

    #ifndef GAMEOBJECT_H
    #define GAMEOBJECT_H
    
    #include <QGraphicsObject>
    
    class GameObject : public QGraphicsObject
    {
        Q_OBJECT
    public:
        explicit GameObject(QGraphicsItem *parent = nullptr);
    
        // Базовый абстрактный ирговой объект, в котором перечислены все
        // типы возможных игровых объектов
        // От данного класса должны быть наследованы все остальные
        enum Type
        {
            Player = UserType + 1,
            Wall,
            Bomb
        };
    
    signals:
    
    public slots:
    };
    
    #endif // GAMEOBJECT_H
    
    #include "gameobject.h"
    
    GameObject::GameObject(QGraphicsItem *parent) : QGraphicsObject(parent)
    {
    
    }
    

    Я не стал добавлять объект взрыва, но логика его срабатывания будет похожа на логику работы бомбочки. Разве что он просто должен произойти, но не влиять никак на другие объекты. Поскольку именно бомбочка будет уничтожать другие объекты в области поражения. А вот уничтожать объекты она будет в деструкторе класса. Это довольно удобно. Бомбочка уничтожилась и прихватила за собой другие объекты.

    #ifndef BOMB_H
    #define BOMB_H
    
    #include "gameobject.h"
    #include <QTimer>
    
    class Bomb : public GameObject
    {
        Q_OBJECT
    public:
        explicit Bomb(QGraphicsItem *parent = 0);
        virtual ~Bomb();
    
        virtual int type() const override;
    
    protected:
        virtual QRectF boundingRect() const override;
        virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
    
    signals:
    
    public slots:
    
    private:
        QTimer m_liveTimer;
    };
    
    #endif // BOMB_H
    
    #include "bomb.h"
    #include <QGraphicsScene>
    #include <QPainter>
    
    Bomb::Bomb(QGraphicsItem *parent) : GameObject(parent)
    {
        // Бомба взрывает не сразу, а через 3 секунды после своего появления
        connect(&m_liveTimer, &QTimer::timeout, this, &QGraphicsObject::deleteLater);
        m_liveTimer.start(3000);
    }
    
    Bomb::~Bomb()
    {
        // Взрыв выполняем в деструкторе бомбы
    
        // Формируем зону взрыва. Можно создать хону взрыва любой формы
        // с помощью QPainterPath
        // Но в данном случае формируем зону взрыва в виде круга
        QPainterPath explosionZone;
        explosionZone.addEllipse(this->pos(), 100, 100);
    
        // Находим все объекты, которые попали в зону взрыва
        QList<QGraphicsItem *> foundedItems = scene()->items(explosionZone);
        for (auto item : foundedItems)
        {
            switch (item->type()) {
            case Type::Wall:
                // Но уничтожаем только стены
                scene()->removeItem(item);
                delete item;
                break;
            default:
                break;
            }
        }
    }
    
    int Bomb::type() const
    {
        // Возвращаем тип объекта "Бомба"
        return Type::Bomb;
    }
    
    QRectF Bomb::boundingRect() const
    {
        return QRectF(-10, -10, 20, 20);
    }
    
    void Bomb::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
        Q_UNUSED(option)
        Q_UNUSED(widget)
    
        painter->drawEllipse(-10, -10, 20 ,20);
    }
    

    Стена - это будет просто зелёный квадрат. Она уничтожается бомбочкой в её деструкторе и не позволяет игроку пройти сквозь себя (это уже обрабатывается в коде передвижения игрока)

    #ifndef WALL_H
    #define WALL_H
    
    #include "gameobject.h"
    
    class Wall : public GameObject
    {
        Q_OBJECT
    public:
        explicit Wall(QGraphicsItem *parent = 0);
    
        virtual int type() const override;
    
    protected:
        virtual QRectF boundingRect() const override;
        virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
    
    signals:
    
    public slots:
    };
    
    #endif // WALL_H
    
    #include "wall.h"
    #include <QPainter>
    
    Wall::Wall(QGraphicsItem *parent) : GameObject(parent)
    {
    
    }
    
    int Wall::type() const
    {
        // Возвращаем тип объекта "Стена"
        return Type::Wall;
    }
    
    QRectF Wall::boundingRect() const
    {
        return QRectF(-10, -10, 20, 20);
    }
    
    void Wall::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
        Q_UNUSED(option)
        Q_UNUSED(widget)
    
        painter->setBrush(Qt::green);
        painter->drawRect(-10, -10, 20, 20);
    }
    
    Наиболее сложный код у игрока, там происходит обработка движения в сторону клика , обработка коллизий со стеной и прохождение сквозь бомбочку.
    #ifndef PLAYER_H
    #define PLAYER_H
    
    #include "gameobject.h"
    #include <QTimer>
    
    #define GO true
    #define STOP false
    
    class Player : public GameObject
    {
        Q_OBJECT
    public:
        explicit Player(QGraphicsItem *parent = 0);
    
        virtual int type() const override;
    
    protected:
        virtual QRectF boundingRect() const override;
        virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
    
    signals:
    
    public slots:
        void slotTarget(QPointF point);
    
    private slots:
        void slotGameTimer();
    
    private:
        QTimer gameTimer;
        QPointF m_target;
        bool m_state;
    };
    
    #endif // PLAYER_H
    
    #include "player.h"
    #include <QPainter>
    #include <QGraphicsScene>
    
    static const double Pi = 3.14159265358979323846264338327950288419717;
    static double TwoPi = 2.0 * Pi;
    
    static qreal normalizeAngle(qreal angle)
    {
        while (angle < 0)
            angle += TwoPi;
        while (angle > TwoPi)
            angle -= TwoPi;
        return angle;
    }
    
    Player::Player(QGraphicsItem *parent)
        : GameObject(parent),
          m_state(STOP)
    {
        connect(&gameTimer, &QTimer::timeout, this, &Player::slotGameTimer);
        gameTimer.start(25);
    }
    
    int Player::type() const
    {
        // Возвращаем тип объекта "Игрок"
        return Type::Player;
    }
    
    QRectF Player::boundingRect() const
    {
        return QRectF(-12,-15,24,30);
    }
    
    void Player::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
        Q_UNUSED(option)
        Q_UNUSED(widget)
    
        QPolygon polygon;
        polygon << QPoint(0,-15) << QPoint(12,15) << QPoint(-12,15);
        painter->setBrush(Qt::red);
        painter->drawPolygon(polygon);
    }
    
    void Player::slotGameTimer()
    {
        if(m_state){
            QLineF lineToTarget(QPoint(0,0), mapFromScene(m_target));
            // Продвигаемся к цели
            if(lineToTarget.length() > 2)
            {
                 setPos(mapToParent(0, -1));
                 // Находим все объекты, на которые наткнулся игрок
                 QList <QGraphicsItem *> items = scene()->collidingItems(this);
                 if (!items.isEmpty())
                 {
                     for (auto item : items)
                     {
                         // Но не можем пройти только сквозь стены
                         // Стеныы отталкивают игрока назад
                         switch (item->type()) {
                         case Type::Wall:
                             setX(this->x() + 1);
                             break;
                         default:
                             // Тогда как через остальные объекты проходим насквозь
                             break;
                         }
                     }
                 }
            }
            else
            {
                m_state = STOP;
            }
            /* Проверка выхода за границы поля
             * Если объект выходит за заданные границы, то возвращаем его назад
             * */
            if(this->x() - 30 < 0)
            {
                this->setX(30);         /// слева
                m_state = STOP;         // Останавливаемся
            }
            if(this->x() + 30 > 520)
            {
                this->setX(520 - 30);   /// справа
                m_state = STOP;         // Останавливаемся
            }
            if(this->y() - 30 < 0)
            {
                this->setY(30);         /// сверху
                m_state = STOP;         // Останавливаемся
            }
            if(this->y() + 30 > 520)
            {
                this->setY(520 - 30);   /// снизу
                m_state = STOP;         // Останавливаемся
            }
        }
    }
    
    void Player::slotTarget(QPointF point)
    {
        // Определяем расстояние до цели
        m_target = point;
        QLineF lineToTarget(QPointF(0, 0), mapFromScene(m_target));
        // Угол поворота в направлении к цели
        qreal angleToTarget = ::acos(lineToTarget.dx() / lineToTarget.length());
        if (lineToTarget.dy() < 0)
            angleToTarget = TwoPi - angleToTarget;
        angleToTarget = normalizeAngle((Pi - angleToTarget) + Pi / 2);
    
        // Поворачиваем героя к цели
        if (angleToTarget >= 0 && angleToTarget < Pi)
        {
            // Rotate left
            setRotation(rotation() - angleToTarget * 180 /Pi);
        }
        else if (angleToTarget <= TwoPi && angleToTarget > Pi)
        {
            // Rotate right
            setRotation(rotation() + (angleToTarget - TwoPi )* (-180) /Pi);
        }
    
        m_state = GO; // Разрешаем идти
    }
    
    Программный код примера во вложении.
      Evgenii Legotckoi
      • 3 января 2017 г. 6:15

      Что касается проверки коллизий сразу при добавлении на графическую сцену, то тут несколько не хватает условий. Для каких именно других графических объектов нужно делать проверку.

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

      Так что вопрос спорный, а нужно ли это выполнять сразу при добавлении?

        C
        • 3 января 2017 г. 6:31

        Проверка при добавлении нужна была для взрыва.
        Спасибо за подробное объяснение.
        Я решил проблему несколько другим способом, скорее костылем. Но так оказалось даже лучше, создал метод в классе взрыва и просто вызывал его после добавление на сцену.
        Вот например.

        for(int i = 1; i < sizeExplosion; i++)
            {
                ExplosionHorizontal *explosionRH = new ExplosionHorizontal(playerID, QPointF(this->pos().x() - 32 * i, this->pos().y()));
                scene()->addItem(explosionRH);
                rightExplosion = explosionRH->checkCollision();
                if(rightExplosion)
                    break;
            }
        
        И сам метод проверки:
        bool ExplosionHorizontal::checkCollision()
        {
            foreach(QGraphicsItem *item, scene()->collidingItems(this))
            {
                if(item->data(0).toInt() == 1)
                {
                    Triangle *it = qgraphicsitem_cast <Triangle *> (item);
                    if(((int (it->x())) / 32) * 32 == this->x() && ((int (it->y())) / 32) * 32 == this->y())
                        it->kill();
                }
                if(item->data(1).toInt() == 2)
                {
                    qDebug() << "StoneNoDestroy";
                    this->deleteLater();
                    return true;
                }
                if(item->data(1).toInt() == 3)
                {
                    qDebug() << "StoneDestroy";
                    StoneDestroy *it = qgraphicsitem_cast <StoneDestroy *> (item);
                    it->destroy();
                    this->deleteLater();
                    return true;
                }
                if(item->data(1).toInt() == 4)
                {
                    qDebug() << "BoomCollision";
                    Bomb *it = qgraphicsitem_cast <Bomb *> (item);
                    it->bombCollision();
                    this->deleteLater();
                    return true;
                }
            }
            return false;
        }
        
        Так же добавил скрин интерфейса. И как срабатывает взрыв
          C
          • 3 января 2017 г. 6:33

          К этой же теме, игрок движется с разной скоростью при разных размерах виджета. Я создал новую тему по этому вопросу

            Evgenii Legotckoi
            • 3 января 2017 г. 6:51
            • Ответ был помечен как решение.

            Да нормальное решение.

            Я уничтожаю объекты при уничтожении бомбочки, а вы при создании взрыва

            Мне в вашем коде другое не понравилось, вы используете много кастов объектов, всё-таки, если кастов много, значит в коде что-то не так. Лучше перейдите на типы через метод type() , который я предложил. Это будет правильнее и более красиво.

            И ещё, используйте for вместо foreach . Дело в том, что foreach был сделан для того, чтобы проходить по контейнерам, поскольку в for не было поддержки контейнеров, но в современном стандарте C++ эта поддержка уже внедрена. Лично я сейчас уже отошёл от использования foreach в своих проектах.

            А как вы добавляли скрины? Я что-то не вижу их в сообщении. Возможно логика обработки сообщений на форуме выпилила скрины. (возможно нужно подкорректировать код на сайте, поэтому опишите подробнее этот момент со скринами)

              C
              • 3 января 2017 г. 6:57

              Понял, учту. Можно пример условий для for . Именно для контейнеров.

              Добавил в Прикрепления Но видимо скрипт на сайте не захотел принять это изображение. Сейчас попробую ещё раз

                Evgenii Legotckoi
                • 3 января 2017 г. 9:38

                Конечно, вот пример для for :

                for(QGraphicsItem* item : scene()->collidingItems(this))
                {
                    // Сделать что-нибудь с item    
                }
                
                То есть тоже самое, что и вы делали с foreach , только будет for и синтаксис немного другой.
                В Qt foreach был разработан, когда у обычного for не было синтаксиса для перебора всяких vector или QList. А сейчас это уже поддерживается самим C++.

                  Комментарии

                  Только авторизованные пользователи могут публиковать комментарии.
                  Пожалуйста, авторизуйтесь или зарегистрируйтесь
                  B

                  C++ - Тест 002. Константы

                  • Результат:16баллов,
                  • Очки рейтинга-10
                  B

                  C++ - Тест 001. Первая программа и типы данных

                  • Результат:46баллов,
                  • Очки рейтинга-6
                  FL

                  C++ - Тест 006. Перечисления

                  • Результат:80баллов,
                  • Очки рейтинга4
                  Последние комментарии
                  k
                  kmssr8 февраля 2024 г. 18:43
                  Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
                  АК
                  Анатолий Кононенко5 февраля 2024 г. 1:50
                  Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
                  EVA
                  EVA25 декабря 2023 г. 10:30
                  Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
                  J
                  JonnyJo25 декабря 2023 г. 8:38
                  Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
                  G
                  Gvozdik18 декабря 2023 г. 21:01
                  Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
                  Сейчас обсуждают на форуме
                  P
                  Pisych27 февраля 2023 г. 4:04
                  Как получить в массив значения из связанной модели? Спасибо, разобрался:))
                  AC
                  Alexandru Codreanu19 января 2024 г. 11:57
                  QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…
                  BlinCT
                  BlinCT27 декабря 2023 г. 8:57
                  Растягивать Image на парент по высоте Ну и само собою дял включения scrollbar надо чтобы был Flickable. Так что выходит как то так Flickable{ id: root anchors.fill: parent clip: true property url linkFile p…
                  Дмитрий
                  Дмитрий10 января 2024 г. 4:18
                  Qt Creator загружает всю оперативную память Проблема решена. Удалось разобраться с помощью утилиты strace. Запустил ее: strace ./qtcreator Начал выводиться весь лог работы креатора. В один момент он начал считывать фай…
                  Evgenii Legotckoi
                  Evgenii Legotckoi12 декабря 2023 г. 6:48
                  Побуквенное сравнение двух строк Добрый день. Там случайно не высылается этот сигнал textChanged ещё и при форматировани текста? Если решиать в лоб, то можно просто отключать сигнал/слотовое соединение внутри слота и …

                  Следите за нами в социальных сетях