Études supérieures - Les enseignements - Plateforme Temps Réel - Atelier Hypermédia - OpenFrameworks -

OpenFrameworks[03] = « Créer une classe (et plein d’objets) » ;

Dans ce cours, nous allons créer notre propre class : comme dans les cours sur Processing, une classe qui servira à dessiner une ellipse sur l’écran. Nous commencerons par créer un seul objet à partir de cette classe. Ensuite, on remplacera cet objet unique pour une suite d’objets contenus dans une liste.

Si vous n’avez jamais vu les notions de «  class  » ou d’«  objet  », je suggère que vous révisez le cours sur Processing concernant ce sujet : Programmation orientée-objet, avant d’attaquer ce cours.

Aussi, plus loin nous allons utiliser des listes pour controller plusieurs objets à la fois. Si vous ne savez pas comment créer un «  array  », reportez-vous sur le cours sur les Listes dans Processing.

Nouveau projet

Comme dans tout projet OpenFrameworks, commencez par copier un projet déjà existant, ici le projet «  _empyExample  ».

Notez que vous pouvez aussi créer un autre dossier à côté du dossier des «  examples  »

...et mettre cette copie dedans.

Ceci vous permet de gérer vos propres projets et de ne pas les mélanger avec les exemples. Le seul hic, c’est qu’il faut absoluement dans ce cas respecter la hierarchie des dossiers vis-à-vis les bibliothèques «  lib  » et «  addons  ». C’est pour cette raison que nous avons créer notre (nos) dossiers à côté du dossier «  examples  » et non pas ailleurs.

Ouvrez ensuite le fichier du «  Project file  » («  .xcodeproj  » sur mac) pour ouvrir votre projet dans votre éditeur de code. Appuyez sur «  Build and Run  » pour être sûr que vous avez bien copié votre projet.

Créer les fichiers de la classe (xCode)

Nous allons maintenant créer deux fichiers. Nous allons créer un fichier «  header  » qui se nommera sous la forme «  nomDufichier.h  », et un fichier «  implementation  » qui se nommera sous la forme «  nomDufichier.cpp  ». Le premier fichier sera le fichier qui déclare notre classe, déclare ses variables, et déclare ses méthodes. Le deuxième implemente cette classe, en explicant comment marche les variables et méthodes dans les objets créés à partir de cette classe.

Dans xCode, ces deux fichiers (header et implementation) se créent en même temps. Ouvrez l’onglet du dossier «  src  » dans votre projet xCode. Nous allons créer nos fichiers dedans. Vous avez deux options : 1) faire un clic gauche et sélectionnez «  Ajouter > Nouveau fichier  », ou 2) sélectionner dans le menu « fichier > Nouveau fichier  ».

Ensuite, vous allez voir une fenêtre présentant différent types de fichiers (qui ne ressemblera peut-être pas à 100% ma photo d’écran). Il suffit de sélectionner dans cette liste l’option «  C++ file  » (elle offrira le choix d’ajouter un fichier.h tout de suite après).

Vous aller donner un nom à votre fichier et je recommande de lui donner le même nom que votre classe. Vérifiez aussi qu’il a coché automatiquement la création d’un fichier «  Balle.h  » pour accompagner votre «  Balle.cpp  ». Attention aussi de noter le nom exacte pour plus tard, y compris si vous avez utilisez des majuscules ou minuscules pour le nom.

Vous devriez enfin voir les deux fichiers de votre classe à l’intérieur de votre projet. Si d’ailleurs vous voulez voir le code de vos fichiers dans l’éditeur sur la droite comme dans ma capture d’écran, il suffit d’appuyer sur l’icône de l’éditeur, ou appuyer sur Cmd-maj-E pour «  Editeur de code  ».

Créer les fichiers de la classe (CodeBlocks)

Nous allons maintenant créer deux fichiers. Nous allons créer un fichier « header » qui se nommera sous la forme « nomDufichier.h », et un fichier « implementation » qui se nommera sous la forme « nomDufichier.cpp ». Le premier fichier sera le fichier qui déclare notre classe, déclare ses variables, et déclare ses méthodes. Le deuxième implemente cette classe, en explicant comment marche les variables et méthodes dans les objets créés à partir de cette classe.

Dans CodeBlocks, on doit créer ces deux fichiers (header et implementation) séparément. Ouvrez l’onglet du dossier « src » dans votre projet CodeBlocks. Nous allons essayer de finir avec nos deux fichiers dedans, même si CodeBlocks va temporairement essayer de nous créer une arborescence bien bordellique — pas d’inquiétudes, on le rangera bien vite.

Pour commencer, sélectionnez «  file > New > file  » d’un des deux menus.

Sélectionner à gauche l’options «  files  » ce qui devrait faire apparaître trois choix : «  C/C++ Header  », «  C/C++ Source  », et «  Empty file  ». Sélectionnez «  C/C++ Header  ».

Ensuite, ouvrez le petit bouton «  ...  » et donner le nom «  Balle  » à votre fichier. Vérifiez bien que vous êtes en train de sauvegarder votre fichier «  Balle.h  » à l’intérieur du dossier «  src  » de votre projet. Attention !!! Il faut absolument cocher les deux options «  release  » et «  debug  ». Si vous ne cochez pas ces cases, votre classe ne sera pas inclus dans votre projet lorsque vous créez et executez votre projet (pas bien ça). Regardez bien ma capture d’écran pour être sûr de savoir de quoi je parle. Si après tout ça, vous ne cochez pas, écoutez, ce n’est plus mon problème ; on peut les amener à l’eau mais on ne pas les faire boire...

Ok. Ça y est, vous avez coché les petites cases comme un bon mouton citoyen, et CodeBlocks vous a généré votre fichier. Par contre, regardez-bien comme il l’a placé : c’est moche comme tout, à l’intérieur d’une arborescence pas possible de sous-dossiers. Réctifiez bien cette mochetté comme dans l’illustration en glissant votre fichier «  Balle.h  » sous l’icône «  src  ».

Ok. On a créé le fichier «  header  ». Il faut maintenant créer celui du code. Faites pareil que tout à l’heure, «  file > New > file  » et sélectionnez «  C/C++ Source  ».

Il vous demanderez si vous voulez créer des codes en langage C ou en langage C++. Choisissez «  C++  ».

Comme avant, vérifiez bien que vous sauvegardez dans votre dossier «  src  » et donnez le nom de «  Balle  » à votre fichier.

N’oubliez pas de cocher les cases «  release  » et «  debug  ». Attention !!! Il faut absolument cocher les deux options « release » et « debug ». Yada yada yada...

Vérifiez aussi que votre fichier s’appelle «  Balle.cpp  » et se trouve dans le dossier «  src  ».

Ok. Génial. On a généré notre fichier «  Balle.cpp  ». Achk ! C’est quoi cette @#&§% !? CodeBlocks a créé une arborescence pas possible pour mon fichier. Ugh. Bon, d’accord, comme avant, sortez ce fichier de là-dedans et replacez-le à l’intérieur de l’icône «  src  ».

Définir la classe

Ouvrez votre fichier «  Balle.h  ». Si vous êtes dans CodeBlocks, notez qu’il a déjà créé une suite de phrases commençant par #ifndef. Ces instructions veulent dire plus ou moins : «  Salut ordinateur. Lorsque que vous convertissez mon code en langage machine, vérifiez bien si vous n’avez pas déjà converti ce bout de code. Si ce n’est pas effectivement le cas, convertissez-le et souvenez vous de ce nom pour plus tard  ». Tout ce qui se trouve entre #ifndef et #endif sera traité de cette mannière. C’est juste une façon d’éviter d’inclure 10x votre code si une dixaine de parties de votre programme font référence à ce code.

Si vous êtes sur xCode il faut le faire rentrer à la main. Voici les instructions qu’il faut ajouter :


#ifndef BALLE
#define BALLE




#endif

Tout ce que vous aller écrire, s’écrira dans l’espace blanc entre ces lignes.

Il faut ensuite rentrer dans notre code une connection à l’ensemble de la bibliothèque OpenFrameworks. Ici, tout marche par référencement d’un fichier à un autre fichier. Si on veut inclure un bout de code dans un autre bout de code sans faire copier-coller (ce qui serait ingérable, vu le nombre de fonctions et sous-fonctions dans OpenFrameworks), on doit dire à la machine via son nom de fichier. Ce sont les noms des fichiers qui permettent de relier tout le code ensemble : votre code + le code d’OpenFrameworks + le code de toutes les bibliothèques open-source dont dépend OpenFrameworks + le code de votre système d’expoitation. Tout ce code est relié par un mot magique : «  #include  ».

Créez donc une connection de votre classe en direction de celui d’OpenFrameworks en écrivant ceci :


#include "ofmain.h"

Ceci permettra à toute référence aux fonctions d’OpenFrameworks dans votre classe d’être compris par le «  compilateur  ». Notez que vous n’avec pas besoin de point-virgule car il ne s’agit pas d’action, mais d’inclusion d’un fichier dans un autre lors de la «  compilation  » de votre code. Si vous ne comprennez pas cette phrase, comprennez au moins ceci : c’est le compilateur qui convertit votre code en langage machine avant son execution, et il doit vérifier toutes les erreurs de frappe et de connection (#include). malheureusement il ne peut pas corriger toutes les erreurs logique, même s’il peut en signaler pas mal. Par contre, il verra tout de suite si vous faites référence à une partie du programme dans un autre fichier qui n’a pas encore été relié par #include. Ce mot « include  » est donc très important et très puissant. Il relie tout le code ensemble.

Ok, nous allons maintenant déclarer notre classe. C’est la déclaration de la classe qui déterminera les variables et méthodes à notre disposition. On va également déclarer le «  constructeur  » de la classe qui plus loin (dans le «  Balle.cpp  ») définira ce qui se passera lors de chaque création d’objet à partir de notre class.

Voici le code tout ensemble qui définit notre class, entouré maintenant de notre #ifndef et le #include :


#ifndef BALLE
#define BALLE

#include "ofmain.h"

class Balle {

public:
       
        Balle();

        void update();
        void draw();
       
        float x,y;
       
};

#endif

Notez que contrairement à Processing/Java, nous écrivons un point-virgule à la fin de la déclaration de la classe.

Nous avons maintenant défini notre classe. Il y a une classe «  Balle  », avec un constructeur «  Balle()  », deux fonctions «  void update() » et «  void draw() » et deux variables «  x  » et «  y  ». Toutes ses propriétés sont «  public :  », c’est-à-dire accessible depuis l’extérieur de cette objet (par exemple depuis le testApp qui va bientôt les appeler.

Définissons maintenant ce que doit faire une balle de type «  Balle  ». Ouvrez donc le fichier «  Balle.cpp  » et ajoutez ces lignes :


#include "Balle.h"

Balle::Balle() {
        x = ofRandom(0, ofGetWidth());
        y = ofRandom(0, ofGetHeight());
}

void Balle::update() {

}

void Balle::draw() {
        ofSetColor(255,0,0);
        offill();
        ofEllipse(x,y,25,25);
}

Que veut dire tout ça ? Eh bien, c’est pas si différent que les classes dans Processing. Ici, nous expliquons ce qui se passera lors de la création d’un objet Balle::Balle() et chaque fois que nous voulons dessiner un objet void Balle::draw(). C’est indentique à Processing sauf le Balle:: qui indique le nom de la classe que nous sommes en train de définir. Le signe «  ::  » est nécessaire à cause de la séparation des fichiers «  .h  » et «  .cpp  ». Il faut savoir si «  update()  » et «  draw  » définissent ces méthodes de la classe «  Balle  » ou d’une toute autre classe, par exemple la classe «  OuiOui  » ou la classe «  Pimprenelle  ». Comme les fichiers sont (presque) toujours séparés dans C++, le «  ::  » est ici nécessaire.

Voilà, notre classe maintenant est déclaré et définie. Il suffira juste de s’en servir dans notre programme «  testApp  ».

Ajouter la classe au programme testApp

Ouvrez d’abord le fichier «  testApp.h  ». Vous allez d’abord lui dire d’inclure le fichier de votre code dans le sien, juste en dessus des autres inclusions :


#include "ofmain.h"
#include "ofAddons.h"
#include "Balle.h"

Vous allez ensuite rajoutez un objet à partir de votre classe «  Balle  », juste après la liste de méthodes :


Balle leNomDemonBallon;

Comme on voit dans l’illustration, on déclare notre classe «  Balle  » à côté de toutes les autres classes d’OpenFrameworks qui vont être appellées depuis notre programme («  testApp  » est notre application ou programme). Ensuite, on déclare qu’il y aura un objet nommé «  leNomDemonBallon  » (drôle de nom, d’accord, mais c’était pour que vous sentiez bien que vous pouvez lui donner n’importe quel nom). Juste en déclarant Balle leNomDemonBallon vous venez de créer un objet de type Balle dans « testApp  ».

Par contre, pour voir l’objet se dessiner, il faut lui dire de le faire. Voici le code qu’il faut modifier au fichier «  testApp.cpp  » :


void testApp::update(){
        leNomDemonBallon.update();
}

void testApp::draw(){
        leNomDemonBallon.draw();
}

Voilà ! Ça devrait marcher. Appuyer sur «  Build and Run  » (F9 dans CodeBlocks, Cmd-R dans xCode) et appréciez votre magnifique balle !

Créer plusieurs objets

Il y a deux façons de faire plusieurs objets. La façon simple, mais pas très souple, et la façon pas très compliqué non plus, et qui nous offre beaucoup de souplesse.

Commençons par le plus simple : transformons notre objet d’une seule balle en plusieurs balles. On va donc changer «  leNomDemonBallon  » à « tousmesBallons  » dans le header de «  testApp.h  », qu’on va également transformer en une liste en ajoutant le signe [100], ce qui créera un talbeau d’une centaine d’objets de type Balle :


Balle tousmesBallons[100];

Ensuite, il faut changer les méthodes update() et draw() pour dessiner non pas une seule balle, mais un tableau rempli d’une centaine de balles :


void testApp::update(){
        for(int i=0; i<100; i++) {
                tousmesBallons[i].update();
        }
}


void testApp::draw(){
        for(int i=0; i<100; i++) {
                tousmesBallons[i].draw();
        }
}

Comme nous avons maintenant 100 objets, il faut alors changer le update() et le draw() pour refleter ce changement. Au lieu de dessiner un seul objet, nous devons dessiner une centaine via le boucle for().

Remplacer le tableau par un tableau dynamique

Un «  Vector  » se comporte comme un tableau, avec quelques avantages en plus : notamment la possibilité de commencer avec une liste vide, puis de le remplir progressivement. Commencez par re-écrire la déclaration de votre liste de balles dans «  testApp.h  » :


vector<Balle> tousmesBallons;

maintenant que notre tableau est dynamique, et que celui-ci est d’une taille variable, nous avons besoin de savoir combien d’élements il y a dans notre Vector pour pouvoir le dessiner. Heureusement, les méthodes des Vector nous donne cette taille via la fonction size(). Remplacez alors les lignes de votre «  testApp.cpp  » en donnant un taille variable à votre boucle for() :


void testApp::update(){
        for(int i=0; i<tousmesBallons.size(); i++) {
                tousmesBallons[i].update();
        }
}


void testApp::draw(){
        for(int i=0; i<tousmesBallons.size(); i++) {
                tousmesBallons[i].draw();
        }
}

Ajoutez ensuite une méthode dans mousePressed() pour permettre d’ajouter des élements à notre tableau dynamique :


void testApp::mousePressed(int x, int y, int button){
        tousmesBallons.push_back( Balle() );
}

La fonction push_back() ajoute un élement à notre Vector. C’est cette ligne qui permet au Vector de s’agrandir ; ce qui n’est pas possible dans une liste classique comme plus haut (où on était limité à 100 élements).

Happy Code Farm

Si vous voulez savoir plus sur les vectors, les étudiants de l’Atelier hypermédia propose quelques exemples dans le Happy Code Farm à partir des exemples que nous venons d’explorer. Vous y trouverez un exemple plutôt simple Click O’Rama, suivi d’une version plus complexe Click O’Rama Record du même.

Il y a aussi deux projets qui montrent à la fois la puissance des vectors mais aussi la difficulté du syntaxe quand on veut commencer à faire des choses plutôt complexes Listes dynamiques.


ESAAix - École supérieure d’art d’Aix-en-Provence - http://www.ecole-art-aix.fr