SOKOBAN (PARTIE 2) : AJOUTONS LES GRAPHISMES

Creations

jicehel

5 years ago

Durée: 30 minutes (à la louche)

Niveau: ancien débutant ayant suivi les premiers tutos

Prérequis

  • Avoir une Gamebuino META
  • Avoir fait l'atelier Installation de la Gamebuino META
  • Avoir suivi les tutoriels jusque au Casse-briques et SOKOBAN (Partie 1) sans quoi vous n'aurez pas le début et par conséquent la suite sans le début, c'est moins bien....


RÉFLÉCHISSONS SUR CE QUE NOUS VOULONS FAIRE

Nous avons vu dans la partie précédente comment charger les données d'un niveau et nous les avons affiché de manière brute: nous avons affiché le caractère qui nous sert dans le tableau "niveau[ligne][colonne]" à suivre l'état d'une "case" du jeu (Si c'est une case Mur, Caisse, Destination, et à savoir si le personnage s'y trouve).

Bien maintenant, des caractères c'est bien mais c'est une peu moche, non ? Alors à la place, on va afficher des sprites. On en tait là dans le dernier tuto. Maintenant il faut réfléchir à la taille de ces sprites. Plus le sprite est gros, plus il est beau (enfin normalement, il peut aussi être gros et moche et là vous avez besoin de quelqu'un qui a du goût et un peu de talent...). Plus il est petit, plus on peut en afficher. On a une solution de 80 pixels de large sur 64 de haut en mode résolution standard. Si on prend un résolution de 12 pixels, on peut afficher 5 sprites de haut : 12 x 5 = 60, c'est court. On aura du mal à appréhender le niveau dans notre cas. Si on prend un sprite qui est 2 fois plus petit: 6 pixels, on peut en mettre: 64/6 soit 10 (2 fois plus c'est assez logique). C'est super mais dessiner un sprite sur 6 pixels dans notre cas, ne nous permet pas de faire ce que je souhaite dessiner. C'est un choix, on pourrait adapter les graphismes pour qu'ils tiennent sur 6 pixels. Je m'en tiendrais donc au choix le plus standard: 8 pixels, ce qui nous laisse 64/8 soit 8 sprites de haut. On pourrait donc en mettre 10 en largeur (80/8) mais dans ce cas il faudrait afficher les informations du jeu sur les sprites. C'est faisable: le jeu Meze le fait très bien mais nous on va s'y prendre autrement: on va garder une partie de l'écran pour afficher ces informations. C'est encore un autre choix et c'est à vous de décider quand vous créez votre jeu quel est le rendu que vous souhaitez. Il n'y a pas de bonnes et de mauvaises solutions (enfin si, si on ne voit rien et que l'on n'arrive pas à lire, c'est un mauvais choix...)


Assez RÉFLÉCHI DESSINONS LE CE NIVEAU

Pour la première partie du job, utilisez le programme que vous souhaitez pour dessiner vos sprites. (regardez ce lien si besoin: http://pixeljoint.com/forum/forum_posts.asp?TID=11299)

Pour mieux comprendre la manipulation des images, vous pouvez lire ce tuto: https://gamebuino.com/creations/images

Bon bref, faites vos images de 8 x 8 pixels et sauvegardez les en PNG ou en BMP 24 bits.

Dans mon cas, j'ai créé ces sprites:

Puis j'utilise cet utilitaire https://gamebuino.com/creations/png-to-code pour les convertir en code mais on pourrait également charger les fichiers tel qu'indiqué dans le tutorial "images". Vous êtes le créateur de vos (nos) jeux alors choisissez la méthode que vous préférez.

Vérifions que nous associons bien une image pour chaque case de notre jeu

Légende :

# : mur             =>      mur.bmp
$ : caisse          =>      caisse.bmp
. : destination  =>      cible.bmp
* : caisse sur une zone de rangement => caisse_sur_cible.bmp
espace (' ')             => sol.bmp

On ne dessine pas pour le moment le personnage, donc pour le moment:
@ : personnage =>  sol.bmp
+ : personnage sur une zone de rangement => cible.bmp

=> OK, ça colle. Nous allons pouvoir ajouter nos images dans notre code.

Nous allons ajouter beaucoup de lignes en intégrant nos données. Afin de ne pas surcharger les parties importantes du code, nous allons ouvrir un nouvel onglet pour y mettre nos images. Hors un  programme peut être constitué de centaines voir de milliers de lignes de code et de       fonctions.  Nous n'atteindrons sans doute jamais  cette complexité, mais dès que le code devient complexe,  il devient essentiel pour s’y retrouver d’organiser votre code en plusieurs modules.     Un module est un couple de fichier source (extension .c pour un fichier c et .ino pour un fichier Arduino )  et un fichier d'en-­tête (« header », extension .h).

On regroupe dans un module des fonctions et/ou  variables, des types qui traitent d'une sous-   partie du programme global. On essaye de les grouper par thèmes. Vous verrez dans les codes des autres Gamebuinistes, souvent, on a une partie Player, une parie Ennemi, une partie son, etc... Dans tous les cas, ce découpage se fait après analyse.

Pour cela, cliquez sur la flèche qui descend qui se trouve en haut à droite de votre fenêtre de code (regardez sur la capture en dessous pour mieux voir où ça se trouve)

Plus tard, on aura sans doute des habitudes ou des conventions, mais en attendant, je vous donne ma méthode que j'ai construit en regardant comment des développeurs plus expérimentés faisaient mais elle peut sans doute être amélioré. En fait j'écris autant ses tutos pour vous que pour moi car j'apprends aussi à programmer sur la Meta...  Ca me permet de faire le point sur mon expérience et de mettre me idées au claire.

Bon bref, on va choisir dans le menu "Nouvel onglet".

Vous allez ensuite donner un nom à cet onglet qui correspond à un fichier qui va être créé dans le même répertoire que SOKOBAN.ino (donc le répertoire SOKOBAN). Nous allons l'appeler Graphics.ino (C'est un choix de ma part, vous pouvez l'appeler comme vous voulez ...)

Dans cet onglet, on va rajouter nos graphiques mais pour pouvoir les utiliser depuis n'importe quel onglet, nous allons recommencer la même opération de création d'onglet pour créer le fichier Graphics.h

Si tout c'est bien passé, le haut de votre fenêtre ressemble désormais à ça:

 

OK, maintenant utilisons image to code converter (https://gamebuino.com/creations/png-to-code): Hyper simple: on fait glisser son image sur la partie grise avec le texte: "Drop your image into this zone"... On ne peut pas plus intuitif (à part peut être avec des commandes neuronales...) et on récupère le code en dessous en le sélectionnant puis en faisant en clic droit, en choisissant "copier" puis en allant dans "Graphics" en faisant un clic droit pour choisir "coller"...

En effet dans  le  fichier source (".c" ou ".ino") du module,  on insère le code source  du module  : on initialise des   variables globales et on y place toutes  les fonctions du module.             

C'est ce que nous faisons pour nos 5 images on collant le code généré.  A la fin, on doit avoir ça dans "Graphics"

const uint16_t CaisseData[] = {8,8,1, 1, 0, 0, 0x0,0x0,0xbc6a,0xbc6a,0xbc6a,0xbc6a,0x0,0x0,0x0,0xb429,0xbcab,0xbccc,0xbc8a,0xb407,0xabe7,0x0,0xbc6a,0xbc8a,0xc52d,0xcd6f,0xabe7,0xabe7,0xb449,0x8b46,0xbc6a,0xbc8a,0xc52d,0xabe7,0xabe7,0xb48b,0xc4ec,0x8b46,0xbc6a,0xb46a,0xabe7,0xabe7,0xac29,0xc52d,0xbcec,0x9345,0xbc6a,0xac08,0xabe7,0xb48b,0xc52d,0xc54e,0xbccc,0x9325,0x0,0xabe7,0xb46a,0xbcab,0xb46a,0xb48a,0xb449,0x0,0x0,0x0,0x8b46,0x8b46,0x8b46,0x8b46,0x0,0x0};
Image Caisse = Image(CaisseData);

const uint16_t Caisse_sur_cibleData[] = {8,8,1, 1, 0, 0, 0xf800,0x0,0xbc6a,0xf800,0xf800,0xbc6a,0x0,0xf800,0x0,0xb429,0xbcab,0xbccc,0xbc8a,0xb407,0xabe7,0x0,0xbc6a,0xbc8a,0xc52d,0xcd6f,0xabe7,0xabe7,0xb449,0x8b46,0xf800,0xbc8a,0xc52d,0xabe7,0xabe7,0xb48b,0xc4ec,0xf800,0xf800,0xb46a,0xabe7,0xabe7,0xac29,0xc52d,0xbcec,0xf800,0xbc6a,0xac08,0xabe7,0xb48b,0xc52d,0xc54e,0xbccc,0x9325,0x0,0xabe7,0xb46a,0xbcab,0xb46a,0xb48a,0xb449,0x0,0xf800,0x0,0x8b46,0xf800,0xf800,0x8b46,0x0,0xf800};
Image Caisse_sur_cible = Image(Caisse_sur_cibleData);

const uint16_t CibleData[] = {8,8,1, 1, 0, 0, 0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0x8410,0x8410,0xf800,0xf800,0x8410,0x8410,0xf800,0xf800,0x8410,0x8410,0xf800,0xf800,0x8410,0x8410,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0x8410,0x8410,0xf800,0xf800,0x8410,0x8410,0xf800,0xf800,0x8410,0x8410,0xf800,0xf800,0x8410,0x8410,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800};
Image Cible = Image(CibleData);

const uint16_t MurData[] = {8,8,1, 1, 0, 0, 0x8984,0x4208,0x4208,0xa1c5,0x4208,0x4208,0x8984,0x4208,0x4208,0xc103,0xc103,0xb143,0xb164,0xc103,0xc103,0x91c5,0x9184,0x9963,0x9943,0xa943,0xa984,0xa1a4,0x99a4,0x4208,0x4208,0xc103,0xc103,0xa164,0xa1a4,0xc103,0xc103,0x8a05,0x9206,0x99a5,0x9184,0x8984,0xa184,0xa184,0x89c5,0x4208,0x4208,0xc103,0xc103,0x8984,0xc103,0xc103,0x89a4,0x79e5,0x9226,0x99c5,0x9964,0xa163,0xa963,0xb143,0x9964,0x4208,0x4208,0x4208,0xc103,0xc0e2,0x4208,0xc0e2,0xc102,0x4208};
Image Mur = Image(MurData);

const uint16_t SolData[] = {8,8,1, 1, 0, 0, 0x4208,0x4208,0x4208,0x4208,0x4208,0x4208,0x4208,0x4208,0x4208,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x4208,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x4208,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x4208,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x4208,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x4208,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x4208,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410};
Image Sol = Image(SolData);


Maintenant passons à Graphics.h. Tout d'abord, comprenons l'utilité de la chose. Les fichiers .h contiennent tout ce dont le compilateur doit connaître lorsque l'on compile le code qui utilise les fonctionnalités du module. Le header (fichier d'entête) contient non pas les fonctionnalités du module  déjà présentes dans le code, mais uniquement leurs déclarations, c'est à dire le  « mode d’emploi » de ces fonctionnalités pour le compilateur mais aussi pour les autres développeurs qui peuvent y retrouver très rapidement  les informations importantes. On y met donc les définitions des types publics, les prototypes  (ou signatures) des fonctions publiques du         module. Oui, on ne définit que ce qui est public, tout ce qui doit pouvoir être vu et manipulé de l'extérieur (depuis le programme principal ou depuis un autre module.  

On a déjà fait pas mal de théorie et je suis sûr que quelqu'un pourra être plus précis ou plus clair dans ses explications.          

De plus, il existe un système simple qui protège des inclusions multiples, grâce aux ifndef le .h ne peut être inclus qu’une fois. Pour cela, on le met tout au début de notre .h, ce qui donne la structure suivante:

#ifndef GRAPHICS_H
#define GRAPHICS_H

// On mettra note code ici

#endif


Donc on teste avec ce code si GRAPHICS_H est définit. S'il ne l'est pas on le définit les parties visible du module. Dans notre cas, ça donnera:

#ifndef GRAPHICS_H
#define GRAPHICS_H

extern Image Caisse;
extern Image Caisse_sur_cible;
extern Image Cible;
extern Image Mur;
extern Image Sol;

#endif 


Voilà, pour l'utiliser, il suffira ensuite de rajouter un include du fichier d'entête dans notre programme principal avec la syntaxe suivante: #include "Graphics.h"

Là, on donne accès à nos 5 images, on les expose aux autres modules, mais si l'on réfléchi un peu plus, on se rend compte que l'on ne va les utiliser que dans notre procédure de dessin de notre écran. Il serait plus intelligent de mettre cette procédure dans ce module et de l'exposer elle seulement.

Puisque l'on en parle, commençons à réfléchir à ce que nous voulons dessiner. Nous savons que nous pouvons dessiner 8 sprites de haut mais nous avons décidé de ne pas limiter la taille d'un niveau à 8 lignes. Ce que l'on veut suivre, c'est notre personnage. Il faut donc que l'on affiche les 8 cases qu'il y a autour de lui. Nous avons encore un autre choix à faire: Comme nous avons 8 sprites affichables, et que personnage sera au centre, nous ne tombons pas sur une solution possible en effet, si on met autant de sprites au dessus qu'en dessous, on tombera forcément sur un nombre impair. N spites en haut + Sprite perso + N sprites en bas => 2N Sprites + 1.

Bon on va faire simple et on mettra initialement 4 en haut et 3 sprites en bas et pareil lattéralement: 4 sprites à gauche et 3 sprites à droite.

Autre choix à faire: Soit on déplace toujours le niveau et le personnage reste au centre mais du coup quand le personnage sera au bord de l'écran, on aura toute une partie de l'écran inutile, soit on ne le déplace que quand c'est nécessaire et sinon, c'est le personnage qui bouge à l'écran. Bon ce sera sans doute plus clair après en voyant le code.

On voit donc que l'on doit suivre la position du personnage. 

Pour le moment, on n'a pas encore ajouté le personnage, mais on va juste ajouter les coordonnées de sa position que l'on va initialiser avec la valeur de départ indiqué dans le niveau afin de s'en servir avec à la procédure de traçage du niveau avec a règle que l'on a expliqué avant (Si x > 4 => tracer depuis x - 4 sinon tracer depuis 0 et pareil pour y: si y > 4, tracer depuis y - 4 sinon tracer depuis 0).

Allez, on termine cette partie et on l'affiche enfin ce niveau...

Donc, on repart de notre programme de la dernière fois, on y rajoute les variables X et Y.

On les initialise à -1 pour dire au début que l'on ne connait pas la position du joueur et on fait une petite procédure pour la trouver. On appelle cette procédure dans notre boucle principale pour le moment quand X et Y sont à -1, c'est à dire quand ils n'ont pas encore la position du joueur. Bien entendu à terme dans un prochain tuto, on mettra en place les phases du jeu (démarrage, changement de niveau, etc... et on aura quelque chose de plus propre mais chaque chose en son temps. Patience et longueur de temps font plus que force et que rage...). Après dans la boucle principale, le seul truc qui nous reste à faire, c'est d'appeler la demande d'affichage de la partie du niveau situé autour du joueur.

Bon, je ne disserte pas plus et je vous mets le programme ici, dites moi s'il manque des explications sur quelque chose. Perso, ça me semble asse intuitif.

#include 
#include "Graphics.h"

#define NB_LIGNES_NIVEAUX 11
#define NB_COLONNES_NIVEAUX 19

int X; // Position horizontale du personnage
int Y; // Position verticale du personnage


char niveau[NB_LIGNES_NIVEAUX][NB_COLONNES_NIVEAUX] =
{
  { ' ', ' ', ' ', ' ', '#', '#', '#', '#', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
  { ' ', ' ', ' ', ' ', '#', ' ', ' ', ' ', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
  { ' ', ' ', ' ', ' ', '#', '$', ' ', ' ', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
  { ' ', ' ', '#', '#', '#', ' ', ' ', '$', '#', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
  { ' ', ' ', '#', ' ', ' ', '$', ' ', '$', ' ', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
  { '#', '#', '#', ' ', '#', ' ', '#', '#', ' ', '#', ' ', ' ', ' ', '#', '#', '#', '#', '#', '#', },
  { '#', ' ', ' ', ' ', '#', ' ', '#', '#', ' ', '#', '#', '#', '#', '#', ' ', ' ', '.', '.', '#', },
  { '#', ' ', '$', ' ', ' ', '$', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '.', '.', '#', },
  { '#', '#', '#', '#', '#', ' ', '#', '#', '#', ' ', '#', '@', '#', '#', ' ', ' ', '.', '.', '#', },
  { ' ', ' ', ' ', ' ', '#', ' ', ' ', ' ', ' ', ' ', '#', '#', '#', '#', '#', '#', '#', '#', '#', },
  { ' ', ' ', ' ', ' ', '#', '#', '#', '#', '#', '#', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
};

void trouve_position_perso() {
  for (int ligne = 0; ligne < NB_LIGNES_NIVEAUX; ligne++) {
    for (int colonne = 0; colonne < NB_COLONNES_NIVEAUX; colonne++) {
      if (niveau[ligne][colonne] == '@') {
        X = colonne;
        Y = ligne;
      }
    }
  }
}

void setup() {
  gb.begin();
  X = -1;
  Y = -1;
}

void loop() {
  while (!gb.update());
  gb.display.clear();

  // Initialisation de la position du personnage pour cette étape
  if ((X == -1) && (Y == -1)) {
    trouve_position_perso();
  }

  // On affiche le contenu de chacune des cases
  DessineNiveau(X, Y);
}



Voilà, pour la procédure, je la mets dans Graphics.ino comme nous l'avons décrie plus haut et pour chaque case, elle demandera l'affichage du sprite trouvé, ce qui nous donne le programme suivant:

const uint16_t CaisseData[] = {8, 8, 1, 1, 0, 0, 0x0, 0x0, 0xbc6a, 0xbc6a, 0xbc6a, 0xbc6a, 0x0, 0x0, 0x0, 0xb429, 0xbcab, 0xbccc, 0xbc8a, 0xb407, 0xabe7, 0x0, 0xbc6a, 0xbc8a, 0xc52d, 0xcd6f, 0xabe7, 0xabe7, 0xb449, 0x8b46, 0xbc6a, 0xbc8a, 0xc52d, 0xabe7, 0xabe7, 0xb48b, 0xc4ec, 0x8b46, 0xbc6a, 0xb46a, 0xabe7, 0xabe7, 0xac29, 0xc52d, 0xbcec, 0x9345, 0xbc6a, 0xac08, 0xabe7, 0xb48b, 0xc52d, 0xc54e, 0xbccc, 0x9325, 0x0, 0xabe7, 0xb46a, 0xbcab, 0xb46a, 0xb48a, 0xb449, 0x0, 0x0, 0x0, 0x8b46, 0x8b46, 0x8b46, 0x8b46, 0x0, 0x0};
Image Caisse = Image(CaisseData);

const uint16_t Caisse_sur_cibleData[] = {8, 8, 1, 1, 0, 0, 0xf800, 0x0, 0xbc6a, 0xf800, 0xf800, 0xbc6a, 0x0, 0xf800, 0x0, 0xb429, 0xbcab, 0xbccc, 0xbc8a, 0xb407, 0xabe7, 0x0, 0xbc6a, 0xbc8a, 0xc52d, 0xcd6f, 0xabe7, 0xabe7, 0xb449, 0x8b46, 0xf800, 0xbc8a, 0xc52d, 0xabe7, 0xabe7, 0xb48b, 0xc4ec, 0xf800, 0xf800, 0xb46a, 0xabe7, 0xabe7, 0xac29, 0xc52d, 0xbcec, 0xf800, 0xbc6a, 0xac08, 0xabe7, 0xb48b, 0xc52d, 0xc54e, 0xbccc, 0x9325, 0x0, 0xabe7, 0xb46a, 0xbcab, 0xb46a, 0xb48a, 0xb449, 0x0, 0xf800, 0x0, 0x8b46, 0xf800, 0xf800, 0x8b46, 0x0, 0xf800};
Image Caisse_sur_cible = Image(Caisse_sur_cibleData);

const uint16_t CibleData[] = {8, 8, 1, 1, 0, 0, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0x8410, 0x8410, 0xf800, 0xf800, 0x8410, 0x8410, 0xf800, 0xf800, 0x8410, 0x8410, 0xf800, 0xf800, 0x8410, 0x8410, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0x8410, 0x8410, 0xf800, 0xf800, 0x8410, 0x8410, 0xf800, 0xf800, 0x8410, 0x8410, 0xf800, 0xf800, 0x8410, 0x8410, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800};
Image Cible = Image(CibleData);

const uint16_t MurData[] = {8, 8, 1, 1, 0, 0, 0x8984, 0x4208, 0x4208, 0xa1c5, 0x4208, 0x4208, 0x8984, 0x4208, 0x4208, 0xc103, 0xc103, 0xb143, 0xb164, 0xc103, 0xc103, 0x91c5, 0x9184, 0x9963, 0x9943, 0xa943, 0xa984, 0xa1a4, 0x99a4, 0x4208, 0x4208, 0xc103, 0xc103, 0xa164, 0xa1a4, 0xc103, 0xc103, 0x8a05, 0x9206, 0x99a5, 0x9184, 0x8984, 0xa184, 0xa184, 0x89c5, 0x4208, 0x4208, 0xc103, 0xc103, 0x8984, 0xc103, 0xc103, 0x89a4, 0x79e5, 0x9226, 0x99c5, 0x9964, 0xa163, 0xa963, 0xb143, 0x9964, 0x4208, 0x4208, 0x4208, 0xc103, 0xc0e2, 0x4208, 0xc0e2, 0xc102, 0x4208};
Image Mur = Image(MurData);

const uint16_t SolData[] = {8, 8, 1, 1, 0, 0, 0x4208, 0x4208, 0x4208, 0x4208, 0x4208, 0x4208, 0x4208, 0x4208, 0x4208, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x4208, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x4208, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x4208, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x4208, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x4208, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x4208, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410};
Image Sol = Image(SolData);

const char TailleSprite = 8;

void DessineNiveau(uint8_t Xp,uint8_t  Yp) {
  char X_camera; // Position en à gauche de la zone affichée
  char Y_camera; // Position en haut de la zone affichée
  char X_max;    // Fin de la zone à afficher
  char Y_max;

  // Calcul de la position de la zone à afficher
  if (Xp > 4) {
    X_camera = Xp - 4;
  } else {
    X_camera = 0;
  }

  if (Yp > 4) {
    Y_camera = Yp - 4;
  } else {
    Y_camera = 0;
  }

  if (Xp <= NB_COLONNES_NIVEAUX - 3) {
    X_max = Xp + 3;
  } else {
    X_max = NB_COLONNES_NIVEAUX;
  }

  if (Yp <= NB_LIGNES_NIVEAUX - 3) {
    Y_max = Yp + 3;
  } else {
    Y_max = NB_LIGNES_NIVEAUX;
  }

  for (int ligne = Y_camera; ligne < Y_max; ligne++) {
    for (int colonne = X_camera; colonne < X_max; colonne++) {
      DessineSprite(niveau[ligne][colonne], colonne - X_camera + 1 , ligne - Y_camera + 1);
    }
  }
}

void DessineSprite(char Type, char Xs, char Ys) {
  switch (Type) {
    case ' ' :
      gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Sol);
      break;
    case '#' :
      gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Mur);
      break;
    case '.' :
      gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Cible);
      break;
    case '$' :
      gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Caisse);
      break;
    case '*' :
      gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Caisse_sur_cible);
      break;
    // Pour le moment, on n'affiche pas le personnage (non géré)
    case '@' :
      gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Sol);
      break;
    case '+' :
      gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Cible);
      break;
  } // end Switch
}


Je ne rentre pas plus dans le détail, c'est assez simple mais si vous avez des questions, j'y répondrais avec plaisir.



A VOUS DE JOUER N°1


Bon là vous allez avoir 2 occasions de vous tester. Dans ce premier "A vous de jouer", essayez de mettre à jour le fichier .h comme il convient en rajoutant uniquement ce dont on a besoin.  


SOLUTION

Bon ce n'était pas sorcier, non ?  




Voici le code correspondant à Graphics.h 



#ifndef GRAPHICS_H
#define GRAPHICS_H

extern const char TailleSprite;

extern void DessineNiveau(uint8_t Xp,uint8_t  Yp);

#endif

Élémentaire non. 


A VOUS DE JOUER N°2


Bon, on passe la vitesse supérieure. Avec ce que l'on a vu dans les 2 tutos, votre mission si vous l'acceptez et de dessiner un sprite pour le personnage en 8x8 et de les intégrer dans un module dédié que l'on appellera Joueur puis de rajouter une procédure qui affichera la sprite du joueur.      






Si vous voulez aller plus loin, vous pouvez rajouter la gestion des touches et changer le sprite du joueur en fonction de son sens de déplacement (vers le haut, le bas, la gauche ou la droite)


La suite au prochain numéro ... mais en attendant j'attends vos commentaires avec impatience, vos propositions d'amélioration, corrections et proposition de code pour le "A vous de jouer n°2"


Retrouvez la partie 1 de ce tutoriel ici: Partie 1
ou continuez avec la partie 3: Partie 3

View full creation

Come

NEW 5 years ago

merci pour la 2eme partie ^^

jicehel

NEW 5 years ago

Voilà, la fin de la seconde partie a été ajoutée. A vous de jouer maintenant ...

jicehel

NEW 5 years ago

Ouch, il y a encore eu du code transformé. La fin de l'article merdouille complètement après sauvegarde. Ca arrive de temps en temps sur des grands posts à priori. Mélange dans les balises ? Aurélien, je corrige la fin de l'article ou est-ce que je le laisse un peu comme ça pour voir s'il n'y aurait pas un petit bug quelque part dans la gestion des posts ? (Bien entendu avant de sauvegarder, l'affichage n'était pas comme ça et les codes étaient complets.)

jicehel

NEW 5 years ago

Bon à priori, pas de réponse alors j'ai commencé à réparer l'article en recollant les blocs comme il faut mais je collerais les 2 derniers codes sources ce soir car il ont été abîmés et il manque la fin des deux.

=> Article réparé, codes recollés => C'est bon normalement.

jicehel

NEW 5 years ago

Ca me saoûle, impossible de réparer l'article. A caque fois les programmes se barrent en cacahuètes dans ce tuto... et la fin du programme prend le texte en dessous.  et il manque la fin des programmes... C'est trop bizarre.


En plus j'ai dû modifier quelque chose j'arrive sur un écran noir maintenant quand je téléverse le source compilé alors que ça marchait bien...


Je vais essayer de remettre les codes ici en espérant qu'il passe dans ce post plus court:

Sokoban.ino

#include <Gamebuino-Meta.h>

#include "Graphics.h"

#define NB_LIGNES_NIVEAUX 11
#define NB_COLONNES_NIVEAUX 19

char X; // Position hrizontale du personnage
char Y; // Position verticale du personnage 


char niveau[NB_LIGNES_NIVEAUX][NB_COLONNES_NIVEAUX] = 
{
{ ' ', ' ', ' ', ' ', '#', '#', '#', '#', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
{ ' ', ' ', ' ', ' ', '#', ' ', ' ', ' ', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
{ ' ', ' ', ' ', ' ', '#', '$', ' ', ' ', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
{ ' ', ' ', '#', '#', '#', ' ', ' ', '$', '#', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
{ ' ', ' ', '#', ' ', ' ', '$', ' ', '$', ' ', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
{ '#', '#', '#', ' ', '#', ' ', '#', '#', ' ', '#', ' ', ' ', ' ', '#', '#', '#', '#', '#', '#', },
{ '#', ' ', ' ', ' ', '#', ' ', '#', '#', ' ', '#', '#', '#', '#', '#', ' ', ' ', '.', '.', '#', },
{ '#', ' ', '$', ' ', ' ', '$', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '.', '.', '#', },
{ '#', '#', '#', '#', '#', ' ', '#', '#', '#', ' ', '#', '@', '#', '#', ' ', ' ', '.', '.', '#', },
{ ' ', ' ', ' ', ' ', '#', ' ', ' ', ' ', ' ', ' ', '#', '#', '#', '#', '#', '#', '#', '#', '#', },
{ ' ', ' ', ' ', ' ', '#', '#', '#', '#', '#', '#', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', }
};

void setup() {
  gb.begin();
  X = -1;
  Y = -1;
}

void trouve_position_perso() {
      for (int ligne=0;ligne<NB_LIGNES_NIVEAUX;ligne++) {
       for (int colonne=0;colonne<NB_COLONNES_NIVEAUX;colonne++) {
         if (niveau[ligne][colonne]=='@'){
             X = colonne;
             Y = ligne;
         }
       }
     } 
}

void loop() {
    while(!gb.update());
    gb.display.clear();

    // Initialisation de la position du personnage pour cette étape
    if (X==-1 && Y==-1) trouve_position_perso();

    // On affiche le contenu de chacune des cases
    DessineNiveau(X,Y);  
}


Garphics.h

#ifndef GRAPHICS_H
#define GRAPHICS_H

extern const char TailleSprite;

extern void DessineNiveau(char Xp,char Yp);

#endif


Graphics.ino

const uint16_t CaisseData[] = {8,8,1, 1, 0, 0, 0x0,0x0,0xbc6a,0xbc6a,0xbc6a,0xbc6a,0x0,0x0,0x0,0xb429,0xbcab,0xbccc,0xbc8a,0xb407,0xabe7,0x0,0xbc6a,0xbc8a,0xc52d,0xcd6f,0xabe7,0xabe7,0xb449,0x8b46,0xbc6a,0xbc8a,0xc52d,0xabe7,0xabe7,0xb48b,0xc4ec,0x8b46,0xbc6a,0xb46a,0xabe7,0xabe7,0xac29,0xc52d,0xbcec,0x9345,0xbc6a,0xac08,0xabe7,0xb48b,0xc52d,0xc54e,0xbccc,0x9325,0x0,0xabe7,0xb46a,0xbcab,0xb46a,0xb48a,0xb449,0x0,0x0,0x0,0x8b46,0x8b46,0x8b46,0x8b46,0x0,0x0};
Image Caisse = Image(CaisseData);

const uint16_t Caisse_sur_cibleData[] = {8,8,1, 1, 0, 0, 0xf800,0x0,0xbc6a,0xf800,0xf800,0xbc6a,0x0,0xf800,0x0,0xb429,0xbcab,0xbccc,0xbc8a,0xb407,0xabe7,0x0,0xbc6a,0xbc8a,0xc52d,0xcd6f,0xabe7,0xabe7,0xb449,0x8b46,0xf800,0xbc8a,0xc52d,0xabe7,0xabe7,0xb48b,0xc4ec,0xf800,0xf800,0xb46a,0xabe7,0xabe7,0xac29,0xc52d,0xbcec,0xf800,0xbc6a,0xac08,0xabe7,0xb48b,0xc52d,0xc54e,0xbccc,0x9325,0x0,0xabe7,0xb46a,0xbcab,0xb46a,0xb48a,0xb449,0x0,0xf800,0x0,0x8b46,0xf800,0xf800,0x8b46,0x0,0xf800};
Image Caisse_sur_cible = Image(Caisse_sur_cibleData);

const uint16_t CibleData[] = {8,8,1, 1, 0, 0, 0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0x8410,0x8410,0xf800,0xf800,0x8410,0x8410,0xf800,0xf800,0x8410,0x8410,0xf800,0xf800,0x8410,0x8410,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0x8410,0x8410,0xf800,0xf800,0x8410,0x8410,0xf800,0xf800,0x8410,0x8410,0xf800,0xf800,0x8410,0x8410,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800,0xf800};
Image Cible = Image(CibleData);

const uint16_t MurData[] = {8,8,1, 1, 0, 0, 0x8984,0x4208,0x4208,0xa1c5,0x4208,0x4208,0x8984,0x4208,0x4208,0xc103,0xc103,0xb143,0xb164,0xc103,0xc103,0x91c5,0x9184,0x9963,0x9943,0xa943,0xa984,0xa1a4,0x99a4,0x4208,0x4208,0xc103,0xc103,0xa164,0xa1a4,0xc103,0xc103,0x8a05,0x9206,0x99a5,0x9184,0x8984,0xa184,0xa184,0x89c5,0x4208,0x4208,0xc103,0xc103,0x8984,0xc103,0xc103,0x89a4,0x79e5,0x9226,0x99c5,0x9964,0xa163,0xa963,0xb143,0x9964,0x4208,0x4208,0x4208,0xc103,0xc0e2,0x4208,0xc0e2,0xc102,0x4208};
Image Mur = Image(MurData);

const uint16_t SolData[] = {8,8,1, 1, 0, 0, 0x4208,0x4208,0x4208,0x4208,0x4208,0x4208,0x4208,0x4208,0x4208,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x4208,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x4208,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x4208,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x4208,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x4208,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x4208,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410};
Image Sol = Image(SolData);

const char TailleSprite = 8;

void DessineNiveau(char Xp,char Yp){
  char X_camera; // Position en à gauche de la zone affichée
  char Y_camera; // Position en haut de la zone affichée
  char X_max;    // Fin de la zone à afficher
  char Y_max;

// Calcul de la position de la zone à afficher
if (Xp > 4) {
    X_camera = Xp-4;
  } else { 
    X_camera = 0;
  }
  if (Yp > 4) {
    Y_camera = Yp-4;
  } else { 
    Y_camera = 0;
  } 

  if (Xp <= NB_COLONNES_NIVEAUX - 3) {
    X_max = Xp + 3;
  } else {
    X_max = NB_COLONNES_NIVEAUX;
  }

  if (Yp <= NB_LIGNES_NIVEAUX - 3) {
    Y_max = Yp + 3;
  } else {
    Y_max = NB_LIGNES_NIVEAUX;
  }

  for (int ligne=Y_camera;ligne<Y_max;ligne++) {
    for (int colonne=X_camera;colonne<X_max;colonne++) {
      DessineSprite(niveau[ligne][colonne],colonne - X_camera + 1 ,ligne - Y_camera +1);
    }
  } 
}

void DessineSprite(char Type,char Xs,char Ys){
        switch (Type) {
           case ' ' : 
              gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Sol);
           break;
           case '#' : 
              gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Mur);
            break;
            case '.' : 
              gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Cible);
            break; 
            case '$' : 
              gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Caisse);
            break;
            case '*' : 
              gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Caisse_sur_cible);
            break;
            // Pour le moment, on n'affiche pas le personnage (non géré)
           case '@' : 
              gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Sol);
           break;
           case '+' : 
              gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Cible);
            break;  
       } // end Switch
}


jicehel

NEW 5 years ago

Je ne comprends pas, en essayant de recompiler ces codes, la compilation fonctionne sans erreur mais quand je lance sur ma Meta ou sur l'émulateur, j'ai SD init ... failed

Pour être sûr que ça ne venait pas de mon installation de l'IDE Arduino, j'ai installé la version 'mobile' (le ZIP du tuto "Comment installer votre META") et j'ai recompilé... pareil. Vous ça marche en compilant ces codes ? Ça marchait chez moi avant mais je ne vois pas ce qui a changé. Si quelqu'un a une idée pour le SD INIT failed au démarrage, je suis preneur.

jicehel

NEW 5 years ago

Personne ne voit ? Petit test pour avoir si le problème vient de moi ou du programme. Si vous collez ces 3 codes et que vous les téléversez dans votre META vous avez aussi l'init de la SD qui affiche failed ? si ça ne le fait que pour moi c'est que ce serait lié à quelque chose dans ma config quand je compile...


Aurélien Rodot

5 years ago

Pour valider que ça ne vient pas du programme tu peux essayer avec un autre programme, comme un des exemples par défaut de Gamebuino.

Si tu as toujours le problème, c'est probablement un soucis avec ta carte SD.

Aurélien Rodot

NEW 5 years ago

jicehel jicehel

Pour valider que ça ne vient pas du programme tu peux essayer avec un autre programme, comme un des exemples par défaut de Gamebuino.

Si tu as toujours le problème, c'est probablement un soucis avec ta carte SD.

jicehel

NEW 5 years ago

Non, ça ne vient pas de la carte SD sinon ça ne ferait pas la même sur l'émulateur mais ça vient bien de mon programme. J'ai compilé le Pong et ça a bien marché. En recopiant le programme j'avais changé le type de X et Y en char mais à priori le char est forcément positif. D'où le problème puisque je l'initialise à -1... C'est stupide comme erreur et j'ai bloqué dessus comme le programme marchait avant et que je ne m'étais pas rendu compte que javais changé ces  types de variables.


Problème réglé, désolé et en plus j'ai pu corriger le problème de mise en page du tuto. J'ai profit des nouvelles fonctionnalités des créations (zip des codes sources et émulateur pour voir le résultat même si pour le moment, il a peut d'intérêt à ce stade du jeu, on peu vérifier que l'on affiche bien la portion du jeu autour du joueur directement depuis l'émulateur). En fait le message SD init... failed s'affiche dans l'émulateur même quand ça marche... C'est ce qui m'avait mis sur une mauvaise piste.

Programme corrigé :

#include <Gamebuino-Meta.h>
#include "Graphics.h"

#define NB_LIGNES_NIVEAUX 11
#define NB_COLONNES_NIVEAUX 19

int X; // Position horizontale du personnage
int Y; // Position verticale du personnage


char niveau[NB_LIGNES_NIVEAUX][NB_COLONNES_NIVEAUX] =
{
  { ' ', ' ', ' ', ' ', '#', '#', '#', '#', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
  { ' ', ' ', ' ', ' ', '#', ' ', ' ', ' ', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
  { ' ', ' ', ' ', ' ', '#', '$', ' ', ' ', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
  { ' ', ' ', '#', '#', '#', ' ', ' ', '$', '#', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
  { ' ', ' ', '#', ' ', ' ', '$', ' ', '$', ' ', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
  { '#', '#', '#', ' ', '#', ' ', '#', '#', ' ', '#', ' ', ' ', ' ', '#', '#', '#', '#', '#', '#', },
  { '#', ' ', ' ', ' ', '#', ' ', '#', '#', ' ', '#', '#', '#', '#', '#', ' ', ' ', '.', '.', '#', },
  { '#', ' ', '$', ' ', ' ', '$', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '.', '.', '#', },
  { '#', '#', '#', '#', '#', ' ', '#', '#', '#', ' ', '#', '@', '#', '#', ' ', ' ', '.', '.', '#', },
  { ' ', ' ', ' ', ' ', '#', ' ', ' ', ' ', ' ', ' ', '#', '#', '#', '#', '#', '#', '#', '#', '#', },
  { ' ', ' ', ' ', ' ', '#', '#', '#', '#', '#', '#', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
};

void trouve_position_perso() {
  for (int ligne = 0; ligne < NB_LIGNES_NIVEAUX; ligne++) {
    for (int colonne = 0; colonne < NB_COLONNES_NIVEAUX; colonne++) {
      if (niveau[ligne][colonne] == '@') {
        X = colonne;
        Y = ligne;
      }
    }
  }
}

void setup() {
  gb.begin();
  X = -1;
  Y = -1;
}

void loop() {
  while (!gb.update());
  gb.display.clear();

  // Initialisation de la position du personnage pour cette étape
  if ((X == -1) && (Y == -1)) {
    trouve_position_perso();
  }

  // On affiche le contenu de chacune des cases
  DessineNiveau(X, Y);
}



Juice_Lizard

NEW 5 years ago

[ENGLISH] To understand how to use different files in your code, the .ino, .cpp and .h and #include, you may read this article: Headers and Includes: Why and How

Aurélien Rodot

5 years ago

In Arduino, all the .ino are concatenated, it's just like if it was one huge file. The .hpp and .cpp are handled as described in your link.

Aurélien Rodot

NEW 5 years ago

Juice_Lizard Juice_Lizard

In Arduino, all the .ino are concatenated, it's just like if it was one huge file. The .hpp and .cpp are handled as described in your link.