Moving Ball > Problème de test de sortie d'écran non effectué

Ok : ajoutons un 3e niveau de difficulté :

enum class Difficulty : uint8_t {

  easy,
  medium,
  hard

};

Difficulty est une énumération, qui définit un nouveau type, et qui est composée d’un ensemble de valeurs constantes nommées, qu’on appelle des énumérateurs, et dont le type sous-jacent est uint8_t.

À chaque nom d’une énumération est assignée une valeur entière qui correspond à sa place dans l’ordre des valeurs de l’énumération. Par défaut, la première valeur est 0, la suivante 1, et ainsi de suite.

Mais on peut tout à fait définir explicitement la valeur d’un énumérateur.
Autrement dit, on pourrait aussi écrire les choses ainsi :

enum class Difficulty : uint8_t {

  easy   = 0,
  medium = 1,
  hard   = 2

};

On peut également choisir de leur affecter des valeurs arbitraires :

enum class Difficulty : uint8_t {

  easy   = 2,
  medium = 4,
  hard   = 8

};

Dans notre cas, il est plus intéressant de conserver les valeurs entières contigües affectées par défaut :

enum class Difficulty : uint8_t {

  easy,
  medium,
  hard

};

Donc :

  • Difficulty::easy = 0
  • Difficulty::medium = 1
  • Difficulty::hard = 2

Pourquoi ? Parce-ce qu’on va justement se servir de ces valeurs entières pour positionner le curseur de sélection du niveau de difficulté :

gb.display.fillRect(20, 31 + 10*(uint8_t)game.difficulty, 4, 4);

L’expression (uint8_t)game.difficulty permet justement d’obtenir la valeur entière associée à l’énumérateur stocké dans la variable game.difficulty.

Ceci permet donc d’obtenir l’une des valeurs 0, 1 ou 2. Par conséquent, selon la valeur entière sous-jacente de game.difficulty on obtient l’un des 3 positionnements suivants. :

gb.display.fillRect(20, 31, 4, 4); // pour Difficulty::easy

gb.display.fillRect(20, 41, 4, 4); // pour Difficulty::medium

gb.display.fillRect(20, 51, 4, 4); // pour Difficulty::hard

Pratique, nan ?

Maintenant, pour implémenter la gestion du curseur de sélection, il existe plusieurs façons de le faire, en jouant sur les énumérateurs. Mais je préfère te donner la plus simple d’entre elles, puisque tu débutes ton apprentisssage du C++. Les autres font intervenir des notions plus avancées qui ne sont pas triviales :wink:


void menu() {

  if (gb.buttons.pressed(BUTTON_UP)) {
    switch (game.difficulty) {
      case Difficulty::medium: game.difficulty = Difficulty::easy; break;
      case Difficulty::hard:   game.difficulty = Difficulty::medium;
    }
  } else if (gb.buttons.pressed(BUTTON_DOWN)) {
    switch (game.difficulty) {
      case Difficulty::easy:   game.difficulty = Difficulty::medium; break;
      case Difficulty::medium: game.difficulty = Difficulty::hard;
    }
  } else if (gb.buttons.pressed(BUTTON_A))
    game.state = State::play;

  gb.display.print(6,  10, "SELECT DIFFICULTY");
  gb.display.print(32, 30, "EASY");
  gb.display.print(32, 40, "MEDIUM");
  gb.display.print(32, 50, "HARD");

  gb.display.fillRect(20, 31 + 10*(uint8_t)game.difficulty, 4, 4);

}

L’inconvénient ici réside dans le fait d’avoir à modifier les règles de changement de valeur de game.difficulty dès lors que tu ajoutes un nouvel énumérateur (donc un nouveau niveau de difficulté), ou bien que tu en retires un. Mais pour un petit ensemble d’énumérateurs, ça reste assez facile à gérer.

Enfin, pour répondre à ta dernière question :

Note que gb.frameCount n’est pas une fonction, mais un attribut (une propriété) de l’objet gb.

Par exemple, si on souhaite simplement modifier la couleur du curseur de sélection tous les 8 frames, on peut implémenter ça ainsi :

uint8_t phase = gb.frameCount % 24;

       if (phase < 8)  gb.display.setColor(BLUE);
  else if (phase < 16) gb.display.setColor(WHITE);
  else                 gb.display.setColor(RED);

  gb.display.fillRect(20, 31 + 10*(uint8_t)game.difficulty, 4, 4);
  • quand phase est compris entre 0 et 7, le curseur passe au bleu,
  • quand phase est compris entre 8 et 15, le curseur passe au blanc,
  • quand phase est compris entre 16 et 23, le curseur passe au rouge.

Est-ce que ça répond à ta question ? Ne sachant pas ce que tu entends par “modifier un élément graphique”, j’ai pris cet exemple au hasard, mais tu pensais peut-être à autre chose ?

Merci @Steph pour ton explication, toujours aussi claire !

Yep !

En fait, dans le cas du choix de difficulté HARD, j’aimerais que la raquette du joueur se contracte et s’étende de manière fluide.
Je comptais pour ce faire utiliser l’attribut framecount() pour que, par exemple, toutes les 4 ou 6 images, la raquette perde un pixel en taille (jusqu’à ce qu’elle se réduise de moitié) puis, que la raquette regagne quelques pixel (12 max) et ainsi de suite.

En utilisant le modulo, l’effet est saccadé, car la réduction / augmentation se fait trop rapidement sur quelques frames, puis rien ne se passe sur les frames suivantes.
Du coup, j’avais imaginé que le code suivant (à revoir) ne s’applique par exemple que tous les 4 ou 6 frames.

// Contrôles de la raquette du joueur de gauche (1)
  if (gb.buttons.repeat(BUTTON_UP, 0) && (raquette1.posY > 0)) {
    raquette1.posY -= 2;
  }
  if (gb.buttons.repeat(BUTTON_DOWN, 0) && (raquette1.posY+raquette1.hauteur < gb.display.height())) {
    raquette1.posY += 2;
  }

  if ((game.difficulty == Difficulty::hard) && ((gb.frameCount % 25) % 4 < 2)) {
    if (raquette1.hauteur >= 8) {
      raquette1.hauteur -= 1;
      raquette1.posY -= 1;
    }
    else if (raquette1.hauteur <= 12) {
      raquette1.hauteur += 1;
      raquette1.posY += 1;
    }

  }

Dis autrement, sur les frames 0 à 5, pas d’action, frame 6, on décrémente ou augmente la taille de la raquette selon la règle édictée ci dessus, frame 6 à 11, rien ne se passe, frame 12, on applique la décrémentation ou augmentation et ainsi de suite, en espérant qu’à l’arrivée, l’effet soit fluide pour l’utilisateur.

Bref, ajouter du piment dans le jeux :smiley:

Salut @Cric,

Pour déclencher une action à intervalles réguliers, par exemple toutes les 4 frames, tu peux utiliser gb.frameCount de la manière suivante :

if (gb.frameCount % 4 == 0) {

  // et là tu fais ce que t'as à faire...

}

Chaque appel à la fonction loop() implique une incrémentation d’une unité sur la propriété gb.frameCount. Par conséquent, l’expression :

gb.frameCount % 4

génèrera indéfiniment, la succession des valeurs suivantes :

0, 1, 2, 3  // puis ça recommence à 0, etc.

Du coup, il suffit d’intercepter l’une de ces valeurs pour déclencher une action à intervalles réguliers. Par exemple, lorsqu’on atteint la valeur 0 :

gb.frameCount % 4 == 0

(l’opérateur % est prioritaire sur ==)

Maintenant, pour en revenir à ce que tu souhaites faire, c’est-à-dire la possibilité de rétrécir ou élargir la raquette, j’t’ai pondu un bout de code qui fait le boulot. Teste-le sur ta Meta :

  • BUTTON_UP pour déplacer la raquette vers le haut
  • BUTTON_DOWN pour déplacer la raquette vers le bas
  • BUTTON_A pour rétrécir la raquette
  • BUTTON_B pour élargir la raquette

J’en ai profité pour t’orienter davantage sur la programmation orientée objet et te faire découvrir une autre façon de programmer, un peu plus structurée que du code monolithique pondu au kilomètre… :joy:

On définit un modèle nommé Paddle qui décrit :

  • sa structure (de quoi il est fait) => ce sont ses attributs (qui sont des variables internes),
  • son comportement (de quelle manière il peut agir sur lui même ou sur le reste du “monde”) => ce sont ses méthodes (qui sont des fonctions)

Le modèle peut être vu comme un plan de construction, à partir duquel on va pourvoir construire des objets, qui respectent tous ce plan de construction. Mais chaque propriété pourra s’exprimer différemment chez un objet ou un autre. Par exemple, le modèle Paddle précise que chaque instance dispose des attributs x et y :

struct Paddle {

  uint8_t x;
  uint8_t y;

};

Mais si on construit deux instances de ce modèle :

Paddle player_1;
Paddle player_2;

player_1.x = 4;
player_1.y = 32;

player_2.x = 73;
player_2.y = 32;

Tu vois qu’on peut attribuer des valeurs différentes aux propriétés x et y de chaque instance.

On peut d’ailleurs faire en sorte de paramétrer la construction d’une instance en définissant un constructeur qui accepte des arguments. Un constructeur est une méthode un peu particulière qui porte nécessairement le nom du modèle auquel il est rattaché. Par exemple, on pourrait définir le constructeur suivant :

struct Paddle {

  uint8_t x;
  uint8_t y;

  Paddle(uint8_t a, uint8_t b) {

    x = a;
    y = b;

  }

};

Et construire deux instances ainsi :

Paddle player_1(4, 32);
Paddle player_2(73, 32);

Ceci permet d’affecter des valeurs initiales aux propriétés x et y de chaque instance du modèle Paddle, au moment de leur création.

On pourrait également écrire le constructeur de cette manière :

struct Paddle {

  uint8_t x;
  uint8_t y;

  Paddle(uint8_t x, uint8_t y) {

    this->x = x;
    this->y = y;

  }

};

Dans ce cas, les arguments portent les mêmes noms que les attributs. Pour pouvoir les différencier dans le corps du constructeur, C++ fournit un opérateur d’autoréférence nommé this qui est un pointeur sur l’instance elle-même. Par conséquent quand on écrit this->x on fait référence à l’attribut x de l’objet.

Lorsque le constructeur accepte des arguments nommés x et y, ces arguments sont déclarés comme des variables locales au constructeur, mais viennent masquer les attributs x et y définis au niveau du modèle… Ça pose problème, car si on écrivait :

struct Paddle {

  uint8_t x;
  uint8_t y;

  Paddle(uint8_t x, uint8_t y) {

    x = x;
    y = y;

  }

};

Il y aurait une indétermination lié au conflit de nommage… de quel x on parle (à gauche comme à droite) quand on écrit :

x = x;  // ???...

En réalité, ici, on ne ferait que réaffecter à l’argument x sa propre valeur… ce qui ne servirait évidemment à rien…

L’opérateur this permet de lever l’ambigüité :

this->x = x;
  • à gauche on fait référence à l’attribut de l’objet,
  • à droite on fait référence à l’argument du constructeur.

C++ nous permet également de définir un constructeur avec une liste d’initialisation des attributs avant que le corps du constructeur soit exécuté. Par exemple, on pourrait réécrire notre constructeur de la façon suivante :

struct Paddle {

  uint8_t x;
  uint8_t y;

  Paddle(uint8_t x, uint8_t y) : x(x), y(y) {

    // et on n'a rien à faire de plus ici...

  }

};

C’est une manière plus concise d’écrire exactement la même chose que précédemment. Ici il n’y pas d’ambigüité, ce qui se trouve entre parenthèses fait référence aux arguments du constructeur. Alors que ce qui précède la parenthèse ouvrante fait référence à l’attribut de l’objet.

Voilà, ceci devrait te permettre de mieux comprendre l’écriture du constructeur que j’ai défini dans mon code :

Paddle(uint8_t x, uint8_t y) : x(x), y(y), h(H), th(h), sizing(false) {}

Tu noteras également que j’ai défini quelques constantes à l’intérieur du modèle Paddle :

struct Paddle {

  static const uint8_t W        = 3;
  static const uint8_t H        = 16;
  static const uint8_t H_SHRUNK = 8;
  static const uint8_t VY       = 2;

};

Ces constantes sont définies dans l’espace de nommage du modèle Paddle et sont accessibles par toutes les instances. Mais aucune copie de ces constantes ne sont reportées au sein de chaque instance. Elles sont définies de manière unique et sont partagées par toutes les instances. C’est ce que précise le mot-clef static.

Bon, je ne vais pas te pondre ici un cours complet sur la programmation orientée objet. Il y aurait beaucoup trop à dire, surtout avec les subtilités du C++, et tu serais très vite rassasié… Mais je trouvais intéressant de t’amener à t’y intéresser :wink:

Si c’est imbitable pour toi, n’hésite pas à me le dire, et je reviendrai à un style de programmation plus procédural.

Concernant le rétrécissement ou l’élargissement de la raquette, c’est dans la méthode update() du modèle Paddle que ça se passe. J’ai implémenté deux approches pour donner deux effets visuels différents :

  • une première approche où la variation de la largeur de la raquette suit une progression arithmétique (donc linéaire),
  • et une seconde approche où la variation de la largeur de la raquette suit une progression géométrique (donc exponentielle).

Une macro en début de code permet de déterminer quelle progression tu veux appliquer :

/**
 * 0 for an arithmetical progression
 * 1 for a geometrical progression
 */
#define GEOMETRIC 0

Je te laisse tester les 2, tu appliqueras celle que tu préfères…

Progression arithmétique de raison 2 :

demo-arithmetical

Progression géométrique de raison 0.5 :

demo-geometrical

On pourrait utiliser gb.frameCount pour espacer les variations par un nombre constant de frames (comme expliqué au début de ce post), mais l’effet obtenu est saccadé… même avec gb.frameCount % 2 == 0 qui n’opère qu’une frame sur deux. Donc je n’ai pas utilisé cette possibilité qui me semble insatisfaisante.

Je suppose que tu n’auras pas trop de mal à comprendre comment tout s’articule. Mais si une zone d’ombre subsiste, n’hésite pas à revenir poser de nouvelles questions :wink:

1 Like

Salut @Cric,

Plus de nouvelles, bonne nouvelles ?..

Hello @Steph,

J’ai étudié ton code avec attention :

  • J’ai beaucoup de mal à me mettre au CPP (plus particulièrement le Struct Paddle) => je vais à nouveau regarder ce WE ;
  • il y a des bouts de code que je ne comprends pas (exemple ci après)
        #if GEOMETRIC
            fh = h;
        #endif
  • j’ai beaucoup de mal à comprendre l’algo suivant :
        if (sizing) {

            #if GEOMETRIC

                float_t dh = th - fh;

                if (abs(dh) < 1)  {

                    h      = th;
                    sizing = false;

                } else {
                    
                    fh += .5f * dh;

                    if (abs(fh - h) > 1.5f) h += fh < h ? -2 : 2;

                }

            #else

                if (h == th) sizing = false;
                else         h += th - h < 0 ? -2 : 2;
            
            #endif
            
        }

Bref, c’est pas encore gagné :thinking:

En C++, les struct permettent d’agréger des variables (donc des données) avec des services dédiés à la manipulation de ces données (qu’on appelle des méthodes, mais qui sont de simples fonctions). Cette implémentation est la mise en oeuvre du paradigme de la programmation orientée objet. Le C++ introduit également le mot-clef class, qui se distingue du mot-clef struct, pour désigner des objets qui sont sémantiquement identiques, mais avec des règles d’encapsulation différentes… Je ne vais pas rentrer dans ces détails ici, pour ne pas compliquer les choses, et considérer que, pour le moment, tu peux t’en tenir aux struct. Mais, oui, tu as tout intérêt à creuser ces notions fondamentales de ton côté !

Maintenant pour en revenir aux “bouts de code que tu ne comprends pas”, c’est pas compliqué. C++ te permet de définir ce que l’on appelle des macros qui seront traitées par le préprocesseur, qui va modifier le code source avant la compilation. Par exemple, dans mon code, j’ai défini la macro GEOMETRIC ainsi :

/**
 * 0 for an arithmetical progression
 * 1 for a geometrical progression
 */
#define GEOMETRIC 0

Autrement dit, si je veux que le changement de taille de la raquette suive une progression arithmétique, je dois écrire ceci :

#define GEOMETRIC 0

Mais si je veux qu’il suive une progression géométrique, je dois écrire cela :

#define GEOMETRIC 1

Le préprocesseur va analyser le code source et y apporter des modifications en fonction des directives de précompilation qu’il va y trouver. Par exemple, partout où il trouvera la chaîne GEOMETRIC, il la substituera avec la valeur qu’elle représente (donc 0 ou 1, en fonction de ce que tu auras défini dans la macro).

Mais il remplacera aussi ce bout de code :

#if GEOMETRIC
    float_t fh;
#endif

en fonction de la valeur représentée par GEOMETRIC, donc :

  • si GEOMETRIC vaut 1, il substituera le code ci-dessus par un simple :

    float_t th;
    
  • et si GEOMETRIC vaut 0, alors il le remplacera par… rien du tout ! Autrement dit, le bout de code compris entre #if et #endif sera tout simplement supprimé.

Même chose pour ce qui se passe dans la méthode update(). Autrement dit :

  • si GEOMETRIC vaut 0, la fonction update() se réduit à :

    void update() {
    
        if (sizing) {
            if (h == th) sizing = false;
            else         h += th - h < 0 ? -2 : 2;
        }
    
        uint8_t h2 = .5f * h; 
    
                if (y < h2)            y = h2;
        else if (y + h2 > SCREEN_H) y = SCREEN_H - h2;
    
    }
    
  • par contre, si GEOMETRIC vaut 1, alors la fonction update() devient :

    void update() {
    
        if (sizing) {
            float_t dh = th - fh;
            if (abs(dh) < 1)  {
                h      = th;
                sizing = false;
            } else {
                fh += .5f * dh;
                if (abs(fh - h) > 1.5f) h += fh < h ? -2 : 2;
            }
        }
    
        uint8_t h2 = .5f * h; 
    
                if (y < h2)            y = h2;
        else if (y + h2 > SCREEN_H) y = SCREEN_H - h2;
    
    }
    

Pratique, nan ?

Merci @Steph, c’est très clair.

Comme je trouve l’approche géométrique plus spectaculaire au niveau du rendu, j’ai simplifié ton code pour en faciliter la lecture en conservant les parties pour GEOMETRIC équivalant 1.

Par contre, je ne comprends pas cette formule (notamment .5f et < h ? -2 : 2)

        else {
            fh += .5f * dh;
            if (abs(fh - h) > 1.5f) h += fh < h ? -2 : 2;
        }

Je continue mes investigations !

Ha… personnellement, je trouvais l’effet obtenu plus sympa avec une progression arithmétique. Du coup j’ai bien fait de te proposer une autre solution. :slightly_smiling_face:

Bref, donc, oui, tu peux simplifier le code en enlevant toutes les structures conditionnelles qui prennent en compte la valeur de la macro GEOMETRIC en ne retenant que les portions de codes où on considère qu’elle vaut 1.

Et l’instruction suivante :

if (abs(fh - h) > 1.5f) h += fh < h ? -2 : 2;

est équivalente à :

if (abs(fh - h) > 1.5f) {
    if (fh < h) {
        h -= 2;
    } else {
        h += 2;
    }
}

Est-ce que c’est plus clair pour toi ?

1 Like

Oui, c’est plus clair comme cela (même si je ne sais pas ce qu’est 1.5f ? f veut dire quoi ?

J’ai avancé cet après-midi sur mon code. Du coup, je m’y prends en 3 étapes :

  1. Simplification de ton code => fait (je pourrai y revenir si je change d’avis entre arithmétique et géométrique :wink:)
  2. Restructuration du code de mon Pong en m’inspirant du tient (i.e. Struct, Constructeur, 1 instance par raquette (joueur / Gamebuino)…) => en cours
  3. Intégration de ton code dans ma version de Pong restructurée => à faire

L’intérêt de reprendre mon code et de l’adapter est que ça me force à comprendre et à m’interroger.
Je ne te cacherai pas que j’en ai bien ch*é à décortiquer ton code, mais j’avoue qu’après y avoir passé du temps, il est bien plus lisible que le miens, et que cela me pousse à poursuivre en C++.

Cela m’amène une première question : dans le code suivant, tu appelles la méthode update() de l’instance player qui contient le corps du jeu.

void update() {

    player.update();
    
}

Dans mon cas, j’ai 2 instances de Paddle : player (raquette de gauche) et computer (raquette de droite). Du coup, est-ce qu’il faut je fasse pareil avec computer.update() ? Car ça n’a pas de sens d’exécuter 2 fois le corps du jeu ? Ou le corps du jeu doit être mis ailleurs que dans Paddle ?

Par contre, j’aurai bien ce code à exécuter pour afficher chacune des requêtes ?

void draw() {

    player.draw();
    computer.draw();
    
}

Je galère encore pour restructurer les “computer.posY” par exemple en C++, mais il faut que je prenne le temps de le faire.

Attention… le “corps du jeu” est contenu dans la fonction loop() et s’articule autour de la séquence classique :

readButtons();
update();
draw();

Et c’est dans la fonction globale update() qu’on est censés mettre à jour les variables du jeu :

void update() {

    player.update();

}

Dans mon petit exemple, je n’avais que les variables du joueur à mettre à jour, mais dans ton cas, il faudra également mettre à jour les variables de l’adversaire :

void update() {

    player.update();
    computer.update();

}

Même chose pour la fonction draw(), comme tu l’as justement suggéré. :slightly_smiling_face:

Bon courage !
Toutes les notions abordées dans ce thread te seront très utiles pour tes projets, et une fois le cap franchi, tu pourras te pencher sur des notions plus avancées (et découvrir les class par exemple). Mais chaque chose en son temps. J’ai moi-même encore une très longue route devant moi concernant le C++ (que j’ai découvert avec la Gamebuino) !

Bon, j’ai simplifié mon code pour commencer par la gestion de la raquette du joueur.

Première difficulté : la raquette sort de l’écran (et disparait) alors que normalement, je l’ai bloquée en haut et en bas avec le code suivant

    void update() {
           if (posY < 0)                           posY = 0;
      else if (posY+hauteur > gb.display.height()) posY = gb.display.height();
      }

Deuxième difficulté : comment faire pour passer la variable “hauteur” définie dans la fonction menu() (dont la taille dépend de la difficulté choisie) dans le modèle Paddle ?

  if (game.difficulty == Difficulty::hard) {
      hauteur = 8;
    }
  else {
      hauteur = 12;
    }

Dernière question par anticipation : lorsque je vais afficher la raquette gérée par l’ordinateur avec une couleur différente (par exemple rouge) de celle du joueur (bleu), comment faire ?
Car il n’y a qu’une seule méthode draw() dans Paddle…

Idem pour le calcul du positionnement de la raquette de l’ordinateur (que je vais mettre dans la méthode update()) : comment sera t-elle différenciée entre player.update() (où je mets le code pour ne pas sortir de l’écran) et computer.update() (qui contiendra le code de calcul de la position par rapport à la balle en mouvement) ?
Désolé pour les questions de néophyte…

Rapidement, parce-que je file au TAF là…

1. La raquette sort de l’écran…

Normal, tu ne la replaces pas correctement… Ce n’est pas :

posY = gb.display.height();

Mais :

posY = gb.display.height() - hauteur;

2. Passer la variable hauteur au modèle Paddle

Il suffit d’ajouter un argument h au constructeur :

Paddle(uint8_t posX, uint8_t posY, uint8_t h)
: posX(posX)
, posY(posY)
, vitesse(V)
, hauteur(h) // <-- la hauteur de la raquette est donc paramétrable
, largeur(L)
{}

Ici tu ne peux utiliser la constante H (hauteur fixée par défaut) définie dans Paddle, puisque ton menu induit une hauteur variable…

3. Des couleurs différentes pour les raquettes

Même chose, tu peux ajouter un argument color au constructeur, que tu dois stocker dans ton modèle pour l’utiliser au moment de dessiner la raquette :

struct Paddle {

    static const uint8_t V = 2;
    static const uint8_t L = 3;

    uint8_t  posX;
    uint8_t  posY;
    uint8_t  vitesse;
    uint8_t  hauteur;
    uint8_t  largeur;
    Color    color; // <-- chaque raquette a sa propre couleur
    
    Paddle(uint8_t posX, uint8_t posY, uint8_t h, Color color) // <-- paramétrée ici
    : posX(posX)
    , posY(posY)
    , vitesse(V)
    , hauteur(h)
    , largeur(L)
    , color(color) // <-- on l'affecte au modèle ici
    {}
    
    void up()   { posY -= V; }
    void down() { posY += V; }

    void update() {
        uint8_t sh = gb.display.height();
             if (posY < 0)            posY = 0;
        else if (posY + hauteur > sh) posY = sh - hauteur;
    }

    void draw() {

        gb.display.setColor(color); // <-- et on fixe la bonne couleur à l'affichage
        gb.display.fillRect(posX, posY, largeur, hauteur);

    }

};

Il faut que tu poses un peu et que tu reprennes un à un les concepts que je t’ai présentés… Tu disposes maintenant de toute la logique pour parvenir à trouver les réponses à tes questions tout seul il me semble. Tu ne crois pas ? Tu as toutes les briques, il ne reste plus qu’à les assembler correctement :wink:

Tu peux aussi t’inspirer des exemples traités dans mon tuto Handling images on the Gamebuino Meta qui utilisent les mêmes concepts… Notamment ceux des chapitres :

  • How to display your images
  • How to move your sprite

Je rebondis juste sur la fin de la réponse de Steph pour appuyer sur ce qu’il a dit: n’hésitez pas à regarder l’académie et à analyser un peu les codes. Rechercher fait certe perdre un peu de temps mais ça fait aussi progresser et ça évite de poser toujours les même questions sur le forum. En diverssifiant les questions, on rend sa lecture plus intéressante et en lisant un peu les codes, les tutos de l’académie et les références de base, ca rend le temps passé par ceux qui les ont écrit plus profitable.