Profitez-en, après celui là c'est fini

Processing, treizième cours

décembre 8th, 2008 Posted in Processing

La programmation orientée objet est un concept relativement récent dans l’histoire de l’informatique (n’ayant été massivement employé que depuis le début des années 1990, quoique l’idée soit bien plus ancienne) mais il semble à présent impossible de vivre sans. En effet, des applications aussi banales que les interfaces graphiques d’ordinateurs ou la gestion des éléments d’un jeu interactif rendent le recours à ce « paradigme » presque obligatoire. Un grand nombre de langages de programmation courants sont orientés objet.

Pour bien comprendre l’intérêt de la programmation objet, prenons un cas simple et facile. Tout d’abord, imaginons que nous voulions créer un stylo « fou » qui dessine sur notre fenêtre d’affichage.

float x, y; // déclaration des variables décimales x et y

void setup(){
  // au démarrage
  size(180,180); // réglage du format de la fenêtre
  background(0); stroke(255); // réglage des couleurs
  x=90;y=90; //affectation de valeurs à x et y; 
}

void draw(){
  // script exécuté régulièrement 
  float newx = x+random(-5,5);  // création de newx
  float newy = y+random(-5,5);  // création de newy
  // contraintes appliquées à newx et newy
  // afin de les empêcher de sortir de l'écran 
  if(newx<0){newx=0;} if(newx>width){newx=width;}
  if(newy<0){newy=0;} if(newy>height){newy=height;}
  // tracé de la ligne
  line (x, y, newx, newy);
  // enfin, x prend la valeur de newx et y celle de newy
  x = newx; y = newy;
}

Le résultat d’un tel script sera le tracé totalement au hasard d’un trait blanc sur un fond noir. L’image ci-contre montre une capture de l’image de la « scène » à un moment du déroulement du programme. La fenêtre fait 180×180 pixels et le tracé commence en son centre c’est à dire au point (90,90). À chaque cycle, un nouveau point est calculé, qui s’éloigne de la position précédente à une distance variant entre -5 et +5 pixels dans les abscisses et entre -5 et +5 pixels dans les ordonnées.
On note qu’à chaque cycle, une fois que les valeurs newx (x + random(-5,5)) et newy (y + random(-5,5)) ont été calculées, la ligne est tracée avant que x soit remplacé par newx et y par newy.

Ce script très simple ne s’adapte cependant qu’au seul et unique cas où nous n’aurions qu’un seul traceur « fou » à utiliser. Mais comment faire pour tracer plusieurs traits en même temps, de plusieurs couleurs différentes en même temps ? C’est précisément là qu’intervient la programmation objet, elle me permet de décréter l’existence d’un objet nommé « traceur_fou » qui existera simultanément en plusieurs occurrences (ou instances), comme suit :

traceur_fou[] traceurs = new traceur_fou[0]; 
// déclaration d'un tableau de type "traceur_fou"
// ce tableau se nomme traceurs a au départ zéro éléments 

void setup(){
  // démarrage
  size(180,180);  // réglage du format de la fenêtre
  background(0); stroke(255);  // réglage des couleurs
   // création de dix objets de type traceur_fou
   // qui sont ajoutés au tableau traceurs 
  for(int a=0;a<10;a++){
    traceurs = (traceur_fou[]) append(traceurs, new traceur_fou());
  }
}

void draw(){
   // de manière régulière (draw())
   // une boucle permet de donner à chaque objet de type traceur_fou 
   // l'ordre d'activer sa commande dessine() 
  for(int a=0;a<traceurs.length;a++){
   traceurs[a].dessine();
 }
}

class traceur_fou{
  // déclaration des variables dont disposera chaque objet de type
  // traceur_fou
  float x=90;float y=90;color c;
  traceur_fou(){
     // "constructeur" d'objets de type traceur_fou
     // ici, la couleur "c" prend une valeur au hasard 
     c=color(random(255),random(255),random(255));
  }
  void dessine(){
     // dans le script "dessine" nous avons les éléments
     // utilisés dans le précédent programme 
     stroke(c);
     float newx = x+random(-5,5);
     float newy = y+random(-5,5);
     if(newx<0){newx=0;} if(newx>width){newx=width;}
     if(newy<0){newy=0;} if(newy>height){newy=height;}
     line (x, y, newx, newy);
     x = newx; y = newy;
  }
}

J’ai tenté d’écrire un programme très simple mais je m’aperçois que sa lisibilité est assez relative. On peut en voir l’effet sur l’image qui se trouve ci-contre et qui est une capture de l’image de la scène à un moment de l’exécution du programme. Au lieu de tracer un trait au hasard, notre programme en trace dix en même temps. Chacun a sa couleur propre, attribuée au hasard.

Le secret d’un tel programme est l’utilisation des « classes » d’objets. Une classe est un ensemble de propriétés de types disparates (une même classe peut contenir des chiffres, des chaînes de caractères, etc.) et de fonctions qui peuvent être attribuées à plusieurs occurrences indépendantes.
Pour prendre une métaphore assez simple, nous pouvons dire que chacun d’entre nous est une occurrence unique de la classe « être humain ». Nous avons de nombreux points en commun mais nous sommes tous différents par certains aspects : notre nom, notre âge, notre identité sexuelle, notre couleur de cheveux, etc. Nous sommes tous capables d’exécuter certaines fonctions de manière indépendante (marcher, courir, parler, etc.) ou simultanée (vieillir). Si je voulais créer une classe d’être humain disposant des propriétés et des fonctions énumérées ci dessus, je la rédigerais par exemple ainsi :

class etre_humain{
  String nom, sexe, cheveux; int age;
   etre_humain (String n, String s, String c, int a){
      nom = n; sexe = s; cheveux = c; age = a;
   }
  void  vieillis(){
      age = age+1;
  }
  void marche(){
      // ...commandes correspondant au fait de marcher
  }
}

Je peux à présent « créer » des instances de l’objet « etre_humain » de cette manière :

etre_humain  eh1 = new etre_humain("Georges", "homme", "brun", 40);
etre_humain  eh2 = new etre_humain("Charles", "homme", "brun", 50);
etre_humain  eh3 = new etre_humain("Lucille", "femme", "blond", 32);
etre_humain  eh4 = new etre_humain("Charlotte", "femme", "chatain", 36);

Chaque instance est stockée dans une variable. J’ai nommé mes variables eh1, eh2, eh3 et eh4, mais j’aurais pu leur donner d’autres noms bien entendu. À présent, si je désire connaître l’âge de Georges (qui est stocké dans eh1), je n’aurais qu’à demander print(eh1.age);. Si je désire changer la couleur des cheveux de Charlotte je peux écrire : eh4.cheveux = "roux";.
Enfin, si je souhaite faire vieillir Charles d’un an, je peux écrire eh2.vieillis();.
Ce genre d’exemple est un peu abstrait puisque nous ne parlons ici que de valeurs numériques ou de chaînes de caractères. La programmation orientée objet est souvent un peu difficile à comprendre au début.
Voici une dernière démonstration d’utilisation du paradigme « orienté objet ».

// déclaration d'une liste d'objets de type "cercle"
cercle[] TousMesCercles = new cercle[0];

void setup(){
  size(180,180); smooth(); strokeWeight(0.5); background(255);
  fill(255,25); // la couleur de remplissage est du blanc à ~10% d'opacité
  // remplissage du tableau TousMesCercles avec 89 objets de type "cercle"
  for(int a=1;a<90;a++){
    TousMesCercles = (cercle[]) append(TousMesCercles, new cercle(a));
  }
}

void draw(){
  noStroke();
  rect(0,0,180,180);
  stroke(0);
  for(int a=0;a<TousMesCercles.length;a++){
    TousMesCercles[a].dessine();
  }
}
class cercle{
  // déclaration des variables
  float rayon; float vitesse; float angle;
  cercle(float r){
    // initialisation de l'instance
    rayon=r; angle=0; vitesse=radians(random(-3,3));
  }
  void dessine(){
    // fonction "dessine" propre à chaque objet
    angle+=vitesse; // la variable "angle" est incrémentée de "vitesse"
    // petit calcul trigonométrique basique
    line(90,90,90+cos(angle)*rayon, 90+sin(angle)*rayon);
  }
}

Cette fois, nous avons créé une classe nommée « cercle » (nous avons inventé ce nom) qui utilise trois variables (nommées rayon, vitesse et angle, toutes de type « float ») et qui contient deux fonctions, son « constructeur » (sa fonction d’initialisation), nommé cercle()(c'est à dire ayant le même nom que la classe) et une fonction nommée dessine().
Une liste nommée  TousMesCercles est déclarée en tête du script puis remplie de 89 éléments dans la fonction setup(). Chacun de ces éléments s’est fait attribuer une variable nommée rayon et allant de 1 à 90. voit invoquer sa fonction dessine() depuis la fonction draw().
La fonction dessine() incrémente la variable angle en l’additionnant à la variable vitesse. Enfin une ligne est tracée entre le centre (90,90) et un point calculé à partir de l’angle et du rayon (90+cos(angle)*rayon, 90+sin(angle)*rayon). Nous reverrons peut-être cette formule trigonométrique de base ultérieurement.
Au début de la fonction draw(), la scène n’est pas repeinte en blanc mais juste recouverte d’un rectangle blanc à 10% d’opacité, ce qui donne un effet de persistance et d’effacement progressif des traits tracés. L’image qui se trouve ci-contre correspond à une capture de l’écran à un moment donné de l’animation mais vous devez copier-coller l’ensemble du code dans Processing pour en voir l’effet véritable.

Il est en tout cas important de noter que les opérations réalisées sur un tableau d’objets ne peuvent être rédigées comme suit :

monTableau = append (monTableau, monObjet);

mais doivent l’être de cette manière :

monTableau = (classe[]) append(monTableau, monObjet);

il est à noter aussi que le mot-clé this est valide dans Processing. Ce mot-clé sert, pour un objet, à se désigner lui-même. C’est l’équivalent de me dans le logiciel Director. Ainsi un objet pourrait s’ajouter de lui-même à une liste d’objets comme ceci :

bidule[] TousMesTrucs = new bidule[0];

class bidule{
  bidule(){
     TousMesTrucs = (bidule[]) append (TousMesTrucs , this);
  }
}

Si vous n’avez pas compris grand chose à cet article, pas de panique. Ces concepts sont souvent difficiles à assimiler. Le prochain article sera nettement plus accessible.

  1. 8 Responses to “Processing, treizième cours”

  2. By Peter on Déc 3, 2009

    Le code avec les classes semble contenir des erreurs.

    En tout cas, chez moi il ne fonctionne pas.

    Message d’erreur

    « The function dessine() does not exist »

  3. By Jean-no on Déc 3, 2009

    @Peter : chez moi ça marche, je viens de le copier coller. As-tu pris tout le script ?

  4. By Joss on Avr 7, 2010

    En copiant le code, je viens de remarquer une toute petite erreur dans la fonction draw a : … for(int a=0;aa<TousMesCercles.length;a++)…
    "aa<TousMesCercles" alors qu'il ne devrait y avoir qu'un seul a.

    Au passage, j'aime beaucoup ce blog et je viens d'y passer ma journee a le parcourir (^o^)

  5. By Jean-no on Avr 7, 2010

    @Joss : c’est corrigé, grand merci.

  6. By sandra on Sep 16, 2010

    Bonjour et un grand merci pour ce blog!
    J’aimerai savoir comment m’y prendre pour gérer l’affichage de plusieurs images (chargées dans le fichier Data) aléatoirement sur ma scène.
    Les images peuvent-elles être importées dans une class.
    Merci pour votre réponse et merci pour tous ces exercices qui donnent du fil a retordre!!!
    Sandra

  7. By Jean-no on Sep 16, 2010

    @sandra : pouvez-vous préciser ? Est-ce qu’il faut en afficher plusieurs à la fois, ou successivement, doivent-elles bouger,… ? Vous pouvez vous contenter de créer des listes String[] (contenant les noms des fichiers), PImage[] (contenant les images) ou PGraphics[] (sans doute pas ce qui vous intéresse), et d’aller piocher dedans ce qui vous intéresse lorsque vous le voulez.

  8. By Jul on Oct 25, 2012

    Sympas ce tuto et en plus ça fonctionne :D

  9. By Alain on Avr 6, 2015

    Mille mercis pour ce blog clair et précis inestimable pour un programmeur en herbe aussi passionné que mauvais. Processing semble être parfait pour les pseudo débutants de mon espèce. On peut assez vite se faire plaisir. Le concept de poo est bien abordé. Merci encore.

Postez un commentaire


Veuillez noter que l'auteur de ce blog s'autorise à modifier vos commentaires afin d'améliorer leur mise en forme (liens, orthographe) si cela est nécessaire.
En ajoutant un commentaire à cette page, vous acceptez implicitement que celui-ci soit diffusé non seulement ici-même mais aussi sous une autre forme, électronique ou imprimée par exemple.