Voici un cours pour mettre en pratique les concepts de programmation que nous venons d’apprendre.

Nous allons commencer par reproduire la fameuse balle rebonissante du premier jeu video Tennis For Two (Higinbotham, 1958), repris quinze ans plus tard dans le premier jeu video commércialisé : Pong!

Réactif + automatique = jeu

Jusqu’ici, nous avons créé des dessins et des animations réactifs. Une animation réactive, comme son nom l’indique, réagit à l’utilisateur, mais elle a tendance à réagir d’une mannière passive : l’utilisateur bouge à droite / le dessin bouge à droite, l’utilisateur bouge à gauche / le dessin bouge à gauche; et ainsi de suite.

Le contraire du réactif est le programme automatique. Le programme est passif d’une autre mannière : il réagit aux conditions internes du programme, sans prendre en compte les mouvements de l’utilisateur.

La rencontre de ces deux couches donnera naissance au jeu video. Si je contrôle trop l’ensemble des élements du programme, je n’ai plus de jeu — je suis dans un système réactif. Si le programme contrôle trop l’ensemble de ses élements, le programme devient purement automatique. mais si je crée la rencontre de ces deux couches, où je contrôle un élement (réactif) dans un milieu hostile (automatique), je suis du coup dans un jeu. Le jeu de Pong! repose totalement sur ce principe, il est même presque le degré zéro de cette rencontre.

Automatique

La partie qui nous intéresse pour l’instant est la couche automatique. Nous savons déjà comment faire réagir le programme à l’utilisateur, il faut maintenant voir comment faire tourner un élement du programme de façon automatique.

Pour faire rebondir une balle, nous avons besoin de trois choses essentielles :

  • Deux variables pour définir la position (horizontale + verticale) de la balle
  • Deux variables pour diriger le déplacement (horizontal + vertical) de la balle entre chaque cycle de la méthode draw( )
  • Une interrogation (if( )) sur la position de la balle : si la balle touche un des bords de l’image, changer la variable de déplacement corréspondante.

Déplacement

Nous allons commencer par la définition des variables de position et de mouvement. Pour comprendre la logique, essayez l’exemple suivant dans votre copie de Processing :


int deplacementX;
int x;

void setup() {

  size(200,200);
  // x définira la position horizontale de la balle
  x = 95;
  // la variable qui définit le *changement* de la position de la balle
  deplacementX = 0;
  // couleur de la balle
  noStroke();
  fill(0,0,0,255);

}

void draw() {

  // nettoyer l'image
  background(255);

  // changer la position de la balle
  x = x + deplacementX;

  rect(x,95,10,10);

}

Et voici le résultat. Notez que notre magnifique balle carrée ne bouge pas encore :

Pourquoi la balle ne bougent pas? Et bien, parce que 30 fois/sec nous demandons à la variable x de changer sa valeur en ajoutant la valeur d’une autre variable. maleureusement celle-ci est actuellement à zéro (deplacementX = 0;). Si j’ajoute la valeur zéro à 95, par exemple, j’aurais toujours 95. Par contre, si nous ajoutons une valeur positive, notre variable x changera de position :


int deplacementX;
int x;

void setup() {

  size(400,400);
  // x définira la position horizontale de la balle
  x = 0;
  // la variable qui définit le *changement* de la position de la balle
  deplacementX = 1;
  // couleur de la balle
  noStroke();
  fill(0,0,0,255);

}


void draw() {

  // nettoyer l'image
  background(255);

  x = x + deplacementX;

  rect(x,95,10,10);

}

Regardez comment la variable deplacementX maintenant arrive bouger le carré d’un pixel lors de chaque cycle dans la méthode draw().

Lors de chaque cycle de la méthode draw( ), la variable deplacementX augemente la valeur de la variable x. Puisqu’on dessine, 30 fois par seconde, un rectangle à la position de cette variable x, chaque fois que la valeur de x change, la position du rectangle change.

Rebondissements

D’accord, nous avons du mouvement, mais l’action est toujours la même, l’histoire est un peu décevant. Ce qui nous faut, c’est un rebondissement.

Pour faire “rebondir” notre balle, nous avons besoin d’interroger sa position lors de chaque cycle : quand la valeur de x sera trop grande, nous inverseront le mouvement en donnant une valeur négative au variable deplacementX :

55 - 1 = 54<br>
54 - 1 = 53<br>
53 - 1 = 52<br>
52 -1 = 51<br>
etc...

Nous ferons la même chose quand la valeur sera trop petite :


int deplacementX;
int x;

****

void setup() {

  size(400,400);

  x = 0;
  deplacementX = 1;

  noStroke();
  fill(0,0,0,255);

}


void draw() {

  background(255);

  x = x + deplacementX;

  if (x > width) {
    deplacementX = -1;
  }

  if (x < 0) {
    deplacementX = 1;
  }

  rect(x,195,10,10);

}

Ce qui donne un mouvement d’aller-retour de la balle :

mieux organiser le programme

Nous conaissons les actions de notre programme :

  • effacer tout
  • incrémenter/décrémenter x
  • vérifier la position
  • dessiner le carré

Ce serait bien si on pouvoit mieux lire ces actions en lisant le programme. Dans l’exemple suivant, nous avons justemment pris toutes nos actions, et les avons rassemblées dans des methodes distinctes :


int deplacementX;
int x;

void setup() {
  size(400,400);      // taille image
  x = 0;              // position horizontal
  deplacementX = 1;   // déplacement
  noStroke();         // pas de trait
  fill(0,0,0,255);    // remplissage noire
}


void draw() {

  nettoyer();
  bouger();
  rebondir();
  dessiner();

}


void nettoyer() {
  background(255);
}


void rebondir() {

  if (x > width) {
    deplacementX = -1;
  }

  if (x < 0) {
    deplacementX = 1;
  }

}


void bouger() {
  x = x + deplacementX;
}


void dessiner() {
  rect(x,195,10,10);
}

Bien sûr, il s’agit juste d’une question de style. Néanmoins, la méthode draw( ) est maintenant beaucoup plus lisible — en tout cas plus lisible pour un être humain. Regardez-le bien, il utilise maintenant 4 verbes, lisibles par nous, plutôt que des noms de méthodes avec des paramètres et des variables bizaroïdes. même si on ne savait pas programmer, on arriverait probablement à lire la méthode draw():


void draw() {

  nettoyer();
  bouger();
  rebondir();
  dessiner();

}

En un seul coup d’oeil, nous savons maintenant que, 30 fois par seconde, le processeur va nettoyer() l’image, bouger() la valeur de x, vérifier si x doit rebondir, puis dessiner() le rectangle à la position de x.

Rien n’a changé dans le résultat sur l’écran, mais notre programme est maintenant prêt à être modifié sans devenir illisible. On peut, par exemple, maintenant ajouter le mouvement/rebondissement vertical, voire même ajouter une notion de vitesse, sans créer une bouillie de code dans notre fenêtre de programmation.

Vertical

Pour ajouter un mouvement vertical, il faut ajouter 2 variables (une pour la position, une autre pour le déplacement). Comme notre programme est maintenant sectionné en petite parties, il devient relativement plus facile de savoir où ajouter ces élements :


int deplacementX, deplacementY; // deux variables de deplacement
int x, y; // deux variables de position

void setup() {

  size(400,400);

  x = 0;
  y = 0;              // position verticale

  deplacementX = 1;
  deplacementY = 1;   // deplacement vertical

  noStroke();
  fill(0,0,0,255);
}


void draw() {

  nettoyer();
  bouger();
  rebondir();
  dessiner();

}


void nettoyer() {
  background(255);
}


void rebondir() {

  if (x > width) {
    deplacementX = -1;
  }

  if (x < 0) {
    deplacementX = 1;
  }

  if (y > width) {      // vertifier rebondissement en bas
    deplacementY = -1;
  }

  if (y < 0) {          // verifier rebondissement en haut
    deplacementY = 1;
  }

}


void bouger() {
  x = x + deplacementX;
  y = y + deplacementY;  // bouger sur l'axe vertical
}


void dessiner() {
  rect(x,y,10,10);      // dessiner la position selon x et y
}

Du coup notre balle bouge et rebondit sur l’axe horizontal et veritcal. Le changement du programme n’était pas aussi douleureux qu’il aurait été si on avait écrit tout à l’intérieur du bloc draw().

Vitesse

Vous avez noté quelque chose d’étrange avec le mouvement?

La balle fait toujours une ligne droite, comme avant, mais en diagonal. Pourquoi? Parce que nous augmentons x en même temps que nous augmentons y. En gros, la valeur de x a la même valeur que y. Quand x= 5, y=5, quand x=150, y=150. La balle rebondit donc en ligne diagonale.

Pour changer cela, il faut changer la valeur de déplacément. Au lieu d’être toujours 1 ou -1, il faudrait que le déplacement horizontal soit, par exemple, 5 alors que le déplacement vertical serait -3. Il s’agit de l’introduction de la notion de vitesse dans le mouvement : au lieu de toujours se déplacer de 1 pixel à chaque cycle, on se déplacera de n pixels :


int deplacementX, deplacementY;
int x, y;

void setup() {

  size(400,400);

  x = 200;  // commençons au centre de l'écran
  y = 200;

  deplacementX = 5;
  deplacementY = -3;

  noStroke();
  fill(0,0,0,255);
}


void draw() {

  nettoyer();
  bouger();
  rebondir();
  dessiner();

}


void nettoyer() {
  background(255);
}


void rebondir() {

  if (x > width) {
    deplacementX = -5;
  }

  if (x < 0) {
    deplacementX = 5;
  }

  if (y > width) {
    deplacementY = -3;
  }

  if (y < 0) {
    deplacementY = 3;
  }

}


void bouger() {
  x = x + deplacementX;
  y = y + deplacementY;
}


void dessiner() {
  rect(x,y,10,10);
}

Ajouter de la souplesse

Notre balle bouge maintenant d’une mannière plus variée. mais il reste un dernier point un peu problématique : la balle fera toujours les {mêmes} mouvements : il sera toujours à la vitesse de 5 et de -5 sur l’axe horizontal, ou de 3 et de -3 sur l’axe verticale. En plus, si on veut changer ces valeurs en haut de programme…


deplacementX = 1;
deplacementY = -2;

…on est obligé de changer également les valeurs en bas du programme :

if (x > width) {
    deplacementX = -1;
}

if (x < 0) {
    deplacementX = 1;
}

if (y > width) {
    deplacementY = -2;
}

if (y < 0) {
    deplacementY = 2;
}

Devoir changer des valeurs partout dans un programme peut être fatiguant et nous amener à faire des erreurs.

On pourrait ajouter donc une petite sophistication, en explicant à la méthode rebondir() comment s’inverser, {quelque soit sa valeur}. Du coup, rebondir() réspectera la valeur de déplacement, mais inverserait le sens (+ / -) de cette valeur. Voici la version plus sophistiqué de la méthode rebondir() :


void rebondir() {

  // si on est trop à droite ET le déplacement horizontal est positif
  if (x > width && deplacementX > 0) {
    deplacementX = -deplacementX; // inverser la valeur (QUELQUE SOIT cette valeur)
  }

  // si on est trop à gauche ET le déplacement horizontal est négatif
  if (x < 0 && deplacementX < 0) {
    deplacementX = abs(deplacementX); // abs() enlève le négative de la valeur
  }

  // si on est trop bas ET le déplacement vertical est positif
  if (y > width && deplacementY > 0) {
    deplacementY = -deplacementY; // rendre négative la valeur
  }

  // si on est trop haut ET le déplacement vertical est negatif
  if (y < 0 && deplacementY < 0) {
    deplacementY = abs(deplacementY); // rendre positive cette valeur
  }

}

Du coup, nous pouvons changer la valeur de départ de deplacementX ou de deplacementY et le programme fonctionnera toujours.

mais notre code est quand-même un peu longue. Peut-être on peut rendre {encore plus sophistiqué}. Voici donc une version encore plus courte qui nous donnera le même comportement. Si vous n’arrivez pas à comprendre, ce n’est pas grâve — laissez tomber. L’important c’est d’arriver à faire ce qu’on veut : on n’est pas des informaticiens! Voici donc cette dernière sophistication :


void rebondir() {

  // si on est (trop à droite ET positive) OU on est (trop à gauche ET négative)
  if ( (x > width && deplacementX > 0) || (x < 0 && deplacementX < 0) ) {
    deplacementX = -deplacementX;
  }

  // si on est (trop bas ET positive) OU on est (trop haut ET négative)
  if ( (y > width && deplacementY > 0) || (y < 0 && deplacementY < 0) ) {
    deplacementY = -deplacementY;
  }

}

Ce code est plus court, mais peut-être plus difficile à lire, si vous n’avez pas totalement saisi l’usage des signes && et || pendant le chapitre sur les bifurcations. A vous de voir.

Exercise

Créez un programme qui dessine automatiquement en laissant des traces derrière le mouvement de la balle.