「衝突するマウス」の例
グラフィックスビュー上のアイテムをアニメーション化する方法を実演します。
「マウスの衝突」サンプルでは、Graphics View フレームワークを使用して、アニメーション付きのアイテムを実装し、アイテム間の衝突を検出する方法を示しています。

Graphics View は、QGraphicsItem クラスを継承した多数のカスタム 2D グラフィック アイテムを管理および操作するための `QGraphicsScene ` クラスと、ズームや回転をサポートしてアイテムを視覚化する `QGraphicsView ` ウィジェットを提供します。
この例は、アイテムクラスとメイン関数で構成されています。Mouse クラスは、QGraphicsItem を継承した個々のマウスを表し、main() 関数はメインアプリケーションウィンドウを提供します。
まず、Mouse クラスを確認して、アイテムのアニメーションやアイテム同士の衝突検出の方法について学び、次にmain() 関数を確認して、アイテムをシーンに配置する方法や、対応するビューを実装する方法について見ていきます。
Mouseクラスの定義
mouse クラスは、QGraphicsItem を継承しています。QGraphicsItem クラスは、Graphics Viewフレームワークにおけるすべてのグラフィック項目の基底クラスであり、独自のカスタム項目を作成するための軽量な基盤を提供します。
class Mouse : public QGraphicsItem
{
public:
Mouse();
QRectF boundingRect() const override;
QPainterPath shape() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget) override;
protected:
void advance(int step) override;
private:
qreal angle = 0;
qreal speed = 0;
qreal mouseEyeDirection = 0;
QColor color;
};カスタムグラフィックアイテムを作成する際は、QGraphicsItem クラスの2つの純粋仮想パブリック関数を実装する必要があります。1つは、アイテムによって描画される領域の推定値を返すboundingRect()であり、もう1つは実際の描画を実装するpaint()です。さらに、shape()およびadvance()も再実装します。shape() を再実装し、マウスアイテムの正確な形状を返すようにします。デフォルトの実装では、単にアイテムのバウンディング矩形が返されるだけです。また、advance() を再実装し、アニメーション処理を 1 回の更新内で完了するようにします。
Mouseクラスの定義
マウスアイテムを生成する際、まず、クラス内で直接初期化されていないアイテムのすべてのプライベート変数が適切に初期化されるようにします:
Mouse::Mouse() : color(QRandomGenerator::global()->bounded(256),
QRandomGenerator::global()->bounded(256),
QRandomGenerator::global()->bounded(256))
{
setRotation(QRandomGenerator::global()->bounded(360 * 16));
}マウスの色の各成分を計算するために、QRandomGenerator を使用します。
次に、QGraphicsItem から継承されたsetRotation()関数を呼び出します。アイテムは独自のローカル座標系に存在します。その座標は通常(0, 0)を中心としており、これがすべての変換の中心でもあります。アイテムのsetRotation()関数を呼び出すことで、マウスが移動を開始する方向を変更します。
QGraphicsScene がシーンを1フレーム分進めることを決定すると、各アイテムに対してQGraphicsItem::advance()を呼び出します。これにより、advance()関数の再実装を使用してマウスをアニメーションさせることができます。
void Mouse::advance(int step)
{
if (!step)
return;
QLineF lineToCenter(QPointF(0, 0), mapFromScene(0, 0));
if (lineToCenter.length() > 150) {
qreal angleToCenter = std::atan2(lineToCenter.dy(), lineToCenter.dx());
angleToCenter = normalizeAngle((Pi - angleToCenter) + Pi / 2);
if (angleToCenter < Pi && angleToCenter > Pi / 4) {
// Rotate left
angle += (angle < -Pi / 2) ? 0.25 : -0.25;
} else if (angleToCenter >= Pi && angleToCenter < (Pi + Pi / 2 + Pi / 4)) {
// Rotate right
angle += (angle < Pi / 2) ? 0.25 : -0.25;
}
} else if (::sin(angle) < 0) {
angle += 0.25;
} else if (::sin(angle) > 0) {
angle -= 0.25;
}まず、stepが0 の場合、advance()の処理を行わないようにしています。これは、advance()が2回呼び出されるためです。1回目はstep ==0 の状態で、アイテムが間もなく進むことを示し、2回目はstep ==1 の状態で、実際に進む処理が行われます。また、マウスが半径150ピクセルの円内にとどまるようにしています。
QGraphicsItem が提供するmapFromScene() 関数に注目してください。この関数は、シーン座標で指定された位置を、アイテムの座標系にマッピングします。
const QList<QGraphicsItem *> dangerMice = scene()->items(QPolygonF()
<< mapToScene(0, 0)
<< mapToScene(-30, -50)
<< mapToScene(30, -50));
for (const QGraphicsItem *item : dangerMice) {
if (item == this)
continue;
QLineF lineToMouse(QPointF(0, 0), mapFromItem(item, 0, 0));
qreal angleToMouse = std::atan2(lineToMouse.dy(), lineToMouse.dx());
angleToMouse = normalizeAngle((Pi - angleToMouse) + Pi / 2);
if (angleToMouse >= 0 && angleToMouse < Pi / 2) {
// Rotate right
angle += 0.5;
} else if (angleToMouse <= TwoPi && angleToMouse > (TwoPi - Pi / 2)) {
// Rotate left
angle -= 0.5;
}
}
if (dangerMice.size() > 1 && QRandomGenerator::global()->bounded(10) == 0) {
if (QRandomGenerator::global()->bounded(1))
angle += QRandomGenerator::global()->bounded(1 / 500.0);
else
angle -= QRandomGenerator::global()->bounded(1 / 500.0);
}次に、他のマウスとの衝突を回避しようとします。
speed += (-50 + QRandomGenerator::global()->bounded(100)) / 100.0;
qreal dx = ::sin(angle) * 10;
mouseEyeDirection = (qAbs(dx / 5) < 1) ? 0 : dx / 5;
setRotation(rotation() + dx);
setPos(mapToParent(0, -(3 + sin(speed) * 3)));
}最後に、マウスの速度と視線方向(マウスを描画する際に使用)を計算し、新しい位置を設定します。
アイテムの位置は、親座標系におけるその原点(ローカル座標 (0, 0))を表します。QGraphicsItem::setPos() 関数は、アイテムの位置を、親座標系における指定された位置に設定します。 親を持たないアイテムの場合、指定された位置はシーン座標として解釈されます。QGraphicsItem には、アイテム座標で指定された位置を親の座標系にマッピングするmapToParent() 関数も用意されています。アイテムに親がない場合、その位置は代わりにシーン座標系にマッピングされます。
次に、QGraphicsItem から継承された純粋仮想関数の実装を定義します。まずはboundingRect()関数を見てみましょう。
QRectF Mouse::boundingRect() const
{
qreal adjust = 0.5;
return QRectF(-18 - adjust, -22 - adjust,
36 + adjust, 60 + adjust);
}boundingRect() 関数は、アイテムの外側境界を矩形で定義します。Graphics View フレームワークは、この境界矩形を用いてアイテムの再描画が必要かどうかを判断するため、すべての描画はこの矩形の内側で行わなければならない点に注意してください。
void Mouse::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
// Body
painter->setBrush(color);
painter->drawEllipse(-10, -20, 20, 40);
// Eyes
painter->setBrush(Qt::white);
painter->drawEllipse(-10, -17, 8, 8);
painter->drawEllipse(2, -17, 8, 8);
// Nose
painter->setBrush(Qt::black);
painter->drawEllipse(QRectF(-2, -22, 4, 4));
// Pupils
painter->drawEllipse(QRectF(-8.0 + mouseEyeDirection, -17, 4, 4));
painter->drawEllipse(QRectF(4.0 + mouseEyeDirection, -17, 4, 4));
// Ears
painter->setBrush(scene()->collidingItems(this).isEmpty() ? Qt::darkYellow : Qt::red);
painter->drawEllipse(-17, -12, 16, 16);
painter->drawEllipse(1, -12, 16, 16);
// Tail
QPainterPath path(QPointF(0, 20));
path.cubicTo(-5, 22, -5, 22, 0, 25);
path.cubicTo(5, 27, 5, 32, 0, 30);
path.cubicTo(-5, 32, -5, 42, 0, 35);
painter->setBrush(Qt::NoBrush);
painter->drawPath(path);
}Graphics Viewフレームワークは、アイテムの内容を描画するためにpaint()関数を呼び出します。この関数は、ローカル座標系でアイテムを描画します。
耳の描画に注意してください。マウスアイテムが他のマウスアイテムと衝突するたびに、その耳は赤で塗りつぶされ、そうでない場合は濃い黄色で塗りつぶされます。QGraphicsScene::collidingItems() 関数を使用して、衝突するマウスが存在するかどうかを確認します。実際の衝突検出は、Graphics View フレームワークが形状間の交差演算を用いて処理します。私たちがすべきことは、QGraphicsItem::shape() 関数がアイテムの正確な形状を返すようにすることだけです:
QPainterPath Mouse::shape() const
{
QPainterPath path;
path.addRect(-10, -20, 20, 40);
return path;
}任意の形状同士の交差計算の計算量は、形状が複雑になるにつれて桁違いに増加するため、この処理にはかなりの時間がかかる場合があります。別のアプローチとして、collidesWithItem() 関数を再実装し、独自のアイテムと形状の衝突検出アルゴリズムを実装することも可能です。
これでMouse クラスの実装は完了し、使用可能な状態になりました。次に、main() 関数を見て、ネズミ用のシーンと、そのシーンの内容を表示するためのビューをどのように実装するかを確認しましょう。
Main() 関数
main() 関数は、メインのアプリケーションウィンドウを提供するとともに、アイテム、そのシーン、および対応するビューを作成します。
int main(int argc, char **argv)
{
QApplication app(argc, argv);まず、アプリケーションオブジェクトを作成し、シーンを作成します:
QGraphicsScene scene;
scene.setSceneRect(-300, -300, 600, 600);QGraphicsScene クラスは、QGraphicsItemのコンテナとして機能します。また、アイテムの位置を効率的に特定したり、シーン上の任意の領域内でどのアイテムが表示されているかを判定したりする機能も提供します。
シーンを作成する際は、シーンの矩形(シーンの範囲を定義する矩形)を設定することをお勧めします。これは主に、QGraphicsView がビューのデフォルトのスクロール可能領域を決定するために使用され、QGraphicsScene がアイテムのインデックス管理を行うために使用されます。 明示的に設定しない場合、シーンのデフォルトの矩形は、シーンの作成以降にシーン上に存在するすべてのアイテムの最大バウンディング矩形となります。つまり、シーンにアイテムが追加されたり移動されたりすると矩形は拡大しますが、縮小することは決してありません。
scene.setItemIndexMethod(QGraphicsScene::NoIndex);アイテムのインデックス関数は、アイテムの検索を高速化するために使用されます。NoIndex を使用すると、シーン上のすべてのアイテムが検索されるため、アイテムの位置特定には線形計算量がかかります。ただし、アイテムの追加、移動、削除は定数時間で実行されます。このアプローチは、多くのアイテムが継続的に追加、移動、削除される動的なシーンに最適です。 もう一つの選択肢として、BspTreeIndex があります。これは二分探索を利用して、対数的な計算量に近いオーダーのアイテム位置特定アルゴリズムを実現します。
for (int i = 0; i < MouseCount; ++i) {
Mouse *mouse = new Mouse;
mouse->setPos(::sin((i * 6.28) / MouseCount) * 200,
::cos((i * 6.28) / MouseCount) * 200);
scene.addItem(mouse);
}次に、シーンにマウスを追加します。
QGraphicsView view(&scene);
view.setRenderHint(QPainter::Antialiasing);
view.setBackgroundBrush(QPixmap(":/images/cheese.jpg"));シーンを表示するには、QGraphicsView ウィジェットも作成する必要があります。QGraphicsView クラスは、スクロール可能なビューポート内にシーンの内容を可視化します。また、アンチエイリアシングを使用してコンテンツがレンダリングされるようにし、ビューの背景ブラシを設定することでチーズの背景を作成します。
背景に使用される画像は、Qtのリソースシステムを利用して、アプリケーションの実行ファイル内にバイナリファイルとして格納されています。QPixmap のコンストラクタは、ディスク上の実際のファイルを参照するファイル名と、アプリケーションに埋め込まれたリソースを参照するファイル名の両方を指定できます。
view.setCacheMode(QGraphicsView::CacheBackground);
view.setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
view.setDragMode(QGraphicsView::ScrollHandDrag);次に、キャッシュモードを設定します。QGraphicsView は、事前にレンダリングされたコンテンツをピクマップにキャッシュすることができ、そのピクマップがビューポートに描画されます。 このようなキャッシュの目的は、レンダリングに時間がかかる領域(例えば、テクスチャ、グラデーション、アルファブレンドされた背景など)の全体的なレンダリング時間を短縮することです。CacheMode プロパティは、ビューのどの部分がキャッシュされるかを指定し、CacheBackground フラグは、ビューの背景のキャッシュを有効にします。
dragMode プロパティを設定することで、ユーザーがシーンの背景をクリックしてマウスをドラッグした際の動作を定義します。ScrollHandDrag フラグを有効にすると、カーソルが「指差し」の形に変わり、マウスをドラッグするとスクロールバーがスクロールします。
view.setWindowTitle(QT_TRANSLATE_NOOP(QGraphicsView, "Colliding Mice"));
view.resize(400, 300);
view.show();
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, &scene, &QGraphicsScene::advance);
timer.start(1000 / 33);
return app.exec();
}最後に、QApplication::exec() 関数を使用してメインイベントループに入る前に、アプリケーションウィンドウのタイトルとサイズを設定します。
最後に、QTimer を作成し、その timeout() シグナルをシーンの advance() スロットに接続します。タイマーが起動するたびに、シーンは 1 フレーム分進みます。
次に、タイマーが 1000/33 ミリ秒ごとに発火するように設定します。 これにより、1秒あたり30フレームのフレームレートが得られ、これはほとんどのアニメーションにとって十分な速度です。シーンを進行させるために単一のタイマー接続を使用してアニメーションを行うことで、すべてのマウスが同時に移動することが保証され、さらに重要なことに、すべてのマウスが移動し終わった後に画面へ送信される更新は1回のみとなります。
© 2026 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.