Отображение части графической сцены Qt

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

  • Автор
    Сообщения
  • #4630
    @admin

    Напомню, что частью библиотеки Qt является каркас графического представления, частью которого является графическая сцена — контейнер для ваших графических объектов, который можно использовать для написания своих казуальных игрушек и не только. В предыдущей статье я описывал как без особого труда, используя графическую сцену, можно написать приложение, в котором с помощью клавиатуры можно управлять перемещением ежика. При этом ежик не просто перемещался, а собирал синие грибы и дойдя до зеленого дерева останавливался (по деревья ежи не лазят). Этот код можно использовать как заготовку какой-нибудь казуальной игры, однако как правило в таких играх отображается часть сцены возле главного героя. В этой заметке я описал как реализовать такое поведение, на картинке показано что у нас в итоге получится. Исходный код можно взять в репозитории.

    Итак, каркас графического представления построено на базе паттерна Модель-вид, при этом объект QgraphicsScene является моделью, а QGraphicsView — видом. Ежик должен перемещаться точно также, как это происходило раньше, измениться должно отображение (вид).

    Отображать будем часть сцены вокруг ежика, размер этой части передадим в конструктор вида:

    int main() {
    // ...
      View view(QSize(300, 200));
      view.fitInView(view.setActorFrame(), Qt::KeepAspectRatio);
    // ...
    }

    Метод fitInView реализован в классе QGraphicsView. Он принимает координаты фрагмента сцены (QRect), который требуется отобразить и способ отображения. В данном случае используется опция Qt::KeepAspectRatio, т.е. фрагмент сцены будет вписан в текущее окно без искажения пропорций объектов сцены.

    Метод setActorFrame мы написали сами, он формирует прямоугольную область сцены, которую требуется отобразить исходя из текущего положения ежика и размеров фрагмента, переданных в конструктор View:

    QRect View::setActorFrame() {
      QPoint actorPos = m_actor.pos().toPoint();
      QRect actorRect = QRect(actorPos - m_frameSize, actorPos + m_frameSize);
      this->setSceneRect(actorRect);
      return actorRect;
    }

    Видно, что этот метод не только возвращает объект QRect, но и передает его в QGraphicsView::setSceneRect. Дело в том, что fitInView устанавливает правильные параметры отображения сцены на вид, но отображаемый фрагмент задается с помощью setSceneRect. Важно понять разницу между этими двумя методами:

    • в дальнейшем, при перемещении ежика по сцене мы будем менять область отображаемой сцены, поэтому использовать будем метод setSceneRect;
    • но при изменении размеров окна (или повороте телефона) необходимо изменять именно параметры отображения сцены (того, как часть сцены вписывается в вид) — поэтому используется метод fitInView();

    Чтобы вызвать fitInView при изменении размеров окна, необходимо определить виртуальный метод QWidget::resizeEvent следующим образом:

    void View::resizeEvent(QResizeEvent *event) {
      this->fitInView(setActorFrame(), Qt::KeepAspectRatio);
      QGraphicsView::resizeEvent(event);
    }

    Все это прекрасно работает, но если мы запустим программу сейчас — то экран не будет центрироваться при перемещении ежика. Необходимо привязать сигнал об изменении его положения к слоту setActorFrame. Сделать это можно также в конструкторе View:

    connect(&m_actor, SIGNAL(moved()), this, SLOT(setActorFrame()));

    Тут у ежика (m_actor) появился сигнал moved(). Т.к. у нас за плавное проигрывание анимации перемещения отвечает встроенный в библиотеку Qt класс QGraphicsItemAnimation и перемещение происходит по сигналам QTimeLine (подробнее про это можно прочитать в статье «Анимация на графической сцене«), то сигнал moved() целесообразно привязать к сигналу изменения QTimeLine:

    m_moveAnimationTimer = new QTimeLine(AnimationPeriodMS, this);
    auto moveAnimation = new QGraphicsItemAnimation(m_moveAnimationTimer);
    
    connect(m_moveAnimationTimer, SIGNAL(finished()),
            this, SLOT(onAnimationFinished()));
    connect(m_moveAnimationTimer, SIGNAL(valueChanged(qreal)),
            this, SIGNAL(moved()));

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

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