Académie
Pong (C++ 2/5)
Balle rebondissante

Balle rebondissante

Étape 2

Donnez vie à votre jeu avec une balle qui rebondit contre les bords de l'écran

  • Conditions "If else"
  • Utiliser les variables pour créer le mouvement
  • Vérifier les collisions
  • Faire rebondir !

Cette fois ça prend vie, votre programme bouge tout seul ! Les prémices de votre jeu de raquettes sont là.

Boïng boïng, ça rebondit de partout

Durée 20 minutes (et plus si affinité)

  • Avoir une Gamebuino META
  • Avoir fait l'Atelier Installation de la Gamebuino META
  • Avoir fait les Ateliers hello, world et compteur d'invités

A l'étape précédente vous avez fait un compteur d'invités. Vous stockiez sa valeur dans une variable de type int qu'on avait décidé d'appelercounter (logique !). On avait des conditions if pour changer la valeur du compteur lorsque l'on appuyait sur les boutons. Et finalement, on affichait un rectangle dont la position dépendait de la valeur de notre variable.

Si on résume, vous faisiez bouger un rectangle à l'écran en appuyant sur les boutons. On n'est pas loin d'avoir une raquette de pong ;) Mais un Pong sans balle c'est pas bien fun, c'est pourquoi on va maintenant faire une balle rebondissante.

Aller de l'avant

Dans le cas de notre balle rebondissante, il serait plus approprié d'appeler la variable positionX que counter, pour mieux refléter ce à quoi elle sert : stocker la position de la balle suivant l'axe horizontal X. On en profite pour ajouter une variable speedX qui va servir à stocker la vitesse horizontale de la balle.

Ensuite, la balle va avancer d'elle-même, pas besoin de condition if avec les boutons. On aurait donc quelque chose comme ça.

#include <Gamebuino-Meta.h>

int positionX = 32;
int speedX = 1;

void setup() {
  gb.begin();

}

void loop() {
  while (!gb.update());
  gb.display.clear();

  positionX = positionX + speedX;

  gb.display.print("positionX: ");
  gb.display.print(positionX);
  gb.display.print("\nspeedX: ");
  gb.display.print(speedX);

  gb.display.fillRect(positionX, 32, 4, 4);
}

À propos de l'écran

Quand vous avez appris à utiliser un graphique, vous avez sûrement vu quelque chose comme ça, où le centre est là où X et Y sont nuls, et les X et Y grandissent quand on se déplace vers le haut et vers la droite. Mais pour les écrans, ça ne marche pas tout à fait comme ça.

Les coordonnées sur l'écran de la Gamebuino META, comme n'importe quel autre écran, commencent en haut à gauche. En plus, les valeurs selon l'axe Y augmentent quand on descend (selon l'axe X, les valeurs augmentent vers la droite, comme sur un graphique classique).

Donc le coin à l'opposé - en bas à droite - a pour coordonnées (gb.display.width() - 1, gb.display.height() - 1).

Mais pourquoi est ce qu'on a soustrait 1 pour obtenir le coin en bas à droite ? C'est parce que le premier pixel (en haut à gauche) est en position (0, 0), et non en position (1, 1). Pour mieux comprendre pourquoi ce fait change tout, imaginons que notre écran avait seulement 4 pixels et que le premier avait comme coordonnée 0. Dans ce cas, quel serait la coordonnée du 4ème (dernier) pixel?

Ici, on voit facilement que le dernier pixel a une coordonnée de 3, et non de 4. Eh bien la même chose se passe avec l'écran de la Gamebuino ! Les pixels de droite ont une coordonnée X égale à gb.display.width() - 1. De la même manière, les pixels du bas ont une coordonnée Y de gb.display.height() - 1.

Remarque : L'écran fait 80 pixels de large par 64 pixels de haut. Vous pouvez vérifier la largeur avec gb.display.print(gb.display.width()); ou en comptant ;). Cependant, même si vous connaissez les dimensions de l'écran, il ne faut pas écrire 80 là où on peut mettre gb.display.width(), car c'est moins explicite et est une sorte d'offuscation légère.

Puisque nous parlons de l'écran de positionnement, j'en profite pour approfondir l'utilisation degb.display.drawRect(). Voici sa structure (que l'on a déjà vu) :

gb.display.drawRect(int x, int y, int largeur, int hauteur); Tout comme l'écran, les X et y sont passés par paramètres. Ce sont les coordonnées du coin en haut à gauche de notre rectangle. Donc on peut calculer la position de l'angle en bas à droite, et on obtient (x + largeur - 1, y + hauteur - 1).

Maintenant que nous avons vu tout ça, essayez d'afficher la balle plus haute sur l'écran. Essayez aussi de l'afficher tout en bas de l'écran :)

Garder la balle en vue

Essayons notre petit jeu et voyons ce qu'il se passe... Hé, mais la balle ne s'arrête pas ! Normal, on n'a mis aucune condition. Elle continue donc d'avancer, même en dehors de l'écran. Comment est-ce qu'on pourrait faire pour corriger ça ? Voilà la logique à avoir, en pseudo-code:

Si la balle atteint le bord droit
Alors aller à gauche
Si la balle atteint le bord gauche
Alors aller à droite

Deux questions se posent alors...

  • Comment faire pour que la balle aille à gauche ?
  • Comment savoir quand on a atteint le bord de l'écran ?

Je vais vous donner la solution, mais vous devriez essayer d'y réfléchir un peu avant de regarder. Quelques indices : essayez de jouer avec la valeur de speedX pour faire varier la vitesse. Qu'est ce qui se passe avec une vitesse de 2, de 3 ? Une vitesse négative ?

Maintenant que vous avez trouvé le truc pour faire aller la balle à gauche (n'est-ce pas ?), vous n'avez plus qu'à détecter quand positionX atteint le bord de l'écran. Les coordonnées horizontales en X peuvent varier de0 à gb.display.width(). Une valeur de 0 correspond au bord gauche de l'écran et une valeur de gb.display.width() correspond au bord droit.

Vous avez maintenant tous les éléments pour faire rebondir la balle de gauche à droite :)

Si vous bloquez, voilà un coup de pouce.

#include <Gamebuino-Meta.h>

int positionX = 32;
int speedX = 1;

void setup() {
  gb.begin();

}

void loop() {
  while (!gb.update());
  gb.display.clear();

  positionX = positionX + speedX;

  // Si la balle atteint ou dépasse le bord gauche
  if(positionX < 0){
    // On part à droite
    speedX = 1;
  }

  // Si la balle atteint ou dépasse le bord droit
  if(positionX > gb.display.width()){
    // On part à gauche
    speedX = -1;
  }

  gb.display.print("positionX: ");
  gb.display.print(positionX);
  gb.display.print("\nspeedX: ");
  gb.display.print(speedX);

  gb.display.fillRect(positionX, 32, 4, 4);
}

À vous de jouer !

Si la balle ne bouge qu'à l'horizontal, ça ne va pas être très excitant comme partie de Pong. Pourquoi ne pas créer de nouvelles variables positionY et speedY pour permettre à la balle de bouger verticalement ? Il faudra aussi des conditions pour détecter le bord de l'écran, cette fois avec gb.display.height() qui donne la hauteur de l'écran.

Faites nous rêver sur les réseaux sociaux avec #gamebuino #bouncingball , on vous suit de près ;)

Exemple de solution

Si vous êtes en panne d'inspiration, voilà ce qu'on a fait de notre côté :)

#include <Gamebuino-Meta.h> 

 




int positionX = 32;
int speedX = 2;
int positionY = 32;
int speedY = 1;
int ballSize = 8;




void setup() {
  gb.begin();




}




void loop() {
  while (!gb.update());
  gb.display.clear();




  // Mise à jour de la position horizontale
  positionX = positionX + speedX;




  // Si la balle atteint le bord gauche
  if(positionX < 0){
    // On part à droite
    speedX = 2;
  }




  // Si la balle atteint le bord droit
  if(positionX > gb.display.width() - ballSize){
    // On part à droite
    speedX = -2;
  }




  // Mise à jour de la position verticale
  positionY = positionY + speedY;




    // Si la balle atteint le bord haut
  if(positionY < 0){
    // On part en bas
    speedY = 1;
  }




  // Si la balle atteint le bord bas
  if(positionY > gb.display.height() - ballSize){
    // On part en haut
    speedY = -1;
  }




  gb.display.setColor(BROWN);
  gb.display.print("positionX: ");
  gb.display.print(positionX);
  gb.display.print("\nspeedX: ");
  gb.display.print(speedX);




  gb.display.print("\npositionY: ");
  gb.display.print(positionY);
  gb.display.print("\nspeedY: ");
  gb.display.print(speedY);




  gb.display.setColor(WHITE);
  gb.display.fillRect(positionX, positionY, ballSize, ballSize);
}

Etape suivante