Créer un Pong avec Processing

Last modified by Maloup Ni on 2019/05/21 18:20

 

Créer un Pong avec Processing

valentin@labboite.fr

Ce tutoriel vous propose de créer une version simplifiée du jeu Pong à l'aide de l'environnement de programmation Processing.

Pong

Pong est un jeu d'arcade commercialisé en 1972. Ce n'est pas à proprement parler le premier jeu vidéo à avoir été inventé, mais c'est le premier à être devenu populaire. Son principe reprend celui du tennis de table : une balle, matérialisée par un carré blanc, traverse l'écran de part en part et rebondit sur les côtés haut et bas de l'écran ; les deux joueurs la contrôlent à l'aide de leur raquette, représentée par un rectangle blanc. Si le joueur manque la balle (celle-ci sort de son côté de l'écran), celui-ci perd le point.

Processing

Processing est un environnement de programmation open-source. Par « environnement de programmation », on entend un ensemble d’outils permettant de créer des programmes informatiques. Mais Processing a ceci de particulier qu’il est orienté vers la création graphique interactive : il a été fondé par Benjamin Fry et Casey Reas, deux artistes américains et découle du projet « Design by numbers » du MIT. Il permet de créer des effets graphiques en quelques lignes de code et se veut relativement accessible : il est donc particulièrement appréciés des créatifs et des débutants en programmation. Il est open-source, c’est-à-dire que son code est mis à la disposition du grand public.

Installation

Si vous ne l'avez pas fait, vous devez au préalable télécharger et installer le logiciel Processing sur votre ordinateur. Pour ce faire, rendez vous sur cette page : https://processing.org/download/

Présentation

Lancez le logiciel. Une fenêtre apparaît alors : c'est votre sketch, le fichier qui contiendra le code qui permettra d'exécuter votre jeu.

processing_interface.png

Petit tour du propriétaire :

  • au centre, une grande zone blanche : c’est ici que nous allons écrire les lignes de code qui va donner vie à notre jeu.
  • en bas, sur fond noir, la console : dans cette zone s’affichera les erreurs, quand notre code ne fonctionnera pas. On peut aussi y faire apparaître du texte à l’exécution du code (mais on ne s’y penchera pas pour ce tutoriel).
  • en haut, un bouton Run (icône en forme de triangle) qui va nous permettre d’exécuter le code. Lorsque nous aurons terminé d’écrire une portion de code, nous le ferons fonctionner en appuyant sur ce bouton.
  • un bouton Stop (icône en forme de carré) qui permet de cesser l’exécution du code

N'oubliez pas d'enregistrer votre sketch et de le sauvegarder régulièrement — vous n'êtes jamais à l'abri d'un plantage.

Création du jeu

1. Objectifs

Avant de taper nos premières lignes de codes, posons un peu le contexte. Nous allons créer un Pong jouable à un seul joueur. Nous devrons créer la zone de jeu, définir sa taille et lui attribuer un fond noir. Puis, nous devrons créer la plateforme située à gauche de la zone de jeu : elle se présentera sous la forme d’un rectangle blanc qui bougera sur l’axe vertical et suivra les mouvements de la souris du joueur. Nous devrons également créer la balle : elle se présentera sous la forme d’un rond blanc, bougera sur toute la zone de jeu et rebondira sur le côté du haut, le côté droit et le côté du bas. Lorsque la balle atteindra le côté gauche de la zone de jeu, alors ce sera un game over…

2. Déclaration des variables

Pour construire notre jeu, nous allons avoir besoin de définir un certain nombre de caractéristiques. Certaines de ces caractéristiques vont varier tout au long du jeu : par exemple, la balle, en se déplaçant, va voir sa position varier dans le temps ; la position de la plateforme va varier selon la position de la souris… Nous allons donc devoir définir des variables. Ces variables sont à écrire tout au début de votre code.

Notre jeu est projeté dans un espace en deux dimensions (2D), ce qui signifie que nous allons devoir, pour chaque position, définir un positionnement horizontal (axe X) et un positionnement vertical (axe Y). Ces valeurs seront exprimées en pixels, ce seront donc des valeurs numériques. Gardez en tête que le point d'origine correspond à l'angle supérieur gauche de la fenêtre de votre programme.

On va d'abord définir notre première variable : la position de la balle en X. Pour ce faire :

  • on définit d'abord le type de la variable. Cette variable correspond à un nom entier (integer en anglais) : on commence donc par écrire le mot clé int.
  • on attribut ensuite un nom à cette variable. Vous pouvez lui donner le nom que vous voulez. Je vous recommande néanmoins de choisir un nom explicite, afin de rendre votre code le plus compréhensible possible. Évitez absolument les accents et les caractères spéciaux, comme la ponctuation. Je choisis balleX.
  • enfin, on clôt la déclaration par un point-virgule. Sur Processing, chaque instruction se termine par un point-virgule pour indiquer que celle-ci est terminée et que le programme doit exécuter la commande suivante. Il arrive très souvent d'oublier un point-virgule et ainsi d'avoir un code qui ne fonctionne pas.

int balleX;

Notre première variable est déclarée !

Nous allons répéter l'opération pour les autres variables :

  • la position de la balle en Y, qui correspondra à la variable balleY
  • les positions de la plateforme en X et en Y, qui correspondront aux variables plateformeX et plateformeY
  • les valeurs de déplacement de la balle en X et en Y, soit les variables deplacementX et deplacementY

On obtient donc le code suivant : 

int balleX;
int balleY;
int plateformeX;
int plateformeY;
int deplacementX;
int deplacementY;

Toutes nos variables sont déclarées ! Mais pour l'instant, elles sont vides : on ne leur a attribué aucune valeur…

3. Définir le setup

Pour démarrer, le programme va avoir besoin de quelques informations : quelle sera la taille de la zone de jeu ? Sa couleur de fond ? La position initiale de la balle ? … Ces informations, nous allons devoir les renseigner. 

Pour cela, nous allons utiliser une fonction spéciale. Une fonction est une série d’instructions : lorsque la fonction est convoquée, elles sont exécutées les unes après les autres, suivant l’ordre dans lequel on les écrit.

La fonction qui est exécutée dès le démarrage du programme s’appelle la fonction setup. Elle est présente dans chaque programme Processing par défaut.

On l'écrit de cette manière :

void setup()
{

}

Nous allons ensuite écrire les différentes instructions entre les accolades. Commençons par la position

de notre plateforme en X : je voudrais la placer à 15 pixels du bord de la zone de jeu. On écrit donc :

void setup()
{
   plateformeX = 15;
}

On attribue ensuite des valeurs aux autres variables. Cela donne :

void setup()
{
   plateformeX = 15;
   plateformeY = 60;

   deplacementX = 6;
   deplacementY = -3;
   balleY = 200;
   balleX = 200;
}

Taille de la zone de jeu

Nous allons également définir la taille de notre zone de jeu : elle sera de 400 pixels sur 400 pixels. Pour cela, nous allons utiliser une autre fonction, appelée size(). Cette fonction à une écriture un peu particulière : entre les parenthèses, nous allons indiquer des paramètres. Ces paramètres varient d'une fonction à l'autre et sont séparées par des virgules. Dans le cas de la fonction size(), il y en a deux : la longueur et la largeur de la fenêtre. La structure de notre fonction serait donc similaire à celle-ci :

size(longueur, largeur);

Nous allons mettre, à la place de « longueur » et « largeur », les valeurs correspondantes. Cela donne donc :

size(400, 400);

Couleur de l'arrière-plan de la zone de jeu

Pour la couleur de l'arrière-plan de notre zone de jeu, Processing propose là aussi une fonction toute faite : c'est la fonction background()

En numérique, les couleurs sont obtenues par le mélange du rouge (R), du vert (V) et du bleu (B). Pour chaque couleur, on indique une valeur numérique, correspondant à un nombre entre 0 et 255. Chaque nuance s'obtient ensuite en mettant plus ou moins de rouge, de vert ou de bleu. Par exemple, pour obtenir un rouge pur, on définira R à 255 (la valeur maximale), V à 0 et B à 0.

La fonction background() va donc contenir trois paramètres : la valeur de R, de V et de B.

Mais comment obtenir la couleur noire ? Cette couleur correspond en réalité à une absence de couleur rouge, vert et bleu. R, V et B seront donc égaux à 0. On écrira donc ceci :

background(0, 0, 0); 

Les trois paramètres ayant la même valeur, Processing nous autorise à simplifier cette écriture. On peut donc écrire simplement :

background(0); 

Et voilà, notre fonction setup() est prête ! 

void setup()
{
   plateformeX = 15;
   plateformeY = 60;

   deplacementX = 6;
   deplacementY = -3;
   balleY = 200;
   balleX = 200;
   size(400,400);
   background(0);
}

Avant de continuer, vérifions si notre code fonctionne. On appuie donc sur le bouton Run.

Si tout va bien, une fenêtre noire apparaît. Mais elle est vide : notre plateforme et notre balle n'apparaissent pas… En effet, nous ne les avons pas encore dessiné !

4. Définir le draw()

Il s’agit donc désormais de créer les différents éléments interactifs de notre jeu.

Processing va exécuter la série d'instructions que nous allons lui indiquer : dessine moi un rectangle blanc à telle position (pour la plateforme), un rond blanc à telle position (pour la balle), ect. Pour cela, nous allons utiliser une autre fonction de Processing : la fonction draw().

Processing va exécuter toutes les instructions écrites dans le draw(), dans l'ordre où nous les avons écrites. Puis, lorsqu'il aura exécuté la dernière instruction, il reviendra à la première. Il faut donc comprendre que la fonction draw() est exécutée en boucle.

L'écriture est similaire à la fonction setup(). On écrit donc ceci :

void draw() 
{

}

On peut maintenant dessiner nos objets !

Dessiner la plateforme

Notre plateforme correspond à un rectangle blanc. Pour dessiner un rectangle, Processing à une fonction toute faite, c'est la fonction rect(). Cette fonction nécessite quatre paramètres, dans l'ordre : la position en X du rectangle, la position en Y, la largeur du rectangle et sa longueur.

La position en X et en Y, nous l’avons désigné respectivement selon les variables plateformeX et plateformeY. On peut donc les écrire tel quel dans notre code : le programme remplacera plateformeX et plateformeY par les valeurs qui varieront tout au long du jeu. On attribue ensuite une largeur de 25 px et une longueur de 85 px. Voilà ce qu'on obtient donc :

void draw() 
{
   rect(plateformeX, plateformeY, 25, 85);
}

Dessiner la balle

Pour la balle, c'est à peu près la même chose. On ne va cependant pas dessiner un rectangle, mais un rond : on va donc utiliser la fonction ellipse(). Là aussi, cette fonction nécessite quatre paramètres, similaires à ceux de rect() : on va indiquer dans l'ordre la position en X de l'ellipse, sa position en Y, sa largeur et sa longueur.

Et là encore, nous avons défini la position en X et en Y de notre balle par les variable balleX et balle Y. Cette fois-ci, nous allons dessiner un cercle de 20 px de diamètre : on indique donc, en largeur comme en longueur, 20.

void draw() 
{
   rect(plateformeX, plateformeY, 25, 85);
   ellipse(balleX, balleY, 20, 20);
}

Définir la couleur des objets

Très bien ! Mais… nos objets sont blancs et nous ne leur avons pas attribué, dans notre code, de couleur.

On va donc utiliser une nouvelle fonction : fill(). Comme pour background(), cette fonction comprendra trois paramètres, correspondant aux valeurs de rouge, de bleu et de vert. Dans l'idée, nos objets sont blancs : R, V et B seront donc égaux à 255. Nous pouvons donc écrire simplement :

void draw() 
{
   fill(255);
   rect(plateformeX, plateformeY, 25, 85);
   ellipse(balleX, balleY, 20, 20);
}

Attention, la fonction fill() est toujours écrite avant les objets sur lesquels elle agit, à savoir aussi mon rectangle et mon rond.

Enfin, pour éviter un effet d’escalier peu esthétique sur nos tracés (ce que l’on appelle, en anglais, l’aliasing) nous allons convoquer une autre fonction que nous placerons avant nos instructions. Il s’agit de smooth()

void draw() 
{
   smooth();
   fill(255);
   rect(plateformeX, plateformeY, 25, 85);
   ellipse(balleX, balleY, 20, 20);
}

Appuyez sur le bouton Run : normalement, la plateforme et la balle apparaissent. Hourra ! Reste maintenant à les animer…

5. Interactivité des objets

Nous allons pouvoir maintenant animer notre balle et notre plateforme. Comme je vous le disais plus haut, Processing va exécuter nos instructions en boucle : on va donc lui indiquer que, lorsqu’il exécute le code une fois, la balle sera à telle position ; la fois suivante, à telle position, et ainsi de suite. Nous allons donc incrémenter notre valeur, c’est à dire lui ajouter exécution après exécution une valeur. 

Définir les mouvements de la balle

On commence par écrire la variable de notre position. Puis, on lui redéfinit sa position : à sa position actuelle, on va lui ajouter une valeur en pixel.

On va donc dire à notre programme d'ajouter, à la position de notre balle, une valeur pour la faire se déplacer. Par exemple, pour ce qui est du déplacement horizontal de notre balle, on va ajouter à balleX la valeur du déplacement en X de la balle, définie par la variable deplacementX. Cela donne donc :

balleX = balleX + deplacementX;

Et voilà ! On fait la même chose pour la position de la balle en Y. Notre code ressemble donc maintenant à cela : 

void draw() 
{
   smooth();
   fill(255);
   rect(plateformeX, plateformeY, 25, 85);
   ellipse(balleX, balleY, 20, 20);

   balleX = balleX + deplacementX;
   balleY = balleY + deplacementY;
}

Définir le déplacement de la plateforme

Nous allons à présent définir le déplacement de notre plateforme. Notre plateforme va se déplacer verticalement uniquement mais devoir suivre également la position de notre souris.

Processing propose, par défaut, des variables qui correspondent à la position de la souris. Ainsi, pour exprimer la position de la souris en X, nous allons utiliser la variable mouseX. On se souvient que la valeur de la position de notre plateforme en Y est associée à la variable plateformeY. Cela donne donc :

plateformeY = (mouseY);

Et on ajoute donc cette ligne à notre code : 

void draw() 
{
   smooth();
   fill(255);
   rect(plateformeX, plateformeY, 25, 85);
   ellipse(balleX, balleY, 20, 20);

   balleX = balleX + deplacementX;
   balleY = balleY + deplacementY;
   plateformeY = (mouseY);
}

Ajout d’un écran noir

Reste un dernier petit détail à régler. Quant Processing répète le processus, il modifie la position de la balle et de la plateforme… Mais il affiche toutes les positions précédentes à l’écran, créant des traces. Pour faire en sorte de garder toujours la dernière position et donner l’impression que l’objet se déplace effectivement, nous avons une petite astuce : créer un écran noir à chaque itération pour « effacer » les dernières positions.

Il nous suffit alors d'ajouter un background de couleur noir en haut de notre code, comme ceci : 

void draw() 
{
   background(0);
   smooth();
   fill(255);
   rect(plateformeX, plateformeY, 25, 85);
   ellipse(balleX, balleY, 20, 20);

   balleX = balleX + deplacementX;
   balleY = balleY + deplacementY;
   plateformeY = (mouseY);
}

Tout est prêt, nos objets sont maintenant interactifs ! Vérifiez-le en exécutant votre code emoticon_smile

6. Faire rebondir la balle

Problème : notre balle bouge… mais ne rebondit pas : elle sort de la fenêtre de jeu. 

Posons un peu le contexte : nous voulons que si la balle touche le haut de la fenêtre, le bord droit de la fenêtre, le bas de la fenêtre, ou la plateforme, la balle rebondisse. Concrètement, au moment du contact, la balle va opérer un déplacement inverse, en X ou en Y. Nous allons, pour chacune de ces situations, redéfinir la valeur de la variable de déplacement.

Rebondissement sur le bord droit de la fenêtre

L’idée est la suivante : si la balle touche le bord droit, alors elle va rebondir. Ici, nous allons utiliser une autre structure disponible dans Processing : la condition. On va dire à notre programme quelque chose comme « si, à cet instant, la situation est telle que… alors, on fait cela ». Nous allons utiliser ici cette structure particulière. Elle s'écrit ainsi :

if (…)
{

}

Entre les parenthèses, nous allons définir les paramètres nécessaires pour que la condition soit validée. Si ces paramètres sont réunis, les instructions écrites entre les accolades seront exécutées. 

Pour que la balle rebondisse sur le bord droit, nous voulons que la balle touche le bord droit de la zone de jeu. Cela équivaut au fait que que la valeur de sa position horizontale correspond à la longueur de la fenêtre. Nous connaissons la variable qui définit la position horizontale de la balle, il s’agit de balleX. La largeur de la fenêtre s’exprime grâce à la variable prédéfinie width. La condition commence donc ainsi : « si la position de la balle en X est égale à la longueur de la fenêtre… ».

Mais attention : la position de la balle est définie à partir de son centre. Or, on veut que la balle rebondisse dès que son côté droit entre en contact avec le bord droit de la zone de jeu. Notre balle fait 20 px de diamètre. On va donc plutôt écrire quelque chose comme : « si la position de la balle en X est égale à la longueur de la fenêtre, moins la moitié du diamètre de la balle… ».

Deuxième condition : la balle se déplace de gauche à droite. On va donc préciser que la valeur de son déplacement doit être supérieur à zéro.

On résume notre condition : « si la position de la balle en X est égale à la longueur de la fenêtre ET que la valeur de son déplacement en X est supérieur à 0… ». En code, cela donne :

if (balleX >  width-10 && deplacementX > 0)
{

}

Si cette condition est remplie, que se passe t-il ? Et bien le déplacement horizontal de la balle est inversé : concrètement, la valeur de ce déplacement est redéfinie et rendue négative. Voilà ce que cela donne :

if (balleX >  width-10 && deplacementX > 0)
{
   deplacementX = -deplacementX;
}

Notre balle rebondit à présent sur le côté droit de la fenêtre !

Rebondissement sur le bas de la fenêtre 

L’idée est à peu près la même pour faire rebondir la balle sur le bas de la fenêtre. Notre condition ressemble à quelque chose comme : « si la position de la balle en Y est égale à la hauteur de la fenêtre ET que la valeur de son déplacement en Y est supérieur à 0, ALORS on inverse son déplacement en Y ».

if (balleY > height+10 && deplacementY > 0)
{
   deplacementY = -deplacementY;
}

Rebondissement sur le haut de la fenêtre

Pour faire rebondir la balle sur le haut de la fenêtre, les choses sont légèrement différentes. Dans l'idée, arrivé en haut de la fenêtre, la valeur de la position de la balle se rapproche du zéro. Donc la condition serait : « si la position de la balle en Y est égale est inférieure à la moitié de son diamètre ET que la valeur de son déplacement en Y est inférieure à 0, ALORS on inverse son déplacement en Y ».

Petite subtilité : on utilise la fonction abs() pour faire l'inversion. On a donc :

if (balleY < 10 && deplacementY < 0) 
{
   deplacementY = abs(deplacementY);
}

Rebondissement sur la plateforme

Enfin, il faut faire rebondir la balle sur la plateforme. Nous partons là encore sur une condition et plusieurs éléments doivent être réunis :

  • la position horizontale de la balle doit être inférieure à la position horizontale du plateau + la largueur du plateau (35 pixels).
  • la positon verticale de la balle doit être supérieure à la position verticale de la plateforme, mais inférieure à la position verticale de la plateforme + sa hauteur (85 pixels).

Si les paramètres de cette condition sont réunis, alors la valeur du déplacement horizontal de la balle est inversée. Et donc :

if (balleX < plateformeX+35 && balleY > plateformeY && balleY < plateformeY+85) 
{
   deplacementX = -deplacementX;
}

Ouf, nos éléments sont devenus interactifs ! Normalement, votre draw() devrait ressembler à ceci à présent :

void draw() 
{
   background(0);
   smooth();
   fill(255);
   rect(plateformeX, plateformeY, 25, 85);
   ellipse(balleX, balleY, 20, 20);

   balleX = balleX + deplacementX;
   balleY = balleY + deplacementY;
   plateformeY = (mouseY);

   if (balleX >  width-10 && deplacementX > 0)
   {
      deplacementX = -deplacementX;
   }

   if (balleY > height+10 && deplacementY > 0)
   {
      deplacementY = -deplacementY;
   }

   if (balleY < 10 && deplacementY < 0) 
   {
     deplacementY = abs(deplacementY);
   }

   if (balleX < plateformeX+35 && balleY > plateformeY && balleY < plateformeY+85) 
   {
      deplacementX = -deplacementX;
   }
}

Reste à définir maintenant le game over, c'est-à-dire quand la balle touche le côté gauche de la zone de jeu…

7. Le game over

Pour arrêter la partie, nous allons encore une fois utiliser une condition, mais cette fois-ci avec un seul paramètre : si la position horizontale de la balle est inférieure à la moitié de son diamètre (10 px), alors le jeu s’arrête.

Pour arrêter le jeu, nous allons demander à Processing d’arrêter l’exécution du programme en boucle. Ainsi, il ne répètera plus le programme. On va utiliser pour cela la fonction noLoop(). Cela donne :

if (balleX < 10) 
{
   noLoop();
}

Nous allons aussi mettre l’écran en rouge, pour bien signifier visuellement au joueur qu’il a perdu. Pour cela, nous reprenons la fonction background() avec, pour R, V et B, les valeurs associées au rouge : 255 pour R, 0 pour V et 0 pour B :

if (balleX < 10) 
{
   noLoop();
   background(255, 0, 0);
}

Votre fonction draw() doit donc, dans l'idée, ressembler à ceci :

void draw() 
{
   background(0);
   smooth();
   fill(255);
   rect(plateformeX, plateformeY, 25, 85);
   ellipse(balleX, balleY, 20, 20);

   balleX = balleX + deplacementX;
   balleY = balleY + deplacementY;
   plateformeY = (mouseY);

   if (balleX >  width-10 && deplacementX > 0)
   {
      deplacementX = -deplacementX;
   }

   if (balleY > height+10 && deplacementY > 0)
   {
      deplacementY = -deplacementY;
   }

   if (balleY < 10 && deplacementY < 0) 
   {
     deplacementY = abs(deplacementY);
   }

   if (balleX < plateformeX+35 && balleY > plateformeY && balleY < plateformeY+85) 
   {
      deplacementX = -deplacementX;
   }

   if (balleX < 10) 
   {
      noLoop();
      background(255, 0, 0);
   }
}

Pour continuer…

Et voilà, notre Pong est prêt à être joué !

Bien évidemment, il reste assez rudimentaire. On pourrait l'améliorer, en ajoutant une interaction pour démarrer une nouvelle partie, ajouter un score, modifier le design de notre jeu… Ce n'est ici, somme toute, qu'une base : à vous de jouer pour le rendre encore plus fun !

Code source

Vous pouvez télécharger le code source du projet dans l'onglet « Pièces jointes » ci-dessous :

  1. téléchargez le fichier pong.zip, puis décompressez-le
  2. ouvrez le fichier pong.pde dans Processing

Le code est complété par des commentaires pour vous aider.

pong_demo.gif

 

Code source

Retrouvez le code source de ce projet en bas de la page dans l'onglet « Pièce jointe » !

Tags:
Created by Maloup Ni on 2019/05/21 18:20
    

Need help?

If you need help with XWiki you can contact: