Ejemplo de colisión de ratones
Demuestra cómo animar elementos en una vista gráfica.
El ejemplo Colliding Mice muestra cómo utilizar el framework Graphics View para implementar elementos animados y detectar la colisión entre elementos.

Graphics View proporciona la clase QGraphicsScene para gestionar e interactuar con un gran número de elementos gráficos 2D personalizados derivados de la clase QGraphicsItem, y un widget QGraphicsView para visualizar los elementos, con soporte para zoom y rotación.
El ejemplo consta de una clase de elemento y una función principal: la clase Mouse representa los ratones individuales que amplían QGraphicsItem, y la función main() proporciona la ventana principal de la aplicación.
Primero revisaremos la clase Mouse para ver cómo animar elementos y detectar colisiones de elementos, y después revisaremos la función main() para ver cómo colocar los elementos en una escena y cómo implementar la vista correspondiente.
Definición de la clase Mouse
La clase mouse hereda de QGraphicsItem. La clase QGraphicsItem es la clase base para todos los ítems gráficos en el framework Graphics View, y proporciona una base ligera para escribir tus propios ítems personalizados.
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; };
Al escribir un elemento gráfico personalizado, debe implementar las dos funciones públicas virtuales puras de QGraphicsItem: boundingRect(), que devuelve una estimación del área pintada por el elemento, y paint(), que implementa la pintura real. Además, reimplementamos las funciones shape() y advance(). Reimplementamos shape() para devolver una forma precisa de nuestro elemento de ratón; la implementación por defecto simplemente devuelve el rectángulo delimitador del elemento. Reimplementamos advance() para manejar la animación y que todo suceda en una sola actualización.
Definición de la clase Mouse
Al construir un elemento de ratón, primero nos aseguramos de que todas las variables privadas del elemento que aún no se han inicializado directamente en la clase están correctamente inicializadas:
Mouse::Mouse() : color(QRandomGenerator::global()->bounded(256), QRandomGenerator::global()->bounded(256), QRandomGenerator::global()->bounded(256)) { setRotation(QRandomGenerator::global()->bounded(360 * 16)); }
Para calcular los distintos componentes del color del ratón, utilizamos QRandomGenerator.
A continuación, llamamos a la función setRotation() heredada de QGraphicsItem. Los elementos viven en su propio sistema de coordenadas local. Sus coordenadas suelen estar centradas alrededor de (0, 0), y éste es también el centro para todas las transformaciones. Llamando a la función setRotation() del ítem alteramos la dirección en la que el ratón comenzará a moverse.
Cuando QGraphicsScene decida avanzar la escena un fotograma, llamará a QGraphicsItem::advance() en cada uno de los ítems. Esto nos permite animar nuestro ratón utilizando nuestra reimplementación de la función 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; }
En primer lugar, no nos molestamos en hacer ningún avance si el paso es 0. Esto se debe a que advance() se llama dos veces: una vez con step == 0, indicando que los elementos están a punto de avanzar, y luego con step == 1 para el avance real. También nos aseguramos de que el ratón se mantiene dentro de un círculo con un radio de 150 píxeles.
Observe la función mapFromScene() proporcionada por QGraphicsItem. Esta función mapea una posición dada en coordenadas de escena, al sistema de coordenadas del ítem.
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); }
A continuación, intentamos evitar colisionar con otros ratones.
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))); }
Por último, calculamos la velocidad del ratón y su dirección ocular (para utilizarla al pintar el ratón), y fijamos su nueva posición.
La posición de un elemento describe su origen (coordenada local (0, 0)) en las coordenadas padre. La función QGraphicsItem::setPos() establece la posición del elemento en la posición dada en el sistema de coordenadas del padre. Para los elementos sin padre, la posición dada se interpreta como coordenadas de la escena. QGraphicsItem también proporciona una función mapToParent() para asignar una posición dada en coordenadas de elemento al sistema de coordenadas del padre. Si el elemento no tiene padre, la posición se asignará al sistema de coordenadas de la escena.
A continuación, es hora de proporcionar una implementación para las funciones virtuales puras heredadas de QGraphicsItem. Veamos primero la función boundingRect():
QRectF Mouse::boundingRect() const { qreal adjust = 0.5; return QRectF(-18 - adjust, -22 - adjust, 36 + adjust, 60 + adjust); }
La función boundingRect() define los límites exteriores del elemento como un rectángulo. Tenga en cuenta que el marco de la Vista Gráfica utiliza el rectángulo delimitador para determinar si el elemento requiere ser redibujado, por lo que toda la pintura debe hacerse dentro de este rectángulo.
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); }
La estructura de la Vista Gráfica llama a la función paint() para pintar el contenido del elemento; la función pinta el elemento en coordenadas locales.
Fíjate en el pintado de las orejas: siempre que un elemento del ratón colisiona con otros elementos del ratón, sus orejas se rellenan de rojo; de lo contrario, se rellenan de amarillo oscuro. Utilizamos la función QGraphicsScene::collidingItems() para comprobar si hay algún ratón colisionando. La detección de colisiones la realiza el framework Graphics View utilizando la intersección forma-forma. Todo lo que tenemos que hacer es asegurarnos de que la función QGraphicsItem::shape() devuelve una forma precisa para nuestro elemento:
QPainterPath Mouse::shape() const { QPainterPath path; path.addRect(-10, -20, 20, 40); return path; }
Debido a que la complejidad de la intersección arbitraria forma-forma crece con un orden de magnitud cuando las formas son complejas, esta operación puede consumir mucho tiempo. Un enfoque alternativo es reimplementar la función collidesWithItem() para proporcionar tu propio algoritmo personalizado de colisión de elementos y formas.
Esto completa la implementación de la clase Mouse; ahora está lista para su uso. Echemos un vistazo a la función main() para ver cómo implementar una escena para los ratones y una vista para mostrar el contenido de la escena.
La función Main()
La función main() proporciona la ventana principal de la aplicación, así como la creación de los elementos, su escena y una vista correspondiente.
int main(int argc, char **argv) { QApplication app(argc, argv);
En primer lugar, creamos un objeto de aplicación y creamos la escena:
QGraphicsScene scene; scene.setSceneRect(-300, -300, 600, 600);
La clase QGraphicsScene sirve como contenedor para QGraphicsItems. También proporciona funcionalidades que permiten determinar eficientemente la ubicación de los ítems, así como determinar qué ítems son visibles dentro de un área arbitraria de la escena.
Al crear una escena se recomienda establecer el rectángulo de la escena; el rectángulo que define la extensión de la escena. Es utilizado principalmente por QGraphicsView para determinar el área de desplazamiento por defecto de la vista, y por QGraphicsScene para gestionar la indexación de elementos. Si no se establece explícitamente, el rectángulo por defecto de la escena será el mayor rectángulo delimitador de todos los elementos de la escena desde que ésta se creó. Esto significa que el rectángulo crecerá cuando se añadan o muevan elementos en la escena, pero nunca se reducirá.
scene.setItemIndexMethod(QGraphicsScene::NoIndex);
La función de índice de elementos se utiliza para acelerar el descubrimiento de elementos. NoIndex implica que la localización de elementos tiene una complejidad lineal, ya que se buscan todos los elementos de la escena. Sin embargo, añadir, mover y eliminar elementos se hace en tiempo constante. Este enfoque es ideal para escenas dinámicas, en las que se añaden, mueven o eliminan muchos elementos continuamente. La alternativa es BspTreeIndex, que utiliza una búsqueda binaria para lograr algoritmos de localización de elementos de un orden más cercano a la complejidad logarítmica.
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); }
A continuación, añadimos los ratones a la escena.
QGraphicsView view(&scene); view.setRenderHint(QPainter::Antialiasing); view.setBackgroundBrush(QPixmap(":/images/cheese.jpg"));
Para poder visualizar la escena, también debemos crear un widget QGraphicsView. La clase QGraphicsView visualiza el contenido de una escena en una ventana desplazable. También nos aseguramos de que el contenido se renderiza utilizando antialiasing, y creamos el fondo de queso estableciendo el pincel de fondo de la vista.
La imagen utilizada para el fondo se almacena como un archivo binario en el ejecutable de la aplicación utilizando el sistema de recursos de Qt. El constructor QPixmap acepta tanto nombres de archivo que se refieren a archivos reales en disco como nombres de archivo que se refieren a los recursos incrustados de la aplicación.
view.setCacheMode(QGraphicsView::CacheBackground); view.setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); view.setDragMode(QGraphicsView::ScrollHandDrag);
A continuación establecemos el modo de caché; QGraphicsView puede almacenar en caché contenido pre-renderizado en un pixmap, que luego se dibuja en la ventana gráfica. El propósito de esta caché es acelerar el tiempo total de renderizado para áreas que son lentas de renderizar, por ejemplo: texturas, degradados y fondos con mezcla alfa. La propiedad CacheMode indica qué partes de la vista se almacenan en caché, y la bandera CacheBackground activa el almacenamiento en caché del fondo de la vista.
Estableciendo la propiedad dragMode, definimos lo que debe ocurrir cuando el usuario hace clic en el fondo de la escena y arrastra el ratón. La bandera ScrollHandDrag hace que el cursor se convierta en una mano que señala, y arrastrando el ratón se desplazarán las barras de desplazamiento.
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(); }
Al final, establecemos el título y el tamaño de la ventana de la aplicación antes de entrar en el bucle de eventos principal utilizando la función QApplication::exec().
Por último, creamos un QTimer y conectamos su señal timeout() a la ranura advance() de la escena. Cada vez que se active el temporizador, la escena avanzará un fotograma.
Entonces le decimos al temporizador que se dispare cada 1000/33 milisegundos. Esto nos dará una velocidad de 30 fotogramas por segundo, que es lo suficientemente rápido para la mayoría de las animaciones. Hacer la animación con una sola conexión de temporizador para avanzar la escena asegura que todos los ratones se mueven en un punto y, lo que es más importante, sólo una actualización se envía a la pantalla después de que todos los ratones se han movido.
© 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.