블록 이동

블록 이동 예제는 사용자 지정 전환이 있는 QStateMachine 을 사용하여 QGraphicsScene 의 항목에 애니메이션을 적용하는 방법을 보여줍니다.

이 예에서는 위 이미지에서 볼 수 있는 파란색 블록에 애니메이션을 적용합니다. 애니메이션은 미리 설정된 네 위치 사이에서 블록을 이동합니다.

이 예제는 다음 클래스로 구성됩니다:

  • StateSwitcher QState 을 상속하고 StateSwitchTransition을 다른 상태에 추가할 수 있습니다. 입력하면 이러한 상태 중 하나로 무작위로 전환됩니다.
  • StateSwitchTransitionStateSwitchEvents에서 트리거되는 사용자 정의 전환입니다.
  • StateSwitchEventStateSwitchTransitions를 트리거하는 QEvent 입니다.
  • QGraphicsRectWidget 는 단순히 배경을 단색 blue 으로 칠하는 QGraphicsWidget 입니다.

블록은 QGraphicsRectWidget 의 인스턴스이며 QGraphicsScene 에서 애니메이션이 적용됩니다. 애니메이션을 삽입하는 상태 그래프를 작성하여 이를 수행합니다. 그런 다음 그래프는 QStateMachine 에서 실행됩니다. 이 모든 작업은 main() 에서 수행됩니다. main() 함수를 먼저 살펴보겠습니다.

main() 함수

QApplication 이 초기화되면 QGraphicsSceneQGraphicsRectWidget를 설정합니다.

    auto button1 = new QGraphicsRectWidget;
    auto button2 = new QGraphicsRectWidget;
    auto button3 = new QGraphicsRectWidget;
    auto button4 = new QGraphicsRectWidget;
    button2->setZValue(1);
    button3->setZValue(2);
    button4->setZValue(3);
    QGraphicsScene scene(0, 0, 300, 300);
    scene.setBackgroundBrush(Qt::black);
    scene.addItem(button1);
    scene.addItem(button2);
    scene.addItem(button3);
    scene.addItem(button4);

QGraphicsView 에 장면을 추가한 후에는 상태 그래프를 작성할 차례입니다. 먼저 우리가 만들려는 상태 차트를 살펴보겠습니다.

group 에는 7개의 하위 상태가 있지만 여기서는 이 중 3개만 다이어그램에 포함했습니다. 이 그래프를 만드는 코드를 한 줄씩 살펴보고 그래프가 어떻게 작동하는지 보여드리겠습니다. 먼저 group 상태를 구성합니다:

    QStateMachine machine;

    auto group = new QState;
    group->setObjectName("group");
    QTimer timer;
    timer.setInterval(1250);
    timer.setSingleShot(true);
    QObject::connect(group, &QState::entered, &timer, QOverload<>::of(&QTimer::start));

타이머는 블록이 이동할 때마다 지연 시간을 추가하는 데 사용됩니다. 타이머는 group 을 입력하면 시작됩니다. 나중에 살펴보겠지만, 타이머가 시간 초과되면 group 상태가 StateSwitcher 상태로 전환됩니다. group 는 머신의 초기 상태이므로 예제가 시작될 때 애니메이션이 예약됩니다.

    auto state1 = createGeometryState(button1, QRect(100, 0, 50, 50),
                                      button2, QRect(150, 0, 50, 50),
                                      button3, QRect(200, 0, 50, 50),
                                      button4, QRect(250, 0, 50, 50),
                                      group);
    ...
    auto state7 = createGeometryState(button1, QRect(0, 0, 50, 50),
                                      button2, QRect(250, 0, 50, 50),
                                      button3, QRect(0, 250, 50, 50),
                                      button4, QRect(250, 250, 50, 50),
                                      group);
    group->setInitialState(state1);

createGeometryState() 는 입력 시 아이템의 지오메트리를 설정하는 QState 를 반환합니다. 또한 group 을 이 상태의 부모로 할당합니다.

전환에 삽입된 QPropertyAnimationQState ( QState::assignProperty() 포함)에 할당된 값을 사용합니다. 즉, 애니메이션은 프로퍼티의 현재 값과 대상 상태의 값 사이에 보간을 수행합니다. 나중에 상태 그래프에 애니메이션 전환을 추가합니다.

    QParallelAnimationGroup animationGroup;

    auto anim = new QPropertyAnimation(button4, "geometry");
    anim->setDuration(1000);
    anim->setEasingCurve(QEasingCurve::OutElastic);
    animationGroup.addAnimation(anim);

항목을 병렬로 이동합니다. 각 항목은 트랜지션에 삽입되는 애니메이션인 animationGroup 에 추가됩니다.

    auto subGroup = new QSequentialAnimationGroup(&animationGroup);
    subGroup->addPause(100);
    anim = new QPropertyAnimation(button3, "geometry");
    anim->setDuration(1000);
    anim->setEasingCurve(QEasingCurve::OutElastic);
    subGroup->addAnimation(anim);

순차 애니메이션 그룹인 subGroup 은 각 항목의 애니메이션 사이에 지연을 삽입하는 데 도움이 됩니다.

    auto stateSwitcher = new StateSwitcher(&machine);
    stateSwitcher->setObjectName("stateSwitcher");
    group->addTransition(&timer, &QTimer::timeout, stateSwitcher);
    stateSwitcher->addState(state1, &animationGroup);
    stateSwitcher->addState(state2, &animationGroup);
    ...
    stateSwitcher->addState(state7, &animationGroup);

StateSwitcher::addState() 의 상태 전환기에 StateSwitchTransition이 추가됩니다. 이 함수에서 애니메이션도 추가합니다. QPropertyAnimation 는 상태의 값을 사용하므로 모든 StateSwitchTransition에 동일한 QPropertyAnimation 인스턴스를 삽입할 수 있습니다.

앞서 언급했듯이 타이머가 시간 초과될 때 트리거되는 상태 전환기를 상태 전환기에 추가합니다.

    machine.addState(group);
    machine.setInitialState(group);
    machine.start();

마지막으로 상태 머신을 생성하고 초기 상태를 추가하고 상태 그래프의 실행을 시작할 수 있습니다.

createGeometryState() 함수

createGeometryState() 에서 각 그래픽 항목의 지오메트리를 설정합니다.

static QState *createGeometryState(QObject *w1, const QRect &rect1, QObject *w2, const QRect &rect2,
                                   QObject *w3, const QRect &rect3, QObject *w4, const QRect &rect4,
                                   QState *parent)
{
    auto result = new QState(parent);
    result->assignProperty(w1, "geometry", rect1);
    result->assignProperty(w2, "geometry", rect2);
    result->assignProperty(w3, "geometry", rect3);
    result->assignProperty(w4, "geometry", rect4);

    return result;
}

앞서 언급했듯이 QAbstractTransitionaddAnimation()로 설정된 속성 값을 사용하여 assignProperty()로 추가된 애니메이션을 설정합니다.

StateSwitcher 클래스

StateSwitcher 에는 createGeometryState() 으로 만든 각 QState에 대한 상태 전환 전환이 있습니다. 이 클래스의 역할은 입력 시 무작위로 이러한 상태 중 하나로 전환하는 것입니다.

StateSwitcher 의 모든 함수는 인라인으로 되어 있습니다. 그 정의를 단계별로 살펴보겠습니다.

class StateSwitcher : public QState
{
    Q_OBJECT
public:
    explicit StateSwitcher(QStateMachine *machine) : QState(machine) { }

StateSwitcher 는 특정 목적을 위해 설계된 상태이며 항상 최상위 상태입니다. m_stateCount 을 사용하여 관리 중인 상태 수를 추적하고 m_lastIndex 을 사용하여 마지막으로 전환한 상태가 어떤 상태였는지 기억합니다.

    void onEntry(QEvent *) override
    {
        int n;
        while ((n = QRandomGenerator::global()->bounded(m_stateCount) + 1) == m_lastIndex)
        { }
        m_lastIndex = n;
        machine()->postEvent(new StateSwitchEvent(n));
    }
    void onExit(QEvent *) override {}

다음 전환할 상태를 선택하고 StateSwitchEvent 을 게시하면 선택한 상태로 StateSwitchTransition 이 트리거됩니다.

    void addState(QState *state, QAbstractAnimation *animation) {
        auto trans = new StateSwitchTransition(++m_stateCount);
        trans->setTargetState(state);
        addTransition(trans);
        trans->addAnimation(animation);
    }

여기서 마법이 일어납니다. 추가된 각 상태에 번호를 할당합니다. 이 번호는 StateSwitchTransition과 StateSwitchEvents에 모두 부여됩니다. 지금까지 살펴본 것처럼 상태 전환 이벤트는 동일한 번호로 전환을 트리거합니다.

StateSwitchTransition 클래스

StateSwitchTransitionQAbstractTransition 를 상속하고 StateSwitchEvent에서 트리거됩니다. 인라인 함수만 포함되어 있으므로, 우리가 정의한 유일한 함수인 eventTest() 함수를 살펴봅시다.

    bool eventTest(QEvent *event) override
    {
        return (event->type() == QEvent::Type(StateSwitchEvent::StateSwitchType))
            && (static_cast<StateSwitchEvent *>(event)->rand() == m_rand);
    }

eventTest 는 트랜지션이 트리거되어야 하는지 여부를 확인할 때 QStateMachine 에서 호출되며, 반환값이 true이면 트리거된다는 뜻입니다. 할당된 숫자가 이벤트의 숫자와 같은지 확인하기만 하면 됩니다(이 경우 실행됩니다).

StateSwitchEvent 클래스

StateSwitchEventQEvent 를 상속하고 StateSwitcher 에서 상태 및 상태 전환 전환에 할당된 번호를 보유합니다. 우리는 이미 StateSwitcher 에서 StateSwitchTransition를 트리거하는 데 어떻게 사용되는지 보았습니다.

class StateSwitchEvent: public QEvent
{
public:
    explicit StateSwitchEvent(int rand) : QEvent(StateSwitchType), m_rand(rand) { }

    static constexpr QEvent::Type StateSwitchType = QEvent::Type(QEvent::User + 256);

    int rand() const { return m_rand; }

private:
    int m_rand;
};

이 클래스에는 인라인 함수만 있으므로 그 정의를 살펴보면 충분할 것입니다.

QGraphicsRectWidget 클래스

QGraphicsRectWidget은 QGraphicsWidget 을 상속하고 rect()을 파란색으로 칠하기만 하면 됩니다. paintEvent () 함수를 인라인하고, 이것이 우리가 정의한 유일한 함수입니다. 다음은 QGraphicsRectWidget 클래스 정의입니다:

class QGraphicsRectWidget : public QGraphicsWidget
{
public:
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
    {
        painter->fillRect(rect(), Qt::blue);
    }
};

계속 진행하기

이 예제에 표시된 기법은 모든 QPropertyAnimation에 대해 동일하게 작동합니다. 애니메이션을 적용할 값이 Qt 속성인 한, 상태 그래프에 해당 값의 애니메이션을 삽입할 수 있습니다.

QState::addAnimation()은 QAbstractAnimation 을 받으므로 모든 유형의 애니메이션을 그래프에 삽입할 수 있습니다.

예제 프로젝트 @ code.qt.io

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.