Pong

Creations

Aurélien Rodot

6 years ago

Challenge your friends with a 2 player game. You will build upon everything we have seen until now, as well as expanding your knowledge on if statements.

Pong, a classic video game

Length 30-40 minutes

Level Complete beginner

Prerequisites

Pong, released in 1972, was one of the first arcade video games that became successful. This classic is both fun and easy enough to learn code.

In this workshop, we will recreate this classic. To do so, we will have to use what we saw in the previous workshop to move the ball and control the paddles based on what we did in the tally counter workshop. Let's start with a detailed breakdown of the game as a whole.

The structure of the game

I have split the game into multiple steps. This allows us to focus on a single smaller task at a time. Trying to figure out the logic behind a game while viewing the game as a whole is... well intimidating and tough. By breaking it down, we will see that Pong, just like any other game, is composed of many simple parts.

  • Create the ball and a paddle
  • Draw the ball and the paddle
  • Update the paddle's position
  • Update the ball's position
  • Check for collisions between the ball and walls
  • Check for collisions between the ball and the paddle
  • Check to see if the game is not over
  • Add a second paddle
  • Count and display the scores

So we will start by creating a 1-player Pong. Once we get that working, adding another player will then be a simple task.

Implementing a ball and a paddle

#include <Gamebuino-Meta.h>





// Ball attributes int ball_posX = 20; int ball_posY = 20; int ball_speedX = 1; int ball_speedY = 1; int ball_size = 4;

// paddle1 attributes int paddle1_posX = 10; int paddle1_posY = 30;

// Dimensions of both paddles int paddle_height = 12; int paddle_width = 3;

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

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




Here we created the variables necessary to describe two things: the ball and the first paddle. The ball has 2 ints to keep track of its position along the two axes X and Y. For each of those axes, we also have a speed that describes the direction of our ball. The paddle also has an X and Y position. However, having a speed variable is unnecessary because we want to control it with the arrows. paddle_height and paddle_width are the paddle's dimensions. We also describe the ball's size, but since it is a square, only one variable (ball_size) is necessary to memorize both its height and width.


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

  // Draw the ball
  gb.display.fillRect(ball_posX, ball_posY, ball_size, ball_size);
  // Draw paddle1
  gb.display.fillRect(paddle1_posX, paddle1_posY, paddle_width, paddle_height);
}

Here nothing too new, we are displaying the ball and paddle as rectangle. As a refresher, here is the syntax for fillRect:

gb.display.fillRect( x coordinate of our rectangle , y coordinate of our rectangle , width , height );

Controlling the paddle

void loop() {




while (gb.update()); gb.display.clear();

// paddle1 movement if (gb.buttons.repeat(BUTTON_UP, 0)) { paddle1_posY = paddle1_posY - 1; } if (gb.buttons.repeat(BUTTON_DOWN, 0)) { paddle1_posY = paddle1_posY + 1; }

// Draw the ball and paddle // }




This piece of code is what allows us to control the paddle's movement. In preceding workshops, we used the function gb.buttons.pressed(BUTTON_A) to check when the user pressed down on button A. But here, we want to move the paddle not WHEN the button was pressed, but rather AS LONG AS the button is pressed. Thankfully, the Gamebuino library offers 5 different functions tied to button behavior:

  • gb.buttons.pressed()
  • gb.buttons.released()
  • gb.buttons.held()
  • gb.buttons.repeat()
  • gb.buttons.timeHeld()

We already know gb.buttons.pressed() and we will talk about gb.buttons.repeat() shortly. To learn more about what the other 3 functions do, head over to the reference page

gb.buttons.repeat(BUTTON_UP, 3) is true every 3 frames while the UP arrow is held down. By putting a 0 where the 3 is, this function is true for every frame where the arrow is held down.

Collisions

void setup() {
  // setup
}





void loop() { // paddle1 movement //

ball_posX = ball_posX + ball_speedX; ball_posY = ball_posY + ball_speedY;

if (ball_posY < 0) { // Bounce on top edge ball_speedY = 1; }

if (ball_posY > gb.display.height() - ball_size) { // Bounce on bottom edge ball_speedY = -1; }

if (ball_posX > gb.display.width() - ball_size) { // Bounce on the right edge ball_speedX = -1; }

// Draw the ball and paddle // }




Here we apply everything we have seen previously. We update the ball's position, then we check to see if it collided with the edges of the screen and make it bounce. The only difference is that we only check for three edges: top, bottom, and right. The left side must be "defended" by our paddle. Later, when we will have a second player, we will remove the right wall as well.

Now, if you play the game as it is (go ahead, don't be afraid to try), the ball and paddle move as planed. But the ball goes straight through the paddle! Not very fun right? So let's look into that right now!


Unlike the collisions with walls, the paddle moves. We have to check multiple conditions to be sure the ball hits the paddle. Let's take a look at the diagrams above. The black rectangle is the paddle, and the three squares are the three possible positions for the ball. In each situation, we want the ball to bounce off the paddle. There are three conditions to deduce from these diagrams.

First of all, you may have guessed that we only want the ball to bounce when it hits the paddle. When looking at it from the X-axis' point of view, this mean that the left side of the ball is overlapping the right side of the paddle.

Also, we want the ball to bounce even if it is only touching the paddle partially. This corresponds to ball 1 and 3 in the diagrams. In the case of ball 1, we need to check that the bottom of the ball is LOWER than the top of the paddle. In a similar fashion with ball 3, we need the top of the ball to be HIGHER than the bottom of the paddle.

  • If the left side of the ball touches the right side of the paddle
  • If the bottom of the ball is lower than the top of the paddle
  • If the top of the ball is higher than the bottom of the paddle


Complex conditions like this are almost impossible to deduce without some help like the diagram above. I initially made the diagram above on a sheet of paper (I cleaned it up and made it a .png later for this tutorial :P ). This is why you should pretty much always have a pencil and a piece of paper within reach when making games. It really prevents headaches.


So if these three conditions are true, then we change the ball's direction. To implement this, we could place three nested ifs:

if (ball_posX == paddle1_posX + paddle_width) {  // If the left side of the ball touches the right side of the paddle
  if (ball_posY + ball_size >= paddle1_posY) {  // If the bottom of the ball is lower than the top of the paddle
    if (ball_posY <= paddle1_posY + paddle_height) {  // If the top of the ball is higher than the bottom of the paddle
       // Bounce off the paddle
    }
  }
}

However, "nesting" ifs one into another like so creates many brackets, which is not easy to read. In C/C++ there is a way to simplify these "waterfalls" of brackets: conditions can use AND and OR operators. The AND operator is written && and the OR operator is ||. Here is an example:

// If a is 3 AND b is negative
if ((a == 3) && (b < 0)) {
}

// If a is at least 3 OR b is equal to 0
if ((a >= 3) || (b == 0)) {
}

The outermost parentheses ( ) (around all conditions) are required.


For our code, we need to test 3 conditions. But the && and || operators are used the same way whether it is with 2, 3, or 12 conditions. Now if you try to place all three of our conditions on the same line, you will have fixed the "waterfall" problem, but created another readability problem: the line is very long. A very long line of code is generally badly seen in the coding community. But we can fix this easily as we are allowed to put line breaks in between conditions (or inside conditions for that matter). Look at the difference:

// The three conditions on one line
if ( (ball_posX == paddle1_posX + paddle_width) && (ball_posY + b >= paddle1_posY) && (ball_posY <= paddle1_posY + paddle_height) ) {
    // Bounce
}

// The same thing, but with line breaks
if ( (ball_posX == paddle1_posX + paddle_width) 
  && (ball_posY + ball_size >= paddle1_posY)
  && (ball_posY <= paddle1_posY + paddle_height) ) {
  // Bounce
}

We can now interact with the ball! And when the player misses the ball, it flies off the screen and the game is over. But the player probably wants to play another round. So let's place the ball back into the screen after a game is lost.


void loop() {
  // Paddle movement //
  // Update ball movement + collisions //

  if (balle_posX < 0) {
    // Reset the ball
    int ball_posX = 20;
    int ball_posY = 20;
    int ball_speedX = 1;
    int ball_speedY = 1;
  }

  // Draw the ball and paddle //
}

Here we go, now when the ball leave the screen on the left side, we put it back on the screen and make it go to the right as not to surprise the player ;) Here is all the code we have written up to this point:

#include <Gamebuino-Meta.h>

 




// ball attributes
int ball_posX = 20;
int ball_posY = 20;
int ball_speedX = 1;
int ball_speedY = 1;
int ball_size = 4;




// paddle1 attributes
int paddle1_posX = 10;
int paddle1_posY = 30;




// Dimension of both paddles
int paddle_height = 10;
int paddle_width = 3;




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




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




  // Update paddle1 position
  if (gb.buttons.repeat(BUTTON_UP, 0)) {
    paddle1_posY = paddle1_posY - 1;
  }
  if (gb.buttons.repeat(BUTTON_DOWN, 0)) {
    paddle1_posY = paddle1_posY + 1;
  }




  // Update ball position
  ball_posX = ball_posX + ball_speedX;
  ball_posY = ball_posY + ball_speedY;




  // Collisions with walls
  if (ball_posY < 0) {
    ball_speedY = 1;
  }
  if (ball_posY > gb.display.height() - ball_size) {
    ball_speedY = -1;
  }
  if (ball_posX > gb.display.width() - ball_size) {
    ball_speedX = -1;
  }




  // Ball-paddle1 collisions
  if ( (ball_posX == paddle1_posX + paddle_width)
    && (ball_posY + ball_size >= paddle1_posY) 
    && (ball_posY <= paddle1_posY + paddle_height) ) {
    ball_speedX = 1;
  }




  // Check if the ball left the screen (on the left side)
  if (ball_posX < 0) {
    // Reset ball
    ball_posX = 20;
    ball_posY = 20;
    ball_speedX = 1;
    ball_speedY = 1;
  }




  // Display ball
  gb.display.fillRect(ball_posX, ball_posY, ball_size, ball_size);
  // Display paddle1
  gb.display.fillRect(paddle1_posX, paddle1_posY, paddle_width, paddle_height);
}



It's your turn!

We started by breaking down Pong into feasible parts. So far we built the core mechanics of the game. We created and drew a ball and a paddle that both move and interact. If the ball slips by the player (and goes off-screen), we reset the ball. The rest is up to you:

  • Add a second paddle
  • Use buttons A and B to make it move
  • Detect when the ball leaves the right side of the screen
  • Count the score of each player
  • Draw the score

Tip: To count and draw the score, refresh your memory with the tally counter workshop.

Share your game on social media #gamebuino #pong , we go through them all the time ;)

Solution example


If you are getting stuck or ran out of ideas, here's what we did :)


#include <Gamebuino-Meta.h>

// ball attributes
int ball_posX = 20;
int ball_posY = 20;
int ball_speedX = 1;
int ball_speedY = 1;
int ball_size = 4;

// paddle1 attributes
int paddle1_posX = 10;
int paddle1_posY = 30;

// paddle2 attributes
int paddle2_posX = gb.display.width() - 13;
int paddle2_posY = 30;

// Dimension of both paddles
int paddle_height = 10;
int paddle_width = 3;


// Scores
int score1;  // Player 1 score
int score2;  // Player 2 score

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

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

  // Update paddle positions
  if (gb.buttons.repeat(BUTTON_UP, 0)) {
    paddle1_posY = paddle1_posY - 1;
  }
  if (gb.buttons.repeat(BUTTON_DOWN, 0)) {
    paddle1_posY = paddle1_posY + 1;
  }
  if (gb.buttons.repeat(BUTTON_B, 0)) {
    paddle2_posY = paddle2_posY - 1;
  }
  if (gb.buttons.repeat(BUTTON_A, 0)) {
    paddle2_posY = paddle2_posY + 1;
  }

  // Update ball position
  ball_posX = ball_posX + ball_speedX;
  ball_posY = ball_posY + ball_speedY;

  // Collisions with walls
  if (ball_posY < 0) {
    ball_speedY = 1;
  }
  if (ball_posY > gb.display.height() - ball_size) {
    ball_speedY = -1;
  }
  if (ball_posX > gb.display.width() - ball_size) {
    ball_speedX = -1;
  }

  // Ball-paddle1 collisions
  if ( (ball_posX == paddle1_posX + paddle_width)
    && (ball_posY + ball_size >= paddle1_posY) 
    && (ball_posY <= paddle1_posY + paddle_height) ) {
    ball_speedX = 1;
  }
  // Ball-paddle2 collisions
  if ( (ball_posX + ball_size == paddle2_posX)
    && (ball_posY + ball_size >= paddle2_posY) 
    && (ball_posY <= paddle2_posY + paddle_height) ) {
    ball_speedX = -1;
  }

  // Check if the ball left the screen
  if (ball_posX < 0) {
    // Reset ball
    ball_posX = 20;
    ball_posY = 20;
    ball_speedX = 1;
    ball_speedY = 1;
    // Increment player2's score
    score2 = score2 + 1;
  }
  if (balle_posX > gb.display.width()) {
    // Reset ball
    ball_posX = 20;
    ball_posY = 20;
    ball_speedX = 1;
    ball_speedY = 1;
    // Increment player1's score
    score1 = score1 + 1;
  }

  // Display ball
  gb.display.fillRect(ball_posX, ball_posY, ball_size, ball_size);
  // Display paddle1
  gb.display.fillRect(paddle1_posX, paddle1_posY, paddle_width, paddle_height);
  // Display paddle2
  gb.display.fillRect(paddle2_posX, paddle2_posY, paddle_width, paddle_height);

  // Display scores
  gb.display.setCursor(35, 5);
  gb.display.print(score1);
  gb.display.setCursor(42, 5);
  gb.display.print(score2);
}


Next Workshop

By Julien Giovinazzo

Any questions / comments / suggestions, be sure to drop a comment down below!

View full creation

jicehel

NEW 6 years ago

Super clair, j'ai même appris des trucs sur les boutons que je ne connaissais pas. Super pour continuer après les bases et voir les débuts d'un jeu (que l'on peut facilement d'ores et déjà améliorer en ajoutant un score avec ce que l'on a vue dans ce tuto et dans le précédent ou l'on a appris à écrire, à utiliser les variables. Combiné avec un test, on peut aussi gérer le gagnant...  ;)

lpopdu51

NEW 6 years ago

Bonjour, dès le premier exemple que je compile dans mon gamebuino :

"

#include <Gamebuino.h>
Gamebuino gb;
void setup() {   gb.begin();   // affiche l’écran principal au lancement du jeu
  gb.titleScreen(F("MON PREMIER PONG")); }
void loop() {   if (gb.update()) {     // dessine la raquette de gauche
    gb.display.fillRect(1, 20, 3, 10);             // dessine la raquette de droite
    // on utilise ici la variable LCDWIDTH
// qui correspond à la largeur de l'écran
// - 5 correspond à l’espace entre la raquette et l’écran
// - 3 correspond à la largeur de la raquette
    gb.display.fillRect(LCDWIDTH - 5 - 3, 20, 3, 10);    
// dessine la balle
// à vous de jouer !
  } }

"

Je me retrouve avec l'erreur suivante :

"

In file included from C:\Users\MOI\Documents\Gamebuino\pong\pong.ino:1:0:

C:\Users\MOI\Documents\Arduino\libraries\Gamebuino/Gamebuino.h:25:23: fatal error: avr/sleep.h: No such file or directory

 #include <avr/sleep.h>

                       ^

compilation terminated.

Utilisation de la bibliothèque Gamebuino version 0.4 dans le dossier: C:\Users\MOI\Documents\Arduino\libraries\Gamebuino
exit status 1
Erreur de compilation pour la carte Gamebuino Meta

"

Je suppose donc qu'il me manque une bibliothèque, mais je ne sais pas laquelle.

Merci beaucoup et bon courage à vous.

Sorunome

6 years ago

Hello, you installed the Gamebuino library, for the Gamebuino Classic, instead of the Gamebuino META library for the new color-screen Gamebuino META

Sorunome

NEW 6 years ago

lpopdu51 lpopdu51

Hello, you installed the Gamebuino library, for the Gamebuino Classic, instead of the Gamebuino META library for the new color-screen Gamebuino META

jicehel

NEW 6 years ago

Yes, master Rodot have make a tutorial to convert Classic prog to META.

At the beginning, he write: Replace: #include <Gamebuino.h>  With #include <Gamebuino-Compat.h>

But else, you can replace  #include <Gamebuino.h>  With #include <Gamebuino-Meta.h> as he have corrected it in the "Hello World" tutorial.

Pong is not yet corrected it's to see if you read all the explanations !!  :D  Or maybe more likely just because he havn't had time to correct it yet.


Good luck for your first programs  :) 

jicehel

6 years ago

Rodot have corrected the Hello World tuto, so now you have the corrected sources alredy done.

jicehel

NEW 6 years ago

jicehel jicehel

Rodot have corrected the Hello World tuto, so now you have the corrected sources alredy done.

oscardo

NEW 6 years ago

bonjour tout monde j ai un petit problème je ne sais pas comment créer un jeux 

vous pouvez m'aider je n'ai pas de tuto ?

jicehel

NEW 6 years ago

Oui oscardo: Les 3 premières étapes

1 suivre le tuto:  INSTALLATION DE LA GAMEBUINO META

2 suivre le tuto: hello, world

3 suivre ce tuto: pong


Voilà, là tu auras les bases du langage et de comment transférer un programme dans ta META.


Après, il faudra que tu réfléchisses à ton jeu (que veux-tu faire). Un conseil, commence simple sinon si tu veux faire Warcraft ou Minecraft directement pour commencer, tu vas aller droit dans le mur... commence juste par te fixer un objectif comme je déplace un carré avec les touches puis je remplace le carré par un sprite, puis je rajoute un fond etc... en ajoutant les éléments les uns après les autres.


Après, il faut penser aux graphiques de ton jeu (c'est un élément important mais il ne sert à rien de les préparer tant que tu ne sais pas ce que tu veux faire et comment le faire) puis à les implémenter.


Quand tu en sera là Rodot et son équipe auront fait d'autres tutos expliquant d'autre points plus avancés comme: format des sauvegarde de la meta

et bien d'autres sujets qui vont être ajoutés. Ta meilleure amie va être l'académie https://gamebuino.com/fr/academy

Nux

NEW 6 years ago

Pour les personnes bloquer au première exercices la grosse partie du problème viens de la bibliothèque utiliser dans l'exemple.

 #include <Gamebuino.h>

qui est pour la classic, il faut utiliser (en début de code), la bibliothèque de la meta.

 #include <Gamebuino-Meta.h>)

Je vous laisse le code que j'ai utilisé.

#include <Gamebuino-Meta.h>

void setup() {
  gb.begin();
  // affiche l’écran principal au lancement du jeu
  // titleScreen est lancer dans gb.begin(); quand le jeu est sur le SD, il ne faut donc normalment pas l'utilisé.
  // cependant cela vous permetra de le voir quand vous lancer votre jeu via Arduino
  gb.titleScreen();
}
void loop() {
  // Retournez voir, la partie hello world si vous ne comprenez pas ce debut
  while(!gb.update());
  gb.display.clear();

  // dessine la raquette de gauche
  gb.display.fillRect(1, 25, 3, 10);

  // dessine la raquette de droite
  //Je l'ai fait a l'oeil c'est le mieux que j'ai pour le moment
  gb.display.fillRect(76, 25, 3, 10);

// dessine la balle
// à vous de jouer !

}

Nux

NEW 6 years ago

AFFICHER DES RECTANGLES

Pour faire un Pong, il faut commencer par afficher les raquettes et la balle.

Pour ça, on peut utiliser la fonction gb.display.fillRectangle (fill pour de l’anglais « remplir » et Rect pour « rectangle »). Elle s’utilise de la façon suivante :

// x : coordonnée en x
// y : coordonnée en y
// w : largeur (de l’anglais width)
// h : hauteur (de l’anglais height)
gb.display.fillRect (x, y, w, h);

Voilà un exemple pour tracer notre raquette à gauche de l’écran :

gb.display.fillRect (5, 20, 3, 12);

Ce qui nous donnera :

Petite coquille je pense.

"Pour ça, on peut utiliser la fonction gb.display.fillRectangle":         gb.display.fillRectangle -> gb.display.fillRect



Sinon par rapport a la méthode  gb.display.fillRect,

Si j'utilise pour ma raquette:

gb.display.fillRect (5, 20, 3, 12);

la raquette fera 5 pixel de large et non 3


Et pour ma balle je voulais la faire 3 pixel de haut et  3 pixel de large

gb.display.fillRect(ballX, ballY, 3,3);

donne une balle de 5 sur 5 du coup

et j'utilise donc 

gb.display.fillRect(ballX, ballY, 2,2);

qui me donne un rectangle de 3 sur 3 mais comment je peut en faire un de 4 sur 4 du coup ?

TUTOENTOUTGENRE

NEW 6 years ago

bonjour, quelqu'un pourrait faire la compilation entière et qui marche car moi ça ne marche pas. Grave a tuto j'ai pu apprendre les bases, mais il il dit "Arduino : 1.8.5 (Mac OS X), Carte : "Arduino/Genuino Uno"

/Users/edouarddjen/Documents/Arduino/PONG/PONG.ino: In function 'void loop()':
PONG:27: error: expected initializer before 'gb'
   gb.begin();
   ^
PONG:32: error: a function-definition is not allowed here before '{' token
 void loop() {
             ^
PONG:50: error: expected '}' at end of input
 }
 ^
exit status 1
expected initializer before 'gb'

Ce rapport pourrait être plus détaillé avec
l'option "Afficher les résultats détaillés de la compilation"
activée dans Fichier -> Préférences." Quelqu'un pourrait m'envoyer la compilation entière svp ?

Aurélien Rodot

6 years ago

Hello, ce tuto n'a pas encore été adapté pour la META, il se sera bientôt. Patience :3

Aurélien Rodot

NEW 6 years ago

TUTOENTOUTGENRE TUTOENTOUTGENRE

Hello, ce tuto n'a pas encore été adapté pour la META, il se sera bientôt. Patience :3

TUTOENTOUTGENRE

6 years ago

Bonjour, j'ai oublié de préciser que j'était sur la version classic :) 

TUTOENTOUTGENRE

NEW 6 years ago

Aurélien Rodot Aurélien Rodot

Bonjour, j'ai oublié de préciser que j'était sur la version classic :) 

jicehel

NEW 6 years ago

After loading library with:

#include <Gamebuino.h>


Have you declared the object gb with:
Gamebuino gb;

NeoTechni

NEW 6 years ago

So where is the english one though? Same for hello world. I can only find the french ones

Aurélien Rodot

6 years ago

We are about to re-write everything from scratch, these were just proof of concept. Expect a few weeks for the English version.

jicehel

NEW 6 years ago

We have to lean english and you have to learn french, nah !! It's joke, they'll write soon i think. Maybe an error in the link as it was existing if i remember well but Rodot will say but else if you need to translate something, we can maybe help (i'm french as you can see when you read my english  :D)