ブロックの移動

Move Blocks の例では、QStateMachine を使って、QGraphicsScene のアイテムをカスタムトランジションでアニメーションさせる方法を紹介しています。

この例では、上の画像に見える青いブロックをアニメーション化します。アニメーションは、4つのプリセットポジションの間でブロックを動かします。

この例は以下のクラスで構成されています:

  • StateSwitcher は を継承し、 を他のステートに追加できます。入力されると、これらのステートのいずれかにランダムに遷移します。QState StateSwitchTransition
  • StateSwitchTransition はカスタムトランジションで、 sをトリガーする。StateSwitchEvent
  • StateSwitchEvent は s をトリガーする です。StateSwitchTransition QEvent
  • QGraphicsRectWidget は、 で、単に背景を無地 で塗ります。QGraphicsWidget blue

ブロックはQGraphicsRectWidget のインスタンスであり、QGraphicsScene でアニメーション化される。ステート・グラフを構築することで、これにアニメーションを挿入する。このグラフはQStateMachine で実行される。 これらはすべてmain() で行われる。まず、main() 関数を見てみよう。

main() 関数

QApplication が初期化された後、QGraphicsRectWidgetを使ってQGraphicsScene をセットアップします。

    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

トランジションに挿入されたQPropertyAnimation は、QStateQState::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);

StateSwitchTransition は、StateSwitcher::addState() のステートスイッチャーに追加されます。また、この関数の中でアニメーションを追加します。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;
}

前述したように、QAbstractTransition は、assignProperty ()で設定したプロパティ値を使用して、addAnimation ()で追加したアニメーションを設定します。

StateSwitcherクラス

StateSwitcher は、 で作成した各 s に対するステート・スイッチ遷移を持っています。このクラスの仕事は、これらのステートが入力されたときに、ランダムにいずれかのステートに遷移することです。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クラス

StateSwitchTransition は を継承し、 s をトリガーします。インライン関数しか含まれていないので、 () 関数を見てみましょう。QAbstractTransition StateSwitchEvent eventTest

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

eventTest この関数は我々が定義した唯一の関数である。 、トランジションがトリガーされるべきかどうかをチェックするときに呼び出される。私たちは単に、割り当てられた番号がイベントの番号と等しいかどうかをチェックするだけである(その場合、発火する)。QStateMachine

StateSwitchEventクラス

StateSwitchEvent は、 を継承し、 によって状態および状態遷移に割り当てられた番号を保持する。このクラスが、 のトリガーにどのように使われるかは、 ですでに見た。QEvent StateSwitcher StateSwitchTransition StateSwitcher

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.