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édentsommairesuivant

XIII. Tire-lui dessus !

Image non disponible

Dans cet exemple, nous introduisons un timer pour créer un tir animé.

XIII-A. Analyse du code ligne par ligne

XIII-A-1. t11/cannonfield.h

CannonField est maintenant capable de tirer.

 
Sélectionnez
     void shoot();

Appeler ce slot fera tirer le canon à condition qu'un projectile ne soit pas déjà dans les airs.

 
Sélectionnez
 private slots:
     void moveShot();

Ce slot privé est utilisé pour déplacer, en utilisant QTimer, le projectile quand il est en l'air.

 
Sélectionnez
 private:
     void paintShot(QPainter &painter);

Cette fonction privée dessine le tir.

 
Sélectionnez
     QRect shotRect() const;

Cette fonction privée renvoie, si un projectile est en l'air, le rectangle qui contient la trajectoire de tir ; sinon, elle retourne un rectangle indéfini

 
Sélectionnez
     int timerCount;
     QTimer *autoShootTimer;
     float shootAngle;
     float shootForce;
 };

Ces variables privées contiennent les informations qui décrivent le tir. Le timerCount calcule le temps écoulé depuis le dernier tir. shootAngle représente l'angle du canon et shootForce la puissance du canon.

XIII-A-2. t11/cannonfield.cpp

 
Sélectionnez
 #include <math.h>

Nous incluons math.h pour pouvoir utiliser les fonctions trigonométriques sin() et cos(). Une alternative pourrait être d'inclure l'en-tête cmath, qui est plus moderne. Malheureusement certaines plateformes UNIX ne le supportent toujours pas correctement.

 
Sélectionnez
 CannonField::CannonField(QWidget *parent)
     : QWidget(parent)
 {
     currentAngle = 45;
     currentForce = 0;
     timerCount = 0;
     autoShootTimer = new QTimer(this);
     connect(autoShootTimer, SIGNAL(timeout()), this, SLOT(moveShot()));
     shootAngle = 0;
     shootForce = 0;
     setPalette(QPalette(QColor(250, 250, 200)));
     setAutoFillBackground(true);
 }

Nous initialisons nos nouvelles variables privées et nous connectons le signal QTimer::timeout() à moveShot(). Nous déplaçons le projectile chaque fois que le timer du canon dépasse le délai choisi (5 millisecondes ; voir ci-dessous).

 
Sélectionnez
 void CannonField::shoot()
 {
     if (autoShootTimer->isActive())
         return;
     timerCount = 0;
     shootAngle = currentAngle;
     shootForce = currentForce;
     autoShootTimer->start(5);
 }

Cette fonction déclenche le tir, sauf si un projectile est déjà en l'air. timerCount est remis à zéro, shootAngle et shootForce sont définis avec les valeurs d'angle et de puissance choisies. Et, enfin, nous démarrons le timer.

 
Sélectionnez
 void CannonField::moveShot()
 {
     QRegion region = shotRect();
     ++timerCount;

     QRect shotR = shotRect();

     if (shotR.x() > width() || shotR.y() > height()) {
         autoShootTimer->stop();
     } else {
         region = region.unite(shotR);
     }
     update(region);
 }

moveShot() est le slot qui déplace le projectile ; il est appelé toutes les 5 millisecondes pendant que QTimer tire. Ses tâches sont de calculer la nouvelle position, de mettre à jour l'écran en dessinant le projectile à sa nouvelle position et, si nécessaire, d'arrêter le timer. Pour commencer, nous créons une QRegion qui conserve le rectangle de tir précédent. Une QRegion est capable de garder les caractéristiques de n'importe quelle région ; ici, nous en utilisons une pour simplifier le dessin. shotRect() renvoie le rectangle du slot. Ce sera expliqué en détail un peu plus bas. Ensuite, nous incrémentons le timerCount qui a pour effet de déplacer « d'un cran » le projectile sur sa trajectoire. Puis nous déterminons le nouveau rectangle de tir. Si le projectile a franchi le bord droit ou le bord inférieur du widget, nous arrêtons le timer ; sinon, nous ajoutons le nouveau shotRect() à la QRegion. Enfin, nous redessinons la QRegion, ce qui va envoyer un simple événement "paint" pour la mise à jour des seuls rectangles qui en ont besoin.

 
Sélectionnez
 void CannonField::paintEvent(QPaintEvent * /* event */)
 {
     QPainter painter(this);

     paintCannon(painter);
     if (autoShootTimer->isActive())
         paintShot(painter);
 }

Par rapport au chapitre précédent, l'événement "paint" a été simplifié. La majeure partie de la logique a été déplacée dans les nouvelles fonctions paintShot() et paintCannon().

 
Sélectionnez
 void CannonField::paintShot(QPainter &painter)
 {
     painter.setPen(Qt::NoPen);
     painter.setBrush(Qt::black);
     painter.drawRect(shotRect());
 }

Cette méthode privée dessine le tir en peignant un rectangle noir. Nous laissons de côté l'implémentation de paintCannon(), semblable à QWidget::paintEvent(), réimplanté du dernier chapitre.

 
Sélectionnez
 QRect CannonField::shotRect() const
 {
     const double gravity = 4;

     double time = timerCount / 20.0;
     double velocity = shootForce;
     double radians = shootAngle * 3.14159265 / 180;

     double velx = velocity * cos(radians);
     double vely = velocity * sin(radians);
     double x0 = (barrelRect.right() + 5) * cos(radians);
     double y0 = (barrelRect.right() + 5) * sin(radians);
     double x = x0 + velx * time;
     double y = y0 + vely * time - 0.5 * gravity * time * time;

     QRect result(0, 0, 6, 6);
     result.moveCenter(QPoint(qRound(x), height() - 1 - qRound(y)));
     return result;
 }

Cette fonction calcule les coordonnées du centre du projectile et renvoie le rectangle contenant le projectile. Il tient compte de la puissance initiale et de l'angle du canon et ajoute timerCount au temps passé. La formule utilisée est la formule standard de Newton pour un mouvement sans frottement, sous l'effet de la gravité. Pour simplifier, nous avons négligé l'effet relativiste d'Einstein. Nous calculons le point central dans un système où les coordonnées y augmentent vers le haut. Après avoir calculé la position du point central, nous créons un QRect de taille 6 x 6 que nous centrons (à l'aide des points que nous avons calculés). En même temps, nous convertissons le point en coordonnées pour le widget (voir le système de coordonnées). La fonction qRound() est une fonction inline définie dans QtGlobal, et incluse dans tous les en-têtes de Qt. qRound() convertit un nombre double (réel en double précision) dans l'entier integer le plus proche.

XIII-A-3. t11/main.cpp

 
Sélectionnez
 class MyWidget : public QWidget
 {
 public:
     MyWidget(QWidget *parent = 0);
 };

On ajoute simplement un bouton poussoir nommé Shoot.

 
Sélectionnez
     QPushButton *shoot = new QPushButton(tr("&Shoot"));
     shoot->setFont(QFont("Times", 18, QFont::Bold));

Dans le constructeur, nous créons et configurons le bouton exactement comme nous l'avions fait avec le bouton Quit.

 
Sélectionnez
     connect(shoot, SIGNAL(clicked()), cannonField, SLOT(shoot()));

Connexion du signal clicked() du bouton poussoir au slot shoot() de CannonField.

XIII-B. Exécuter l'application

Le canon peut tirer mais il n'a pas de cible.

XIII-C. Exercices

Représentez le tir par un cercle (suggestion : QPainter::drawEllipse() peut aider). Changez la couleur du canon quand il vise le ciel.


précédentsommairesuivant

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.