Qt – анимация на графической сцене

      Комментарии к записи Qt – анимация на графической сцене отключены

В этой теме 0 ответов, 1 участник, последнее обновление  Васильев Владимир Сергеевич 2 мес., 2 нед. назад.

  • Автор
    Сообщения
  • #3185

    При разработке приложений, и особенно игр, часто требуется анимировать изменение свойств, например координат или угла поворота объекта. Это нужно если в вашей игре могут летать астероиды, пули или двигаться юниты. В библиотеке Qt для этого используется класс QPropertyAnimation – он позволяет плавно , но при работе с графической сценой применяется QGraphicsItemAnimation, т.к. QGraphicsItem не является наследником QObject.

    В этой заметке я описал как вы можете сделать анимацию перемещения объекта на графической сцене. Мы создадим наследника класса QGraphicsView, добавим на него графическую сцену и один QGraphicsEllipseItem, изображающий круг. Определим метод mousePressEvent, для обработки кликов мышью. При щелчке левой кнопкой выполним плавное перемещение объекта в новую точку, а при щелчке правой – просто изменим позицию элемента (очень резкое перемещение). Ниже приведено видео работы программы:

    qgraphicsitemanimation_example

    #include <QGraphicsView>
    #include <QGraphicsScene>
    #include <QGraphicsEllipseItem>
    
    class View : public QGraphicsView {
        Q_OBJECT
    public:
        explicit View(QWidget *parent = 0);
    protected slots:
        void mousePressEvent(QMouseEvent *event);
        void onAnimationFinished();
    private:
        const int AnimationPeriodMS = 1000;
        const int ActorSize = 10;
    
        QGraphicsScene m_scene;
        QGraphicsEllipseItem m_actor;
    };

    Константами задаются время перемещения объекта по сцене (скорость будет зависеть от расстояния) и размер элемента. Обратите внимание на слот onAnimationFinished() – он нужен чтобы корректно освободить память после анимации.

    В конструкторе создадим сцену и перемещаемый элемент, установим для вида сцену, добавим на сцену элемент:

    #include "view.h"
    #include <QMouseEvent>
    #include <QTimeLine>
    #include <QGraphicsItemAnimation>
    
    View::View(QWidget *parent)
        : QGraphicsView(parent),
          m_scene(),
          m_actor(0, 0, ActorSize, ActorSize) {
        setScene(&m_scene);
        m_scene.addItem(&m_actor);
        m_actor.setBrush(QBrush(QColor(255, 128, 128)));
        m_scene.setSceneRect(-100, -100, 200, 200);
        setSceneRect(-100, -100, 100, 100);
    }

    При обработки события мыши нас интересуют только правая и левая кнопки. При событии правой кнопки мы просто перемещаем элемент в заданную позицию. Заметьте, что QMouseEvent содержит позицию для QGraphicsView, а перемещаем мы элемент по сцене, поэтому нам требуется преобразовать координаты вида в координаты сцены методом mapToScene:

    void View::mousePressEvent(QMouseEvent *event) {
        const QPointF eventPos = mapToScene(event->pos()) - QPoint(ActorSize, ActorSize)/2;
        if (event->button() == Qt::LeftButton) {
            QTimeLine *timer = new QTimeLine(AnimationPeriodMS, this);
            QGraphicsItemAnimation *animation = new QGraphicsItemAnimation(timer);
    
            connect(timer, SIGNAL(finished()), SLOT(onAnimationFinished()));
    
            animation->setItem(&m_actor);
            animation->setTimeLine(timer);
            animation->setPosAt(1.0, eventPos);
    
            timer->start();
        }
        else if (event->button() == Qt::RightButton) {
            m_actor.setPos(eventPos);
        }
    }

    При событии от левой кнопки мыши выполняется плавное перемещение, которое будет длиться AnimationPeriodMS миллисекунд – для этого нужно создать QTimeLine и задать в нем соответствующие параметры. Затем создадим объект анимации – QGraphicsItemAnimation, обратите внимание, что в качестве родительского для него мы указываем таймер. Анимации устанавливается элемент, таймер и конечная точка (точки анимации нумеруются от нуля до единицы). Сигнал окончания работы таймера связывается со слотом onAnimationFinished для освобождения памяти (ведь память под таймер и анимацию была выделена динамически и это нельзя сделать на стеке, т.к. объекты существуют дольше, чем выполняется текущая функция).

    В слоте onAnimationFinished методом sender() мы получим отправителя сигнала – это таймер. Вызовем для него деструтор, но не сразу, а более безопасно – методом deleteLater (объект будет разрушен когда очередь входящих сигналов у него станет пустой, т.е. сначала он выполнит все, что от него хотели):

    void View::onAnimationFinished() {
        sender()->deleteLater();
    }

    Мы освободили память из под QTimeLine, а из под объекта анимации – нет, но она будет освобождена автоматически – именно для этого мы установили у нее таймер в качестве родительского класса.

Для ответа в этой теме необходимо авторизоваться.