Exemple de collision entre souris
Illustre comment animer des éléments dans une vue graphique.
L'exemple « Collision entre souris » montre comment utiliser le framework Graphics View pour implémenter des éléments animés et détecter les collisions entre ces é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 ces éléments, avec prise en charge du zoom et de la rotation.
L’exemple se compose d’une classe d’élément et d’une fonction principale : la classe ` Mouse ` représente les souris individuelles héritant de ` QGraphicsItem`, et la fonction ` main() ` fournit la fenêtre principale de l’application.
Nous allons d'abord examiner la classe ` Mouse ` pour voir comment animer les éléments et détecter leurs collisions, puis nous examinerons la fonction ` main() ` pour voir comment placer les éléments dans une scène et comment implémenter la vue correspondante.
Définition de la classe Mouse
La classe ` mouse ` hérite de ` QGraphicsItem`. La classe ` QGraphicsItem ` est la classe de base de tous les éléments graphiques du framework Graphics View 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 créez un élément graphique personnalisé, vous devez implémenter les deux fonctions publiques virtuelles pures de ` QGraphicsItem` : ` boundingRect()` (qui renvoie une estimation de la surface peinte par l’élément) et ` paint()` (qui implémente le dessin proprement dit). De plus, nous réimplémentons les méthodes ` shape()` et ` advance()`. Nous réimplémentons shape() afin de renvoyer la forme exacte de notre élément « souris » ; l’implémentation par défaut se contente en effet de renvoyer le rectangle englobant de l’élément. Nous réimplémentons advance() pour gérer l’animation de manière à ce qu’elle s’effectue en une seule mise à jour.
Définition de la classe Mouse
Lors de la construction d’un élément « mouse », 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 le sont correctement :
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 évoluent dans leur propre système de coordonnées local. Leurs coordonnées sont généralement centrées autour de (0, 0), qui 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 la fonction ` QGraphicsScene ` décide de faire avancer la scène d’une image, elle appelle ` QGraphicsItem::advance()` sur chacun des éléments. Cela nous permet d’animer notre souris à l’aide de 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 nous soucions pas d’effectuer d’avance si la valeur de step est 0. En effet, la fonction advance() est appelée deux fois : une première fois avec step == 0, indiquant que les éléments sont sur le point d’avancer, puis avec step == 1 pour l’avance 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 convertit une position donnée en coordonnées de scène en coordonnées du système de référence 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 toute collision 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 son regard (à utiliser lors du dessin de la souris), puis nous 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 fournie est interprétée comme des coordonnées de la scène. QGraphicsItem fournit également une fonction mapToParent() permettant de mapper une position donnée en coordonnées de l’élément vers le système de coordonnées du parent. Si l’élément n’a pas de parent, la position sera alors mappée vers 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 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 framework Graphics View utilise ce rectangle de délimitation pour déterminer si l’élément doit être redessiné ; tout le dessin doit donc être effectué à 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 framework Graphics View appelle la fonction paint() pour dessiner le contenu de l’élément ; cette fonction dessine l’élément en coordonnées locales.
Remarquez le rendu des oreilles : chaque fois qu’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 en collision. La détection de collision proprement dite est gérée par le framework Graphics View à l’aide de l’intersection de formes. Il nous suffit de nous assurer que la fonction ` QGraphicsItem::shape()` renvoie une forme précise pour notre élément :
QPainterPath Mouse::shape() const
{
QPainterPath path;
path.addRect(-10, -20, 20, 40);
return path;
}Comme la complexité de l’intersection entre des formes arbitraires augmente d’un ordre de grandeur lorsque les formes sont complexes, cette opération peut s’avérer particulièrement longue. Une autre approche consiste à réimplémenter la fonction collidesWithItem() afin de fournir votre propre algorithme personnalisé de collision entre éléments et formes.
L'implémentation de la classe Mouse est ainsi terminée ; elle est désormais prête à l'emploi. Examinons la fonction main() pour voir comment implémenter une scène pour les souris et une vue permettant d'afficher le contenu de la scène.
La fonction Main()
La fonction main() fournit la fenêtre principale de l’application, tout en créant les éléments, leur scène et la vue correspondante.
int main(int argc, char **argv)
{
QApplication app(argc, argv);Tout d’abord, nous créons un objet d’application et la 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 permettant de déterminer efficacement l'emplacement des éléments ainsi que ceux qui sont 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élimite son étendue. 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 correspondra au plus grand rectangle englobant tous les éléments présents dans la scène depuis sa création. Cela signifie que le rectangle s’agrandira à mesure que 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 sert à accélérer leur localisation. NoIndex implique que la localisation des éléments présente une complexité linéaire, car tous les éléments présents dans la scène sont parcourus. 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 continu. L’alternative est l’ BspTreeIndex, qui utilise une recherche binaire pour mettre en œuvre des algorithmes de localisation des éléments dont la complexité est plus proche d’une 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 ` affiche le contenu d’une scène dans une fenêtre de visualisation défilable. Nous veillons également à ce que le contenu soit rendu avec un lissage, et nous créons l’arrière-plan « fromage » en définissant le pinceau 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 de ` QPixmap ` accepte à la fois les noms de fichiers faisant référence à des fichiers réels sur le disque et les noms de fichiers faisant référence aux ressources intégrées de l’application.
view.setCacheMode(QGraphicsView::CacheBackground);
view.setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
view.setDragMode(QGraphicsView::ScrollHandDrag);Nous définissons ensuite le mode de mise en cache ; ` QGraphicsView ` peut mettre en cache du contenu pré-rendu dans une image bitmap, qui est ensuite dessinée dans la fenêtre d’affichage. L'objectif de cette mise en cache est d'accélérer le temps de rendu global des zones dont le rendu est lent, par exemple : les textures, les dégradés et les arrière-plans avec fusion alpha. La propriété CacheMode indique quelles parties de la vue sont mises en cache, et l'indicateur CacheBackground active la mise en cache de l'arrière-plan de la vue.
En définissant la propriété ` dragMode `, nous déterminons 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 pointante, et le fait de faire glisser 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 de l’application avant d’entrer dans la boucle d’événements principale à l’aide de la fonction ` QApplication::exec()`.
Enfin, nous créons un ` QTimer ` et connectons son signal `timeout()` au slot `advance()` de la scène. À chaque fois que le minuteur se déclenche, la scène avance d'une image.
Nous configurons ensuite le minuteur pour qu’il se déclenche toutes les 1 000/33 millisecondes. Cela nous donnera une fréquence d’affichage de 30 images par seconde, ce qui est suffisamment rapide pour la plupart des animations. Réaliser l’animation à l’aide d’une seule connexion au minuteur pour faire avancer la scène garantit que toutes les souris sont déplacées en même temps et, surtout, qu’une seule mise à jour est envoyée à l’écran une fois que toutes les souris ont été déplacées.
© 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.