IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Débuter dans la création d'interfaces graphiques avec PyQt 4

Image non disponible


précédentsommaire

XVI. Face au mur

Image non disponible

C'est l'exemple final : le jeu est complet ! Nous ajoutons à CannonField des accélérateurs de clavier ainsi que des événements de la souris. Nous dessinons un cadre autour de CannonField et plaçons un obstacle (un mur) pour rendre le jeu plus difficile.

XVI-A. Analyse du code ligne par ligne

XVI-A-1. t14/cannonfield.h

Le CannonField peut maintenant recevoir les événements de la souris permettant de pointer l'extrémité du canon sur lui et en le déplaçant avec la souris. Le CannonField comporte également un obstacle.

 
Sélectionnez
 protected:
     void paintEvent(QPaintEvent *event);
     void mousePressEvent(QMouseEvent *event);
     void mouseMoveEvent(QMouseEvent *event);
     void mouseReleaseEvent(QMouseEvent *event);

En plus des capteurs d'événements habituels, CannonField implémente trois capteurs d'événements pour la souris. Leurs noms parlent d'eux-mêmes.

 
Sélectionnez
     void paintBarrier(QPainter &painter);

Cette fonction privée dessine le mur.

 
Sélectionnez
     QRect barrierRect() const;

Cette fonction privée retourne le rectangle entourant l'obstacle.

 
Sélectionnez
     bool barrelHit(const QPoint &pos) const;

Cette fonction privée vérifie si un pixel est à l'intérieur du fût du canon.

 
Sélectionnez
     bool barrelPressed;

Cette variable privée est vraie si l'utilisateur a cliqué avec la souris sur le fût du canon.

XVI-A-2. t14/cannonfield.cpp

 
Sélectionnez
          barrelPressed = false;

Cette ligne a été ajoutée au constructeur. À l'initialisation, la souris ne clique pas sur le canon.

 
Sélectionnez
     } else if (shotR.x() > width() || shotR.y() > height()
                || shotR.intersects(barrierRect())) {

Maintenant que nous avons un obstacle, il y a une troisième façon de manquer le tir. Nous allons la tester.

 
Sélectionnez
 void CannonField::mousePressEvent(QMouseEvent *event)
 {
     if (event->button() != Qt::LeftButton)
         return;
     if (barrelHit(event->pos()))
         barrelPressed = true;
 }

C'est un gestionnaire d'événement Qt. Il est appelé quand l'utilisateur appuie sur un bouton de la souris alors que le curseur est sur le widget. Si l'événement n'est pas généré par le bouton gauche de la souris, nous retournons immédiatement. Sinon, nous vérifions si le curseur de la souris est bien situé à l'intérieur du fût du canon. Si c'est le cas, nous mettons barrelPressed à true. Notez que la fonction QMouseEvent::pos() retourne un point dans le système de coordonnées du widget.

 
Sélectionnez
 void CannonField::mouseMoveEvent(QMouseEvent *event)
 {
     if (!barrelPressed)
         return;
     QPoint pos = event->pos();
     if (pos.x() <= 0)
         pos.setX(1);
     if (pos.y() >= height())
         pos.setY(height() - 1);
     double rad = atan(((double)rect().bottom() - pos.y()) / pos.x());
     setAngle(qRound(rad * 180 / 3.14159265));
 }

C'est un autre gestionnaire d'événement Qt. Il est appelé quand l'utilisateur maintient appuyé le bouton de la souris alors que son curseur est situé à l'intérieur du widget et qu'il déplace la souris (vous pouvez utiliser Qt pour envoyer des événements de mouvement de souris même quand aucun bouton n'est pressé ; regardez QWidget::setMouseTracking()).

Ce gestionnaire replace le fût du canon selon la position du curseur.

Tout d'abord, si l'on ne clique pas sur le fût du canon, nous retournons. Ensuite, nous examinons la position du curseur : si le curseur est à gauche ou en-dessous du widget, nous ajustons sa position pour qu'il soit dans le widget.

Puis nous calculons l'angle formé par le bord inférieur du widget et la ligne imaginaire reliant l'angle supérieur gauche du widget et la position du curseur. Enfin, nous réglons l'angle de tir à la nouvelle valeur exprimée en degrés.

Souvenez-vous que setAngle() redessine le canon.

 
Sélectionnez
 void CannonField::mouseReleaseEvent(QMouseEvent *event)
 {
     if (event->button() == Qt::LeftButton)
         barrelPressed = false;
 }

Le gestionnaire d'événement Qt est appelé quand l'utilisateur relâche le bouton de la souris après avoir cliqué à l'intérieur du widget. Si le bouton gauche est relâché, nous pouvons être sûrs que le fût du canon n'est plus concerné par le clic. L'événement de dessin a droit à une ligne de plus :

 
Sélectionnez
     paintBarrier(painter);

paintBarrier() fait la même chose que paintShot(), paintTarget(), et paintCannon()

 
Sélectionnez
 void CannonField::paintBarrier(QPainter &painter)
 {
     painter.setPen(Qt::black);
     painter.setBrush(Qt::yellow);
     painter.drawRect(barrierRect());
 }

Cette fonction privée dessine l'obstacle comme un rectangle rempli de jaune avec un contour noir.

 
Sélectionnez
 QRect CannonField::barrierRect() const
 {
     return QRect(145, height() - 100, 15, 99);
 }

Cette fonction privée retourne le rectangle représentant l'obstacle. Nous plaçons le bord inférieur de l'obstacle en bas du widget.

 
Sélectionnez
 bool CannonField::barrelHit(const QPoint &pos) const
 {
     QMatrix matrix;
     matrix.translate(0, height());
     matrix.rotate(-currentAngle);
     matrix = matrix.inverted();
     return barrelRect.contains(matrix.map(pos));
 }

Cette fonction retourne true si le point est à l'intérieur du fût du canon ; sinon elle retourne false. Nous utilisons ici la classe QMatrix. Cette classe définit un système de coordonnées. Elle permet les même transformations que QPainter. Nous appliquons les mêmes transformations que pour dessiner l'extrémité dans la fonction paintCannon(). Tout d'abord nous appliquons au système de coordonnées une translation suivie d'une rotation. Nous devons alors vérifier si le point pos (dans les coordonnées du widget) se situe dans le fût du canon.. Pour cela, nous inversons la matrice de transformation. Cette matrice inversée correspond aux opérations inverses de celles que nous avons utilisées pour dessiner le fût du canon. Nous localisons le point pos en utilisant la matrice inversée et retournons true s'il est dans le rectangle d'origine de l'embout.

XVI-A-3. t14/gameboard.cpp

 
Sélectionnez
     QFrame *cannonBox = new QFrame;
     cannonBox->setFrameStyle(QFrame::WinPanel | QFrame::Sunken);

Nous créons et activons une QFrame et lui assignons un style de cadre. Le résultat est un cadre 3D autour de CannonField.

 
Sélectionnez
     (void) new QShortcut(Qt::Key_Enter, this, SLOT(fire()));
     (void) new QShortcut(Qt::Key_Return, this, SLOT(fire()));
     (void) new QShortcut(Qt::CTRL + Qt::Key_Q, this, SLOT(close()));

Ici, nous créons et activons trois objets QShortcut. Ces objets interceptent les événements du clavier envoyés à un widget et appellent des slots si certaines touches sont pressées. Notez qu'un objet QShortcut est l'enfant d'un widget et sera donc détruit quand ce widget sera détruit. QShortcut lui-même n'est pas un widget et n'a pas d'effet visible sur son parent.

Nous définissons trois raccourcis-clavier. Nous voulons que le slot fire() soit appelé quand l'utilisateur presse Entrée ou Retour. Nous voulons aussi que l'application se ferme quand les touches Ctrl et Q sont enfoncées en même temps. Au lieu de nous connecter à QCoreApplication::quit(), nous nous connectons cette fois à QWidget::Close, ce qui a le même effet que quit() puisque GameBoard est maintenant le widget principal de l'application.

Qt::CTRL, Qt::Key_Enter, Qt::Key_Return et Qt::Key_Q sont toutes des constantes déclarées dans l'espace de nommage de Qt.

 
Sélectionnez
     QVBoxLayout *cannonLayout = new QVBoxLayout;
     cannonLayout->addWidget(cannonField);
     cannonBox->setLayout(cannonLayout);

     QGridLayout *gridLayout = new QGridLayout;
     gridLayout->addWidget(quit, 0, 0);
     gridLayout->addLayout(topLayout, 0, 1);
     gridLayout->addLayout(leftLayout, 1, 0);
     gridLayout->addWidget(cannonBox, 1, 1, 2, 1);
     gridLayout->setColumnStretch(1, 10);
     setLayout(gridLayout);

Nous donnons à cannonBox son propre QVBoxLayout et nous ajoutons cannonField à cette mise en page. cannonField devient implicitement l'enfant de cannonBox. Puisqu'il n'y a rien d'autre dans la boîte, QVBoxLayout placera donc un cadre autour de CannonField. Nous mettons cannonBox, non CannonField, dans la mise en page de la grille.

XVI-B. Exécuter l'application

Maintenant le canon tire quand nous appuyons sur Entrée. Nous pouvons aussi régler l'angle de tir avec la souris. L'obstacle rend le jeu un peu plus difficile. Nous avons maintenant un joli cadre autour de CannonField.

XVI-C. Exercices

Écrire un jeu de "space invaders". Cet exercice a initialement été proposé par Igor Rafienko. Vous pouvez télécharger son jeu. Nouvel exercice : écrire un jeu de casse briques. Objectif final : aller plus loin et créer des chefs-d'oeuvre de programmation.


précédentsommaire

Copyright © 2009 - 2021 Developpez.com LLC Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.