Casse-briques

Créations

JulienGio

il y a 2 ans

Casse briques

Durée 45 minutes

Niveau Débutant

Prérequis

  • Avoir complété le Chapitre 1
  • L'atelier TapTap, Attrap'Oeuf

Le casse-briques est un jeu extrêmement connu. Il est sorti pour la toute première fois en 1975 et une quantité incroyable de clones et variantes ont été créés depuis (et encore aujourd'hui, surtout sur mobile). Dans cet atelier, nous allons continuer la tradition en faisant notre propre clone :P

L'objectif sera d'approfondir nos capacités d'utilisation des tableaux et des boucles for. Nous verrons ici comment utiliser les boucles imbriquées et maitriser les tableaux 2D. Pour finir, je vous donnerais de quoi exercer tout ça en modifiant les briques.

Je vais commencer en estimant que vous êtes capables de programmer le casse-briques sans les briques. Juste la raquette, la balle, et les murs. Si vous avez du mal, je vous conseille de relire et de refaire l'atelier Pong.

// Breakout.ino //
#include <Gamebuino-Meta.h>

// Caractèristiques de la raquette
int raquetteX;
const int RAQUETTE_Y = 58;
const int RAQUETTE_LARGEUR = 10;
const int RAQUETTE_HAUTEUR = 2;

// Caractèristiques de la balle
int balleX;
int balleY;
int balleVX;
int balleVY;
const int BALLE_TAILLE = 3;

void setup() {
  gb.begin();
  reinitialiser();  // Situé dans miseAJour.ino
}

void loop() {
  while (!gb.update());
  // INPUTS //
  entrees();

  // LOGIC //
  miseAJour();

  // DRAW //
  affichage();
}
// entrees.ino //




void entrees() { if (gb.buttons.repeat(BUTTON_LEFT, 1) && raquetteX > 0) { raquetteX -= 3; } else if (gb.buttons.repeat(BUTTON_RIGHT, 1) && raquetteX < gb.display.width() - RAQUETTE_LARGEUR) { raquetteX += 3; } }



// miseAJour.ino //
void miseAJour() {
  // MAJ Balle
  balleX += balleVX;
  balleY += balleVY;



// Collisions avec les murs if (balleX < 0 || balleX > gb.display.width() - BALLE_TAILLE) { balleVX *= -1; } if (balleY < 0) { balleVY *= -1; } else if (balleY > gb.display.height()) { reinitialiser(); }

// Collision avec la raquette if (gb.collide.rectRect(balleX, balleY, BALLE_TAILLE, BALLE_TAILLE, raquetteX, RAQUETTE_Y, RAQUETTE_LARGEUR, RAQUETTE_HAUTEUR)) { balleVY = -1; // Haut } }

void reinitialiser() { balleX = random(0, gb.display.width() - BALLE_TAILLE); balleY = RAQUETTE_Y - BALLE_TAILLE - 1; // Juste au dessus de la raquette balleVX = 1; // Droite balleVY = -1; // Haut

}


// affichage.ino //
void affichage() {
  gb.display.clear();



// Raquette gb.display.fillRect(raquetteX, RAQUETTE_Y, RAQUETTE_LARGEUR, RAQUETTE_HAUTEUR);

// Balle gb.display.fillRect(balleX, balleY, BALLE_TAILLE, BALLE_TAILLE); }




Ici, nous avons une structure de code très similaire aux derniers jeux que nous avons fait. Nos trois fonctions entrees(), miseAJour() et affichage() sont présentes, et j'ai ajouté une quatrième fonction : reinitialiser() dans miseAJour.ino. Elle sert à remettre la balle dans l'écran en début de partie. Elle servira aussi plus tard à remettre les briques cassées. En parlant de briques...

Les briques

Comment allons-nous représenter les briques ? Pour comprendre comment nous allons parvenir à avoir une grille de briques, commençons par afficher une simple ligne horizontale sans couleurs, comme ceci :


Des briques alignées ? Cela ressemble beaucoup à notre jeu de TapTap. Pour ce jeu-là, nous avions créé un tableau d'entiers qui représentait les briques.

La différence ici, c'est que nous ne voulons pas savoir si la brique est 'à gauche' ou 'à droite'. Mais plutôt, si la brique existe ou pas ! Et nous n'allons pas nous casser la tête, au début, notre tableau sera rempli de 1, et puis à chaque fois que la balle viendra frapper une brique, sa valeur passera à 0.


// Breakout.ino //

// Raquette //
// Balle //


// Briques. Chaque entier correspond à une couleur
const int GRILLE_TAILLE = 8;  // Grille carré
int briques[GRILLE_TAILLE];
const int BRIQUE_LARGEUR = gb.display.width() / GRILLE_TAILLE - 2;
const int BRIQUE_HAUTEUR = 3;

// setUp() //
// loop() //

Nous allons devoir afficher une ligne de 8 briques. Et à la place de bricoler pour obtenir la largeur parfaite pour que toutes les briques rentrent dans l'écran, on peut tout simplement la calculer :D Enfin, "tout simplement"... il faut réfléchir pour trouver la bonne formule. En divisant la largeur de l'écran par le nombre de briques on obtient la largeur maximale des briques pour qu'elles rentrent toutes. Mais pour mieux les discerner, on place un trou de 2 pixels entre chaque brique. C'est comme ça qu'on obtient BRIQUE_LARGEUR = gb.display.width() / GRILLE_TAILLE - 2;.


// miseAJour.ino //
void miseAJour() {
  // MAJ balle //
  // Collisions avec murs //
  // Collision avec raquette //

  // Collision avec les briques
  for (int x = 0; x < GRILLE_TAILLE; x += 1) {
    if (briques[x] == 0) {  // Ignorer les briques nulles
      continue;  // 'Continue' force la boucle à passer a recommencer (et donc incrémenter)
    }

    int briqueX = x * (BRIQUE_LARGEUR + 2) + 1;
    int briqueY = 10;  // Hauteur des briques
    if (gb.collide.rectRect(balleX, balleY, BALLE_TAILLE, BALLE_TAILLE,
                            briqueX, briqueY, BRIQUE_LARGEUR, BRIQUE_HAUTEUR)) {
      balleVY *= -1;
      briques[x] = 0;  // Détruire la brique

      // Verifier qu'il reste encore des briques
      bool encoreDesBriques = false;  // Si ce booléen reste faux, alors il n'y a plus de briques
      for (int i = 0; i < GRILLE_TAILLE; i += 1) {
        if (briques[x] == 0) {  // On a trouver une brique!
          encoreDesBriques = true;  // Il reste encore au moins une brique
        }

      }

      if (encoreDesBriques == false) {  // S'il n'y a plus de briques
        reinitialiser();
      }
    }

  }
}

void reinitialiser() {
  // Remise à zero de la balle //

  for (int i = 0; i < GRILLE_TAILLE; i += 1) {
    briques[i] = 1;  // Faire réapparaitre toutes les briques
  }
}
// affichage.ino //




void affichage() { // raquette // // Balle //

// Briques for (int x = 0; x < GRILLE_TAILLE; x += 1) { if (briques[x] == 0) { continue; // Passer à la brique suivante }

int briqueX = x * (BRIQUE_LARGEUR + 2) + 1;
int briqueY = 20;
gb.display.fillRect(briqueX, briqueY, BRIQUE_LARGEUR, BRIQUE_HAUTEUR);

}
}




Afin de pouvoir afficher et calculer les collisions des briques, il nous faut leur taille et leur position. Toutes les briques on la même taille (BRIQUE_LARGEUR et BRIQUE_HAUTEUR). Il reste donc la position. Pour cela, nous allons calculer les coordonnées du coin en haut à gauche de chaque brique. Je les ai appelées briqueX et briqueY. Ces variables sont déclarées dans les accolades de notre boucle. Donc elle n'existent pas en dehors de ces accolades. Ce fait n'est pas seulement vrai pour les boucles, c'est vrai à chaque fois qu'il y a des accolades. Toutes variables déclarées à l'intérieur de deux accolades, n'existe pas à l'extérieur des ces accolades, on appelle ça la portée d'une variable.

Il y a deux exceptions. Tout d'abord, les variables statiques, nous vous laissons explorer ce que c'est si vous êtes curieux ;). L'autre exception se trouve dans la boucle for, quand on écrit for (int i = 0; i < var; i += 1) { ... }, on considère que i est déclaré dans les accolades de la boucle.

Pour en revenir au calcul de nos briques, nous pouvons directement définir la position Y des briques. Vu qu'elles sont toutes alignées, on peut simplement leur donner une hauteur de 10 (plus tard, quand nous ajouterons d'autres briques, cette valeur changera). Pour briqueX, il faut pouvoir déterminer sa position en fonction de son indice dans notre tableau. Dans mon code, l'indice s'appelle x. Donc quand x vaut 0, on veut que la brique s'affiche tout à gauche. La brique d'après s'afficher à sa droite, avec un espace de deux pixels. Etc. Dans ce cas briqueX = x * (BRIQUE_LARGEUR + 2);. Dans mon code, j'ai ajouté 1 à la position de toutes les briques pour pas que la première brique soit collée au bord de l'écran.

Dernière chose à propos de ces deux fichiers. J'utilise le mot clé continue au début de mes boucles. continue force la boucle ignorer le reste de son code. En termes plus concrets, continue passe à l'indice suivant. Je l'appelle quand il n'y a pas de brique (i.e. brique[x] = 0), car c'est inutile de calculer la position d'une brique inexistante ;)


Et voilà ! Nous avons complété notre casse-briques... mais il n'a qu'une seule rangé de briques :/

Des trucs dans des trucs

Pour avoir plusieurs lignes, on pourrait copier-coller le code qui nous a permis de faire une rangé. 8 copier-coller = 8 rangées. Mais, comme nous l'avons dit plusieurs fois auparavant, si on se retrouve à copier-coller, c'est que ce n'est pas la bonne approche. Alors comment allons-nous mémoriser une grille de briques ?

Pour trouver la réponse il faut penser à ce que nous avons fait jusqu'à présent, et ce qu'il nous manque. Nous avons une rangée de briques. Il nous faut un certain nombre de rangées. Logiquement, on devrait créer un tableau de rangées. Or chaque rangée est elle-même un tableau. Nous allons donc crée un tableau de tableaux.

// Tableau de tableaux d'entiers




int tabDetab[5][3] = { {00, 01, 02}, {10, 11, 12}, {20, 21, 22}, {30, 31, 32}, {40, 41, 42} };




On peut comprendre pourquoi un tableau de tableaux est souvent appeler tableau 2D, quand on les déclare, ils ressemblent à des grilles ou des quadrillages. Et ça tombe bien ! Nous cherchons à faire une "grille" de briques :D


// Breakout.ino //

// Raquette //
// Balle //


// Briques. Chaque entier correspond à une couleur
const int GRILLE_TAILLE = 8;  // Grille carré
int briques[GRILLE_TAILLE][GRILLE_TAILLE];  // Tableau 2D
const int BRIQUE_LARGEUR = gb.display.width() / GRILLE_TAILLE - 2;
const int BRIQUE_HAUTEUR = 3;

// setUp() //
// loop() //

Pour ne pas s'emmêler les pinceaux, nous allons travailler avec une grille carrée de 8 par 8 briques. Chaque brique aura une hauteur de 3 pour ne pas occuper trop de place. Mais les plus gros changements sont dans l'affichage et les détections de collisions.

// affichage.ino //
void affichage() {
  for (int rangee = 0; rangee < GRILLE_TAILLE; rangee += 1) {
    for (int colonne = 0; colonne < GRILLE_TAILLE; colonne += 1) {
      if (briques[rangee][colonne] == 0) {
        continue;
      }

      int briqueX = colonne * (BRIQUE_LARGEUR + 2) + 1;
      int briqueY = rangee * (BRIQUE_HAUTEUR + 2) + 1;
      gb.display.fillRect(briqueX, briqueY, BRIQUE_LARGEUR, BRIQUE_HAUTEUR);
    }
  }
}

Et oui ! Pour traverser tous les éléments d'un tableau 2D, il faut placer une boucle dans une boucle ! Ça s'appelle des boucles imbriquées. Ici, nous allons traverser toutes les rangées, et pour chaque rangée, nous allons passer par chacun de ses éléments. La tâche qui prête à confusion chez les débutants est d'accéder au bon élément du tableau 2D. Pour les tableaux 2D, on place d'abord la rangée, puis la colonne. Une manière de s'en rappeler est de se demander Que représente briques[0] ? C'est le tout premier élément de notre tableau, c'est une rangée.

L'autre chose qui change par rapport à la dernière fois est la valeur de briqueY. Nos briques ne sont plus toutes alignées. Mais la logique de la formule est analogique à celle pour briqueX.

// miseAJour.ino //
void miseAJour() {
  // MAJ Balle //
  // Collisions avec les murs et raquette //

  // Collisions avec les briques
  for (int rangee = 0; rangee < GRILLE_TAILLE; rangee += 1) {
    for (int colonne = 0; colonne < GRILLE_TAILLE; colonne += 1) {
      if (briques[rangee][colonne] == 0) {  // Ignorer les briques nulles
        continue;
      }

      int briqueX = colonne * (BRIQUE_LARGEUR + 2) + 1;
      int briqueY = rangee * (BRIQUE_HAUTEUR + 2) + 1;
      if (gb.collide.rectRect(balleX, balleY, BALLE_TAILLE, BALLE_TAILLE,
                              briqueX, briqueY, BRIQUE_LARGEUR, BRIQUE_HAUTEUR)) {
        balleVY *= -1;
        briques[rangee][colonne] = 0;  // Détruire la brique

        // Verifier qu'il reste encore des briques
        bool plusDeBriques = true;  // Si cet entier reste vrai, alors il n'y a plus de briques
        for (int x = 0; x < GRILLE_TAILLE; x += 1) {
          for (int y = 0; y < GRILLE_TAILLE; y += 1) {
            if (briques[y][x] == 0) {  // On a trouvé une brique!
              plusDeBriques = false;  // ne pas reinitialiser
            }
          }
        }

        if (plusDeBriques) {
            reinitialiser();
        }
      }

    }
  }


}

void reinitialiser() {
  // Remise à zero de la balle //

  for (int rangee = 0; rangee < GRILLE_TAILLE; rangee += 1) {
    for (int colonne = 0; colonne < GRILLE_TAILLE; colonne += 1) {
      briques[rangee][colonne] = 1;  // Faire réapparaitre toutes les briques
    }
  }
}

Les changements de miseAJour.ino sont les mêmes que ce de affichage.ino. Si vous avez tout suivi jusqu'à présent, vous devriez avoir ceci sur votre console :


Maintenant, vous avez un jeu de casse-briques complet, mais avant de vous passer la main, ajoutons deux petites choses dans notre code.

Arc-En-Ciel

Pour finir en beauté, ajoutons des couleurs à nos briques. Pour ajouter des couleurs, il faut qu'on puisse différencier une brique verte d'une brique bleue. Pour cela, nous allons assigner la valeur de briques à une couleur. Donc si la brique [0][0] vaut 4, elle sera jaune par exemple. 0 sera toujours la valeur qui signifie 'pas de brique', mais les valeurs de 1 à 8 correspondront à des couleurs. Comme ça, notre logique de jeu ne sera pas affectée par ce changement :)

// affichage.ino //
void affichage() {
  // Raquette //
  // Balle //

  // Briques
  const Color arcEnCiel[8] = {
    WHITE, PURPLE, RED, YELLOW, LIGHTGREEN, GREEN, DARKBLUE, BLUE
  };
  for (int rangee = 0; rangee < GRILLE_TAILLE; rangee += 1) {
    for (int colonne = 0; colonne < GRILLE_TAILLE; colonne += 1) {
      // Sauter les briques égales à 0 //

      gb.display.setColor(arcEnCiel[ briques[rangee][colonne] - 1 ]);

      // Afficher la brique //
    }
  }
}

Pour avoir des briques colorées, il suffit de placer un gb.display.setColor() avec la bonne couleur. Le problème maintenant est de pouvoir récupérer la couleur associée.

Nous allons prendre avantage du fait que nous avons lié chaque couleur à un entier. Nous allons créer un tableau de couleurs (oui, Color est un type de variable sur Gamebuino !). La première couleur du tableau sera celle qui correspond à une valeur de 1. La deuxième sera associée à 2. La troisième à 3. Etc. Donc l'indice de de chaque couleur sera presque égale à la valeur de la brique. "presque" parce-que 1 correspond à la première couleur qui a un indice de 0. Donc indice couleur = valeur brique - 1.



Maintenant on peut modifier notre fonction reinitialiser() pour générer des briques de couleur. Par exemple voici une grille toute rouge :


for (int rangee = 0; rangee < GRILLE_TAILLE; rangee += 1) {
  for (int colonne = 0; colonne < GRILLE_TAILLE; colonne += 1) {
    briques[rangee][colonne] = 3;
  }
}

Voici le code complet du casse-briques :

// breakout.ino //


#include <Gamebuino-Meta.h>

// Briques. Chaque entier correspond à une valeur const int GRILLE_TAILLE = 8; // Grille carré int briques[GRILLE_TAILLE][GRILLE_TAILLE]; const int BRIQUE_LARGEUR = gb.display.width() / GRILLE_TAILLE - 2; const int BRIQUE_HAUTEUR = 3;

// Caractèristiques de la raquette int raquetteX; const int RAQUETTE_Y = 58; const int RAQUETTE_LARGEUR = 10; const int RAQUETTE_HAUTEUR = 2;

// Caractèristiques de la balle int balleX; int balleY; int balleVX; int balleVY; const int BALLE_TAILLE = 3;

void setup() { gb.begin(); reinitialiser(); }

void loop() { while (!gb.update()); // INPUTS // entrees();

// LOGIC // miseAJour();

// DRAW // affichage(); }

// entrees.ino //


void entrees() { if (gb.buttons.repeat(BUTTON_LEFT, 1) && raquetteX > 0) { raquetteX -= 3; } else if (gb.buttons.repeat(BUTTON_RIGHT, 1) && raquetteX < gb.display.width() - RAQUETTE_LARGEUR) { raquetteX += 3; } }


void miseAJour() {
  // MAJ Balle
  balleX += balleVX;
  balleY += balleVY;



// Collisions avec les murs if (balleX < 0 || balleX > gb.display.width() - BALLE_TAILLE) { balleVX *= -1; } if (balleY < 0) { balleVY *= -1; } else if (balleY > gb.display.height()) { reinitialiser(); }

// Collisions avec les briques for (int rangee = 0; rangee < GRILLE_TAILLE; rangee += 1) { for (int colonne = 0; colonne < GRILLE_TAILLE; colonne += 1) { if (briques[rangee][colonne] == 0) { // Ignorer les briques nulles continue; }

  int briqueX = colonne * (BRIQUE_LARGEUR + 2) + 1;
  int briqueY = rangee * (BRIQUE_HAUTEUR + 2) + 1;
  if (gb.collide.rectRect(balleX, balleY, BALLE_TAILLE, BALLE_TAILLE,
                          briqueX, briqueY, BRIQUE_LARGEUR, BRIQUE_HAUTEUR)) {
    balleVY *= -1;
    briques[rangee][colonne] = 0;  // Détruire la brique

    // Verifier qu'il reste encore des briques
    bool plusDeBriques = true;  // Si cet entier reste vrai, alors il n'y a plus de briques
    for (int x = 0; x < GRILLE_TAILLE; x += 1) {
      for (int y = 0; y < GRILLE_TAILLE; y += 1) {
        if (briques[y][x] == 0) {  // On a trouver une brique!
          plusDeBriques = false;  // ne pas reinitialiser
        }
      }
    }

    if (plusDeBriques) {
      reinitialiser();
    }
  }

}

}

// Collisions avec la raquette
if (gb.collide.rectRect(balleX, balleY, BALLE_TAILLE, BALLE_TAILLE,
raquetteX, RAQUETTE_Y, RAQUETTE_LARGEUR, RAQUETTE_HAUTEUR)) {
balleVY = -1; // Haut
}

}

void reinitialiser() {
// Balle
balleX = random(0, gb.display.width() - BALLE_TAILLE);
balleY = RAQUETTE_Y - BALLE_TAILLE - 1; // Juste au dessus de la raquette
balleVX = 1; // Droite
balleVY = -1; // Haut

// Briques
for (int rangee = 0; rangee < GRILLE_TAILLE; rangee += 1) {
for (int colonne = 0; colonne < GRILLE_TAILLE; colonne += 1) {
briques[rangee][colonne] = 1;
}
}
}


// affichage.ino //
void affichage() {
  gb.display.clear();



// Raquette gb.display.fillRect(raquetteX, RAQUETTE_Y, RAQUETTE_LARGEUR, RAQUETTE_HAUTEUR);

// Balle gb.display.fillRect(balleX, balleY, BALLE_TAILLE, BALLE_TAILLE);

// Briques const Color arcEnCiel[8] = { WHITE, PURPLE, RED, YELLOW, LIGHTGREEN, GREEN, DARKBLUE, BLUE }; for (int rangee = 0; rangee < GRILLE_TAILLE; rangee += 1) { for (int colonne = 0; colonne < GRILLE_TAILLE; colonne += 1) { if (briques[rangee][colonne] == 0) { continue; } gb.display.setColor(arcEnCiel[ briques[rangee][colonne] - 1 ]);

  int briqueX = colonne * (BRIQUE_LARGEUR + 2) + 1;
  int briqueY = rangee * (BRIQUE_HAUTEUR + 2) + 1;
  gb.display.fillRect(briqueX, briqueY, BRIQUE_LARGEUR, BRIQUE_HAUTEUR);
}

}
}



A vous de jouer!



Pour recréer le fameux casse-briques, nous avons repris tout ce qu'on a vu jusqu'à présent dans le chapitre 2 : les tableaux, les constantes, les boucles for, et les fonctions. Mais créer une grille de brique nous a appris à manipuler des tableaux 2D (tableaux de tableaux) et des boucles imbriquées. Je vous propose un exercice pratique qui vérifie si vous avez bien assimilé tout ça ^^. La consigne est simple, en modifiant seulement les boucles for dans reinitialiser(), recréez les dix formaitions de briques ci-dessous. Les 2-3 derniers sont difficiles, si vous bloquez, n'hésitez pas à jeter un œil à nos solutions plus bas.

Pour éviter d'avoir des erreurs, commencez par donner une valeur de 0 à toutes les briques avant d'en afficher d'autres au début de votre fonction. Je vous donne la structure de reinitialiser() :

void reinitialiser() {
  // Balle
  balleX = random(0, gb.display.width() - BALLE_TAILLE);
  balleY = RAQUETTE_Y - BALLE_TAILLE - 1;  // Juste au dessus de la raquette
  balleVX = 1;  // Droite
  balleVY = -1;  // Haut

  // Briques
  // Tout effacer avant d'en ajouter
  for (int rangee = 0; rangee < GRILLE_TAILLE; rangee += 1) {
    for (int colonne = 0; colonne < GRILLE_TAILLE; colonne += 1) {
      briques[rangee][colonne] = 0;
    }
  }

  ///////////////////////////
  // PLACEZ VOTRE CODE ICI //
  ///////////////////////////
}


  • Astuce #1: Pour le 3, il faut jouer avec l'incrémentation de la boucle for.
  • Astuce #2: Pour le 9, modifiez un aspect du 8.
  • Astuce #3: Pour le 10, il vous faudra un total de trois boucles for.

Partage tes créations sur les réseaux avec #gamebuino #atelier #cassebriques, on les regarde tous ;)

Exemple de Solution

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

// 1 - Rectangle en haut à gauche //
for (int rangee = 0; rangee < 3; rangee += 1) {
  for (int colonne = 0; colonne < 5; colonne += 1) {
    briques[rangee][colonne] = 1;
  }
}


// 2 - Rectangle centré //
for (int rangee = GRILLE_TAILLE / 2 - 1; rangee < GRILLE_TAILLE / 2 + 1; rangee += 1) {
  for (int colonne = GRILLE_TAILLE / 2 - 2; colonne < GRILLE_TAILLE / 2 + 2; colonne += 1) {
    briques[rangee][colonne] = 1;
  }
}


// 3 - Une ligne sur 2 //
for (int rangee = 0; rangee < GRILLE_TAILLE; rangee += 1) {
  for (int colonne = 0; colonne < GRILLE_TAILLE; colonne += 2) {
    briques[rangee][colonne] = 1;
  }
}


// 4 - Rectangle unicolore //
for (int rangee = 0; rangee < GRILLE_TAILLE; rangee += 1) {
  for (int colonne = 0; colonne < GRILLE_TAILLE; colonne += 1) {
    briques[rangee][colonne] = 5;
  }
}


// 5 - Rectangle arc-en-ciel horizontal //
for (int rangee = 0; rangee < GRILLE_TAILLE; rangee += 1) {
  for (int colonne = 0; colonne < GRILLE_TAILLE; colonne += 1) {
    briques[rangee][colonne] = colonne + 1;
  }
}


// 6 - Rectangle arc-en-ciel vertical //
for (int colonne = 0; colonne < GRILLE_TAILLE; colonne += 1) {
  for (int rangee = 0; rangee < GRILLE_TAILLE; rangee += 1) {
    briques[rangee][colonne] = rangee + 1;
  }
}


// 7 - Triangle en bas à gauche - n'oubliez pas la diagonale ;) //
for (int rangee = 0; rangee < GRILLE_TAILLE; rangee += 1) {
  for (int colonne = 0; colonne < rangee + 1; colonne += 1) {
    briques[rangee][colonne] = rangee + 1;
  }
}


// 8 - Triangle en haut à gauche //
for (int rangee = 0; rangee < GRILLE_TAILLE; rangee += 1) {
  for (int colonne = 0; colonne < GRILLE_TAILLE - rangee; colonne += 1) {
    briques[rangee][colonne] = colonne + 1;
  }
}


// 9 - Triangle en haut à droite avec arc-en-ciel en diagonale //
for (int rangee = 0; rangee < GRILLE_TAILLE; rangee += 1) {
  for (int colonne = 0; colonne < GRILLE_TAILLE - rangee; colonne += 1) {
    briques[rangee][colonne + rangee] = colonne + 1;
  }
}


// 10 - Triangle symertique //
for (int colonne = 0; colonne < GRILLE_TAILLE; colonne += 1) {
  for (int rangee = 0; rangee < colonne / 2 + 1; rangee += 1) {
    briques[rangee][colonne] = rangee + 1;
  }
  for (int rangee = colonne / 2 + 1; rangee < colonne + 1; rangee += 1) {
    briques[rangee][colonne] = (colonne + 1) - rangee;
  }
}


Par Julien Giovinazzo

Des questions / commentaires / suggestions ? Laissez-nous un commentaire plus bas !

Voir la création

jicehel

NEW il y a 2 ans

Très bien Julien  ;)  J'ai trouvé des astuces que j'aurais pu appliquer dans mon casse-briques. Si j'y retouche, je pense que j'utiliserais le collide par exemple.

Sinon quelques petites fautes à corriger:  au début: tu met une fois casse brique (mieux vaut ajouter le s car tu le mets avant et après. Plus bas "en estiment" à corriger en "en estimant". Dans la section "Les briques", il est écrit: "Mais mieux les discerner", je pense qu'un pour a disparu et que tu voulais écrire "Mais pour mieux les discerner". Plus loin: " l'intérieur de deux accolades, n'existe pas à l'extérieur" => " l'intérieur de deux accolades, n'existent pas à l'extérieur". Avant Arc en ciel "vous devrez" => "vous devriez".

Après ce joli tuto j'espère que plein de lecteur auront des idées pour améliorer mon Breakout (casse-briques en anglais).  :D  

Juice_Lizard

NEW il y a 2 ans

"Un seul point-virgule vous manque et rien n'est compilé." (Lamartine à la plage)

Il faut enlever l'espace en trop dans #include <Gamebuino-Meta.h >

Et corriger les collide.rectRect en collideRectRect

Et là ça marche. Merci pour ce tuto.

Aurélien Rodot

il y a 2 ans

Et corriger les collide.rectRect en collideRectRect

collideRectRect est obsolète, tu devrais mettre ta library à jour et utiliser gb.collide.rectRect :)

Aurélien Rodot

NEW il y a 2 ans

Juice_Lizard Juice_Lizard

Et corriger les collide.rectRect en collideRectRect

collideRectRect est obsolète, tu devrais mettre ta library à jour et utiliser gb.collide.rectRect :)

Juice_Lizard

il y a 2 ans

Ma bibliothèque est déjà à jour, version 1.1.0, je ne vois pas de version plus récente. Dans votre article "Référence", c'est toujours écrit "collideRectRect".

Juice_Lizard

NEW il y a 2 ans

Aurélien Rodot Aurélien Rodot

Ma bibliothèque est déjà à jour, version 1.1.0, je ne vois pas de version plus récente. Dans votre article "Référence", c'est toujours écrit "collideRectRect".

Aurélien Rodot

il y a 2 ans

La nouvelle 1.2.0 est maintenant en ligne ! https://github.com/Gamebuino/Gamebuino-META/releases/tag/1.2.0

Il faudra attendre quelques heures pour qu'elle apparaisse directement dans Arduino.

Aurélien Rodot

NEW il y a 2 ans

Juice_Lizard Juice_Lizard

La nouvelle 1.2.0 est maintenant en ligne ! https://github.com/Gamebuino/Gamebuino-META/releases/tag/1.2.0

Il faudra attendre quelques heures pour qu'elle apparaisse directement dans Arduino.

Grungy

NEW il y a 2 ans

Bonjour je débute et suis les tuto 1 par un et pour celui-ci je sèche mon casse brique reste tout blanc aucune couleurs arc en ciel, meme en faisant un copié/collé du code...


Codnpix

NEW il y a 2 ans

Bonjour, merci pour ce tuto ! Je suis arrivé au bout mais je crois que j'ai trouvé une petite erreur, ou alors je n'ai pas saisi quelque chose.. : 

Dans la condition pour vérifier s'il reste des briques  :

for (int x = 0; x < GRILLE_TAILLE; x += 1) {
          for (int y = 0; y < GRILLE_TAILLE; y += 1) {
            if (briques[y][x] == 0) {  // On a trouvé une brique!
              plusDeBriques = false;  // ne pas reinitialiser
            }
          }

est-ce que ça ne serait pas plutôt :" if (briques[y][x] == 1) " au lieu de if (briques[y][x] == 0) ? (Elles existent quand elles sont égales à 1, donc si on en trouve une égale à 1 c'est qu'elle existe encore...

___

Sinon, j'ai fait un essai de calcul pour la vitesse ajoutée en x à la balle pour pouvoir contrôler un peu l'angle avec lequel on la fait rebondir. Le rendu n'est pas très très propre mais ça fonctionne quand même à peu près comme j'imaginais. Je partage mon extrait de code, si vous avez des conseils :).

L'idée est que l'angle avec lequel la balle est renvoyée varie en fonction de la distance entre la balle et le bord de la palette au moment du rebond. Si on tape au centre de la palette la balle rebondit verticalement, si on tape plus ou moins proche du bord gauche, on rebondit plus ou moins vers la gauche, pareil pour la droite...

void update() {
//...
//collisions
    if (gb.collide.rectRect(ballX, ballY, BALL_SIZE, BALL_SIZE, padX, padY, PAD_W, PAD_H) || ballY <= 0) {
      if (gb.collide.rectRect(ballX, ballY, BALL_SIZE, BALL_SIZE, padX, padY, PAD_W, PAD_H)) {

        float dist_edgePad_left = (ballX + BALL_SIZE /2) - padX;//distance balle/côté gauche pad
        float dist_edgePad_right = (padX + PAD_W) - (ballX + BALL_SIZE /2);//distance ball/côté droit pad

        if (dist_edgePad_right > dist_edgePad_left) {
          speedX = min(-1 / dist_edgePad_left, -1);//si la balle est du coté gauche du pad, partir vers la gauche(speeX max = -1)
        }
        else if (dist_edgePad_left > dist_edgePad_right) {
          speedX = min(1 / dist_edgePad_right, 1);//si la balle est du côté droit, partir vers la droite(speeX max = 1)
        } 
        else {
          speedX = 0;
        }
      }

      speedY *= -1;
    }
//...
}

jicehel

NEW il y a 2 ans

Tu as raison pour le test avec if (briques[y][x] == 1) pour vérifier qu'il y a encore des briques.

Sinon tu t'embêtes à calculer la distance par rapport à la gauche ou à la droite... Est ce vraiment nécessaire ? Tu as déjà le signe si tu calcules par apport au milieu de la raquette... Par contre comme ta raquette est plate, ton rebond devrait se faire juste en inversant la vitesse en y. Pour ton genre de rebond, la raquette devrait être courbe si je ne m'abuse.

Sinon, si tu veux que ta vitesse soit plus réalise, change les vx et vy. Tant qu'ils seront en entier, tu auras des choses bizarre avec les formules que tu appliques.

Bonne chance pour tes dev et n'hésites pas à partager ton résultat. 

PS pou l'équipe et Sylvain: Pour les tutos avec des sujets communs pour que tout le monde puisse partager sa création sans créer des centaines de créations sur le même sujet. Sur une création ou un tuto, l'idée serait de pouvoir créer une création rattachée à ce sujet (qui n'apparaîtrait pas dans les listes de créations).





Codnpix

il y a 2 ans

Merci de ta réponse.

Par contre comme ta raquette est plate, ton rebond devrait se faire juste en inversant la vitesse en y. Pour ton genre de rebond, la raquette devrait être courbe si je ne m'abuse.

L'idée était justement un peu "d'émuler" une surface de raquette un peu courbe. Même si dans la réalité elle reste plate effectivement...

Sinon, si tu veux que ta vitesse soit plus réalise, change les vx et vy. Tant qu'ils seront en entier, tu auras des choses bizarre avec les formules que tu appliques.

J'avais bien passé ballX et speedX en float pour pouvoir y mettre la valeur que je récupère avec "+ ou - 1 / distance_bord_droit_ou_gauche".  Pour speedY je n'y pas touché, c'est toujours 1 ou -1.

J'essaierai de chercher un calcul plus efficace en me basant sur le milieu plutôt que sur les bords.

Voici la totalité de mon code, ce sera plus concret (et plus simple pour tester le résultat peut-être ^^) !

#include <Gamebuino-Meta.h>

bool launched;

//balle
float ballX;
int ballY;
float speedX;
int speedY;
const int BALL_SIZE = 4;

//palette
int padX;
int padY;
const int PAD_W = 14;
const int PAD_H = 3;

//briques
const int GRID_SIZE = 8;
int briques[GRID_SIZE][GRID_SIZE];
const int BRICK_W = gb.display.width() / GRID_SIZE - 2;
const int BRICK_H = 3;

void setup() {
  gb.begin();
  initGame();
}

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

input();
if (launched) {
  updates();
}
draw();
}


void input() {
    //ENTREES//
    if (gb.buttons.pressed(BUTTON_A)) {
      launched = true;
    }
    if (gb.buttons.repeat(BUTTON_LEFT,0)) {
      movePad("left");
    }
    if (gb.buttons.repeat(BUTTON_RIGHT,0)) {
      movePad("right");
    }
}


void updates() {
    //UPDATES//

    //collisions
    if (gb.collide.rectRect(ballX, ballY, BALL_SIZE, BALL_SIZE, padX, padY, PAD_W, PAD_H) || ballY <= 0) {
      if (gb.collide.rectRect(ballX, ballY, BALL_SIZE, BALL_SIZE, padX, padY, PAD_W, PAD_H)) {

        float dist_edgePad_left = (ballX + BALL_SIZE /2) - padX;//distance balle/côté gauche pad
        float dist_edgePad_right = (padX + PAD_W) - (ballX + BALL_SIZE /2);//distance balle/côté droit pad

        if (dist_edgePad_right > dist_edgePad_left) {
          speedX = min(-1 / dist_edgePad_left, -1);//si la balle est du coté gauche du pad, partir vers la gauche(speeX max = -1)
        }
        else if (dist_edgePad_left > dist_edgePad_right) {
          speedX = min(1 / dist_edgePad_right, 1);//si la balle est du côté droit, partir vers la droite(speeX max = 1)
        } 
        else {
          speedX = 0;
        }
      }

      speedY *= -1;
    }

    else if (ballX + BALL_SIZE >= gb.display.width() || ballX <= 0) {
      speedX *= -1;
    }

    else if (ballY + BALL_SIZE >= gb.display.height()) {
      initGame();
    }

    //collisions briques

    for (int rangee = 0; rangee < GRID_SIZE; rangee++) {
      for (int colonne = 0; colonne < GRID_SIZE; colonne++) {
        if (briques[rangee][colonne] == 0) {
          continue;
        }
        int briqueX =  colonne * (BRICK_W +2) +1;
        int briqueY = rangee * (BRICK_H +2) +1;
        if (gb.collide.rectRect(ballX, ballY, BALL_SIZE, BALL_SIZE, briqueX, briqueY, BRICK_W, BRICK_H)) {
          speedY *= -1;
          briques[rangee][colonne] = 0;//detruire la brique en cas de collision
          //verifier briques restantes
          bool zeroBriques = true;//passera à false si il reste au moins une brique
          for (int x = 0; x < GRID_SIZE; x++) {
            for (int y = 0; y < GRID_SIZE; y++) {
              if (briques[y][x] == 1) {
                zeroBriques = false;
              }
            }
          }
          if (zeroBriques) {
            initGame();
          }
        }
      }
    }
    ballX += speedX;
    ballY += speedY;
}

void initGame() {
  padX = gb.display.width() /2 - 5;
  padY = gb.display.height() -3;
  ballX = padX + PAD_W /2 - BALL_SIZE /2;
  ballY = padY - BALL_SIZE;
  speedX = 0;
  speedY = -1;
  launched = false;

  for (int rangee = 0; rangee < GRID_SIZE; rangee++) {
    for (int colonne = 0; colonne < GRID_SIZE; colonne++) {
      briques[rangee][colonne] = 0;
    }
  }

  for (int rangee = 0; rangee < GRID_SIZE; rangee++) {
    for (int colonne = 0; colonne < GRID_SIZE; colonne++) {
      briques[rangee][colonne] = rangee + 1;
    }
  }
}

void movePad(char dir[6]) {
  if (dir == "left") {
    if (padX <=0) {
      padX = 0;
    } else {
      padX -= 1;
    }
  }
  else if (dir == "right") {
    if (padX + PAD_W >= gb.display.width()) {
      padX = gb.display.width() - PAD_W;
    } else {
      padX += 1;
    }
  } else {
    padX = padX;
  }
}


void draw() {
    //DISPLAY//
  gb.display.clear();
  //paddle 
  gb.display.fillRect(padX, padY, PAD_W, PAD_H);
  //ball
  gb.display.fillRect(ballX, ballY, BALL_SIZE, BALL_SIZE);
  //briques
  const Color rainbow[8] = {WHITE, PURPLE, RED, YELLOW, LIGHTGREEN, GREEN, DARKBLUE, BLUE};
  for (int rangee = 0; rangee < GRID_SIZE; rangee ++) {
    for (int colonne = 0; colonne < GRID_SIZE; colonne++) {
      if(briques[rangee][colonne] == 0) {
        continue;
      }
      gb.display.setColor(rainbow[briques[rangee][colonne] - 1]);
      int briqueX = colonne * (BRICK_W + 2) + 1;
      int briqueY = rangee * (BRICK_H +2) +1;
      gb.display.fillRect(briqueX, briqueY, BRICK_W, BRICK_H);
    }
  }
}

Codnpix

NEW il y a 2 ans

jicehel jicehel

Merci de ta réponse.

Par contre comme ta raquette est plate, ton rebond devrait se faire juste en inversant la vitesse en y. Pour ton genre de rebond, la raquette devrait être courbe si je ne m'abuse.

L'idée était justement un peu "d'émuler" une surface de raquette un peu courbe. Même si dans la réalité elle reste plate effectivement...

Sinon, si tu veux que ta vitesse soit plus réalise, change les vx et vy. Tant qu'ils seront en entier, tu auras des choses bizarre avec les formules que tu appliques.

J'avais bien passé ballX et speedX en float pour pouvoir y mettre la valeur que je récupère avec "+ ou - 1 / distance_bord_droit_ou_gauche".  Pour speedY je n'y pas touché, c'est toujours 1 ou -1.

J'essaierai de chercher un calcul plus efficace en me basant sur le milieu plutôt que sur les bords.

Voici la totalité de mon code, ce sera plus concret (et plus simple pour tester le résultat peut-être ^^) !

#include <Gamebuino-Meta.h>

bool launched;

//balle
float ballX;
int ballY;
float speedX;
int speedY;
const int BALL_SIZE = 4;

//palette
int padX;
int padY;
const int PAD_W = 14;
const int PAD_H = 3;

//briques
const int GRID_SIZE = 8;
int briques[GRID_SIZE][GRID_SIZE];
const int BRICK_W = gb.display.width() / GRID_SIZE - 2;
const int BRICK_H = 3;

void setup() {
  gb.begin();
  initGame();
}

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

input();
if (launched) {
  updates();
}
draw();
}


void input() {
    //ENTREES//
    if (gb.buttons.pressed(BUTTON_A)) {
      launched = true;
    }
    if (gb.buttons.repeat(BUTTON_LEFT,0)) {
      movePad("left");
    }
    if (gb.buttons.repeat(BUTTON_RIGHT,0)) {
      movePad("right");
    }
}


void updates() {
    //UPDATES//

    //collisions
    if (gb.collide.rectRect(ballX, ballY, BALL_SIZE, BALL_SIZE, padX, padY, PAD_W, PAD_H) || ballY <= 0) {
      if (gb.collide.rectRect(ballX, ballY, BALL_SIZE, BALL_SIZE, padX, padY, PAD_W, PAD_H)) {

        float dist_edgePad_left = (ballX + BALL_SIZE /2) - padX;//distance balle/côté gauche pad
        float dist_edgePad_right = (padX + PAD_W) - (ballX + BALL_SIZE /2);//distance balle/côté droit pad

        if (dist_edgePad_right > dist_edgePad_left) {
          speedX = min(-1 / dist_edgePad_left, -1);//si la balle est du coté gauche du pad, partir vers la gauche(speeX max = -1)
        }
        else if (dist_edgePad_left > dist_edgePad_right) {
          speedX = min(1 / dist_edgePad_right, 1);//si la balle est du côté droit, partir vers la droite(speeX max = 1)
        } 
        else {
          speedX = 0;
        }
      }

      speedY *= -1;
    }

    else if (ballX + BALL_SIZE >= gb.display.width() || ballX <= 0) {
      speedX *= -1;
    }

    else if (ballY + BALL_SIZE >= gb.display.height()) {
      initGame();
    }

    //collisions briques

    for (int rangee = 0; rangee < GRID_SIZE; rangee++) {
      for (int colonne = 0; colonne < GRID_SIZE; colonne++) {
        if (briques[rangee][colonne] == 0) {
          continue;
        }
        int briqueX =  colonne * (BRICK_W +2) +1;
        int briqueY = rangee * (BRICK_H +2) +1;
        if (gb.collide.rectRect(ballX, ballY, BALL_SIZE, BALL_SIZE, briqueX, briqueY, BRICK_W, BRICK_H)) {
          speedY *= -1;
          briques[rangee][colonne] = 0;//detruire la brique en cas de collision
          //verifier briques restantes
          bool zeroBriques = true;//passera à false si il reste au moins une brique
          for (int x = 0; x < GRID_SIZE; x++) {
            for (int y = 0; y < GRID_SIZE; y++) {
              if (briques[y][x] == 1) {
                zeroBriques = false;
              }
            }
          }
          if (zeroBriques) {
            initGame();
          }
        }
      }
    }
    ballX += speedX;
    ballY += speedY;
}

void initGame() {
  padX = gb.display.width() /2 - 5;
  padY = gb.display.height() -3;
  ballX = padX + PAD_W /2 - BALL_SIZE /2;
  ballY = padY - BALL_SIZE;
  speedX = 0;
  speedY = -1;
  launched = false;

  for (int rangee = 0; rangee < GRID_SIZE; rangee++) {
    for (int colonne = 0; colonne < GRID_SIZE; colonne++) {
      briques[rangee][colonne] = 0;
    }
  }

  for (int rangee = 0; rangee < GRID_SIZE; rangee++) {
    for (int colonne = 0; colonne < GRID_SIZE; colonne++) {
      briques[rangee][colonne] = rangee + 1;
    }
  }
}

void movePad(char dir[6]) {
  if (dir == "left") {
    if (padX <=0) {
      padX = 0;
    } else {
      padX -= 1;
    }
  }
  else if (dir == "right") {
    if (padX + PAD_W >= gb.display.width()) {
      padX = gb.display.width() - PAD_W;
    } else {
      padX += 1;
    }
  } else {
    padX = padX;
  }
}


void draw() {
    //DISPLAY//
  gb.display.clear();
  //paddle 
  gb.display.fillRect(padX, padY, PAD_W, PAD_H);
  //ball
  gb.display.fillRect(ballX, ballY, BALL_SIZE, BALL_SIZE);
  //briques
  const Color rainbow[8] = {WHITE, PURPLE, RED, YELLOW, LIGHTGREEN, GREEN, DARKBLUE, BLUE};
  for (int rangee = 0; rangee < GRID_SIZE; rangee ++) {
    for (int colonne = 0; colonne < GRID_SIZE; colonne++) {
      if(briques[rangee][colonne] == 0) {
        continue;
      }
      gb.display.setColor(rainbow[briques[rangee][colonne] - 1]);
      int briqueX = colonne * (BRICK_W + 2) + 1;
      int briqueY = rangee * (BRICK_H +2) +1;
      gb.display.fillRect(briqueX, briqueY, BRICK_W, BRICK_H);
    }
  }
}

Max_NN

NEW il y a 2 ans

Bonjour tout le monde ! J'ai un problème, je veux faire un programme qui dessine un cercle avec une taille aléatoire, mais pour le moment la taille est toujours la même. Voici mon programme. Merci !

#include <Gamebuino-Meta.h>

int ballSize = random(1, 15);

void setup() {
  gb.begin();
}

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

 
  gb.display.print(ballSize);
  gb.display.setColor(WHITE);
  gb.display.drawCircle(39, 31, ballSize);
}

deeph

il y a 2 ans

Tu devrais créer ta propre discussion parce que là c'est un peu hors sujet.

En regardant rapidement ton code, le problème c'est que ton calcul aléatoire est en dehors de la boucle, donc qu'il ne s’exécute qu'une fois. Essaie plutôt ça :

#include <Gamebuino-Meta.h>

void setup() {
  gb.begin();
}

void loop() {
  gb.display.clear();
  gb.display.setColor(WHITE);
  int ballSize = random(1, 15);
  gb.display.print(ballSize);
  gb.display.drawCircle(39, 31, ballSize);
  while (!gb.update());
}

deeph

NEW il y a 2 ans

Max_NN Max_NN

Tu devrais créer ta propre discussion parce que là c'est un peu hors sujet.

En regardant rapidement ton code, le problème c'est que ton calcul aléatoire est en dehors de la boucle, donc qu'il ne s’exécute qu'une fois. Essaie plutôt ça :

#include <Gamebuino-Meta.h>

void setup() {
  gb.begin();
}

void loop() {
  gb.display.clear();
  gb.display.setColor(WHITE);
  int ballSize = random(1, 15);
  gb.display.print(ballSize);
  gb.display.drawCircle(39, 31, ballSize);
  while (!gb.update());
}