Exemple de souris en collision
Démontre comment animer des éléments sur une vue graphique.
L'exemple Colliding Mice montre comment utiliser le cadre Graphics View pour mettre en œuvre des éléments animés et détecter les collisions entre les éléments.

Graphics View fournit la classe QGraphicsScene pour gérer et interagir avec un grand nombre d'éléments graphiques 2D personnalisés dérivés de la classe QGraphicsItem, ainsi qu'un widget QGraphicsView pour visualiser les éléments, avec prise en charge du zoom et de la rotation.
L'exemple se compose d'une classe d'éléments et d'une fonction principale : la classe Mouse représente les souris individuelles qui étendent QGraphicsItem, et la fonction main() fournit la fenêtre principale de l'application.
Nous examinerons d'abord la classe Mouse pour voir comment animer les objets et détecter les collisions, puis la fonction main() pour voir comment placer les objets dans une scène et comment implémenter la vue correspondante.
Définition de la classe Souris
La classe mouse hérite de la classe QGraphicsItem. La classe QGraphicsItem est la classe de base pour tous les éléments graphiques dans le cadre de la vue graphique et fournit une base légère pour l'écriture de vos propres éléments personnalisés.
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; };
Lorsque vous écrivez un élément graphique personnalisé, vous devez implémenter les deux fonctions publiques virtuelles pures de QGraphicsItem: boundingRect(), qui renvoie une estimation de la zone peinte par l'élément, et paint(), qui implémente la peinture proprement dite. En outre, nous réimplémentons les fonctions shape() et advance(). Nous réimplémentons shape() pour renvoyer une forme précise de l'élément de notre souris ; l'implémentation par défaut renvoie simplement le rectangle de délimitation de l'élément. Nous réimplémentons advance() pour gérer l'animation de manière à ce qu'elle se fasse en une seule fois.
Définition de la classe de souris
Lors de la construction d'un élément de souris, nous nous assurons d'abord que toutes les variables privées de l'élément qui n'ont pas encore été initialisées directement dans la classe sont correctement initialisées :
Mouse::Mouse() : color(QRandomGenerator::global()->bounded(256), QRandomGenerator::global()->bounded(256), QRandomGenerator::global()->bounded(256)) { setRotation(QRandomGenerator::global()->bounded(360 * 16)); }
Pour calculer les différentes composantes de la couleur de la souris, nous utilisons QRandomGenerator.
Ensuite, nous appelons la fonction setRotation() héritée de QGraphicsItem. Les éléments vivent dans leur propre système de coordonnées local. Leurs coordonnées sont généralement centrées autour de (0, 0), et c'est également le centre de toutes les transformations. En appelant la fonction setRotation() de l'élément, nous modifions la direction dans laquelle la souris commencera à se déplacer.
Lorsque QGraphicsScene décide de faire avancer la scène d'une image, il appelle QGraphicsItem::advance() sur chacun des éléments. Cela nous permet d'animer notre souris en utilisant notre réimplémentation de la fonction 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; }
Tout d'abord, nous ne prenons pas la peine d'avancer si l'étape est 0. En effet, advance() est appelée deux fois : une fois avec step == 0, indiquant que les éléments sont sur le point d'avancer, et ensuite avec step == 1 pour l'avancée proprement dite. Nous veillons également à ce que la souris reste à l'intérieur d'un cercle d'un rayon de 150 pixels.
Notez la fonction mapFromScene() fournie par QGraphicsItem. Cette fonction fait correspondre une position donnée en coordonnées de scène au système de coordonnées de l'élément.
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); }
Ensuite, nous essayons d'éviter les collisions avec d'autres souris.
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))); }
Enfin, nous calculons la vitesse de la souris et la direction de ses yeux (à utiliser pour peindre la souris), et définissons sa nouvelle position.
La position d'un élément décrit son origine (coordonnée locale (0, 0)) dans les coordonnées du parent. La fonction QGraphicsItem::setPos() définit la position de l'élément à la position donnée dans le système de coordonnées du parent. Pour les éléments sans parent, la position donnée est interprétée comme les coordonnées de la scène. QGraphicsItem fournit également une fonction mapToParent() pour faire correspondre une position donnée dans les coordonnées de l'élément au système de coordonnées du parent. Si l'élément n'a pas de parent, la position sera mappée dans le système de coordonnées de la scène.
Il est ensuite temps de fournir une implémentation pour les fonctions virtuelles pures héritées de QGraphicsItem. Examinons tout d'abord la fonction boundingRect() :
QRectF Mouse::boundingRect() const { qreal adjust = 0.5; return QRectF(-18 - adjust, -22 - adjust, 36 + adjust, 60 + adjust); }
La fonction boundingRect() définit les limites extérieures de l'élément sous la forme d'un rectangle. Notez que le cadre d'affichage graphique utilise le rectangle de délimitation pour déterminer si l'élément doit être redessiné, de sorte que toute peinture doit être effectuée à l'intérieur de ce rectangle.
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); }
Le cadre de la vue graphique appelle la fonction paint() pour peindre le contenu de l'élément ; la fonction peint l'élément en coordonnées locales.
Notez la peinture des oreilles : lorsqu'un élément de souris entre en collision avec d'autres éléments de souris, ses oreilles sont remplies de rouge ; sinon, elles sont remplies de jaune foncé. Nous utilisons la fonction QGraphicsScene::collidingItems() pour vérifier s'il y a des souris qui entrent en collision. La détection des collisions proprement dite est gérée par le cadre de travail Graphics View à l'aide de l'intersection forme-forme. Tout ce que nous avons à faire, c'est de nous assurer que la fonction QGraphicsItem::shape() renvoie une forme précise pour notre objet :
QPainterPath Mouse::shape() const { QPainterPath path; path.addRect(-10, -20, 20, 40); return path; }
Étant donné que la complexité de l'intersection arbitraire forme-forme augmente d'un ordre de grandeur lorsque les formes sont complexes, cette opération peut prendre beaucoup de temps. Une autre approche consiste à réimplémenter la fonction collidesWithItem() pour fournir votre propre algorithme personnalisé de collision entre l'objet et la forme.
Ceci complète l'implémentation de la classe Mouse; elle est maintenant prête à être utilisée. Jetons un coup d'œil à la fonction main() pour voir comment implémenter une scène pour les souris et une vue pour afficher le contenu de la scène.
La fonction Main()
La fonction main() fournit la fenêtre principale de l'application et crée les éléments, leur scène et la vue correspondante.
int main(int argc, char **argv) { QApplication app(argc, argv);
Nous commençons par créer un objet d'application et une scène :
QGraphicsScene scene; scene.setSceneRect(-300, -300, 600, 600);
La classe QGraphicsScene sert de conteneur pour les QGraphicsItems. Elle fournit également des fonctionnalités qui vous permettent de déterminer efficacement l'emplacement des éléments ainsi que les éléments visibles dans une zone arbitraire de la scène.
Lors de la création d'une scène, il est recommandé de définir le rectangle de la scène, c'est-à-dire le rectangle qui définit l'étendue de la scène. Il est principalement utilisé par QGraphicsView pour déterminer la zone de défilement par défaut de la vue et par QGraphicsScene pour gérer l'indexation des éléments. S'il n'est pas explicitement défini, le rectangle par défaut de la scène sera le plus grand rectangle de délimitation de tous les éléments de la scène depuis sa création. Cela signifie que le rectangle s'agrandira lorsque des éléments seront ajoutés ou déplacés dans la scène, mais qu'il ne rétrécira jamais.
scene.setItemIndexMethod(QGraphicsScene::NoIndex);
La fonction d'indexation des éléments est utilisée pour accélérer la découverte des éléments. NoIndex implique que la localisation des éléments est d'une complexité linéaire, puisque tous les éléments de la scène sont recherchés. L'ajout, le déplacement et la suppression d'éléments s'effectuent toutefois en temps constant. Cette approche est idéale pour les scènes dynamiques, où de nombreux éléments sont ajoutés, déplacés ou supprimés en permanence. L'alternative est BspTreeIndex, qui utilise une recherche binaire pour obtenir des algorithmes de localisation d'éléments d'un ordre plus proche de la complexité logarithmique.
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); }
Nous ajoutons ensuite les souris à la scène.
QGraphicsView view(&scene); view.setRenderHint(QPainter::Antialiasing); view.setBackgroundBrush(QPixmap(":/images/cheese.jpg"));
Pour pouvoir visualiser la scène, nous devons également créer un widget QGraphicsView. La classe QGraphicsView permet de visualiser le contenu d'une scène dans une fenêtre déroulante. Nous nous assurons également que le contenu est rendu en utilisant l'anticrénelage et nous créons l'arrière-plan du fromage en définissant la brosse d'arrière-plan de la vue.
L'image utilisée pour l'arrière-plan est stockée sous forme de fichier binaire dans l'exécutable de l'application à l'aide du système de ressources de Qt. Le constructeur QPixmap accepte à la fois les noms de fichiers qui se réfèrent à des fichiers réels sur le disque et les noms de fichiers qui se réfèrent aux ressources intégrées de l'application.
view.setCacheMode(QGraphicsView::CacheBackground); view.setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); view.setDragMode(QGraphicsView::ScrollHandDrag);
Ensuite, nous définissons le mode de cache ; QGraphicsView peut mettre en cache le contenu pré-rendu dans une pixmap, qui est ensuite dessinée sur la fenêtre de visualisation. L'objectif de cette mise en cache est d'accélérer le temps de rendu total pour les zones qui sont lentes à rendre, par exemple : les textures, les dégradés et les arrière-plans mélangés en alpha. La propriété CacheMode définit les parties de la vue qui sont mises en cache, et le drapeau CacheBackground active la mise en cache de l'arrière-plan de la vue.
En définissant la propriété dragMode, nous définissons ce qui doit se passer lorsque l'utilisateur clique sur l'arrière-plan de la scène et fait glisser la souris. L'indicateur ScrollHandDrag transforme le curseur en main et le fait de déplacer la souris fait défiler les barres de défilement.
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(); }
Enfin, nous définissons le titre et la taille de la fenêtre d'application avant d'entrer dans la boucle d'événements principale à l'aide de la fonction QApplication::exec().
Enfin, nous créons un site QTimer et connectons son signal timeout() au slot advance() de la scène. Chaque fois que la minuterie se déclenche, la scène avance d'une image.
Nous demandons ensuite au timer de se déclencher toutes les 1000/33 millisecondes. Nous obtenons ainsi un taux de rafraîchissement de 30 images par seconde, ce qui est suffisamment rapide pour la plupart des animations. Le fait de réaliser l'animation avec une seule connexion au timer pour faire avancer la scène permet de s'assurer que toutes les souris sont déplacées à un moment donné et, plus important encore, qu'une seule mise à jour est envoyée à l'écran après que toutes les souris ont bougé.
© 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.