SOKOBAN (PARTIE 3) : Créons la vie !

Créations

jicehel

il y a 5 ans

Durée: 30 minutes (à la louche)

Niveau: 2 (+3 en charisme et +3 en intelligence ;) )

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 2) 


Dessiner un niveau c'est bien mais pouvoir s'y déplacer s'est plus interessant

Nous avons vu dans le chapitre précédent comment dessine un niveau. Maintenant nous allons rajouter un joueur (notre gardien de stock qui devra ranger les caisses) et gérer l'appuie sur les touches haut, droite, gauche et bas.

Pour le moment nous ne pourrons pas pousser les caisses, juste nous déplacer. Nous pourrions déplacer un sprite unique: une boule, un petit bonhomme mais non, nous on va faire un peu plus compliqué, par ce que bon, on n'est plus des débutants quand même après avoir lus et mis en pratique tous ces tutos.

Nous allons également animer le personnage. Nous pourrions le déplacer en une seule fois en le passant d'une case à l'autre mais bon ce ne seait pas drôle et beaucoup moins joli.

Pour cela nous allons utiliser 8 sprites:

Voilà, rien d'extraordinaire, c'est un petit bonhomme e 8 pixels de haut ... mais si vous avez des idées de génie ou des variantes, vous pouvez les proposer (des hérissons avec des nez rouges, des chats, etc... mais bon sur 8 pixels)

Je ne reviens pas sur le procédé pour les convertir en data pour notre programme, nous l'avons déjà vu dans le dernier tuto.

On va donc rajouter dans graphics.ino

/* ////////////////////////////////////////////////////////////////
// Ajout des sprites du personnage
//////////////////////////////////////////////////////////////// */

const uint16_t Personnage_marche_bas_anim1x8Data[] = {8,8,1, 1, 0xf80d,0, 0xf80d,0xf80d,0xa8a3,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0x4439,0x210,0x210,0x210,0xf80d,0xf80d,0xf80d,0x210,0x4439,0x4439,0x4439,0x210,0x210,0xf80d,0xacd0,0xffff,0xfd42,0xfd42,0xfd42,0xa8a3,0xffff,0xacd0,0xffff,0xacd0,0xfd42,0xf720,0xfd42,0xfd42,0xacd0,0xffff,0xfeb2,0xf80d,0xfd42,0xf720,0xfd42,0xfd42,0xf80d,0xfeb2,0xf80d,0xf80d,0xf80d,0xfeb2,0xcc68,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d};
Image Marche_bas1 = Image(Personnage_marche_bas_anim1x8Data);

const uint16_t Personnage_marche_bas_anim2x8Data[] = {8,8,1, 1, 0xf80d, 0, 0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xd8e4,0xf80d,0xf80d,0xf80d,0xf80d,0x210,0x210,0x210,0x4439,0xf80d,0xf80d,0xf80d,0x210,0x210,0x4439,0x4439,0x4439,0x210,0xf80d,0xacd0,0xffff,0xfd42,0xfd42,0xfd42,0xd8e4,0xffff,0xacd0,0xffff,0xacd0,0xfd42,0xf720,0xfd42,0xfd42,0xacd0,0xffff,0xfeb2,0xf80d,0xfd42,0xf720,0xfd42,0xfd42,0xf80d,0xfeb2,0xf80d,0xf80d,0xf80d,0xfeb2,0xcc68,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d};
Image Marche_bas2 = Image(Personnage_marche_bas_anim2x8Data);

const uint16_t Personnage_marche_droite_anim1x8Data[] = {8,8,1, 1, 0xf80d, 0, 0xf80d,0xf80d,0xf80d,0xacd0,0xffff,0xfeb2,0xf80d,0xf80d,0xf80d,0xf80d,0x210,0xffff,0xacd0,0xf80d,0xf80d,0xf80d,0xf80d,0x210,0x210,0xfd42,0xf720,0xf720,0xf80d,0xf80d,0xf80d,0x210,0x4439,0xf720,0xfd42,0xfd42,0xfeb2,0xf80d,0xf80d,0x210,0x4439,0xfd42,0xfd42,0xfd42,0xcc68,0xf80d,0xd8e4,0x4439,0x4439,0xd8e4,0xd8e4,0xd8e4,0xf80d,0xf80d,0xf80d,0xf80d,0x210,0xffff,0xacd0,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xacd0,0xffff,0xfeb2,0xf80d,0xf80d};
Image Marche_droite1 = Image(Personnage_marche_droite_anim1x8Data);

const uint16_t Personnage_marche_droite_anim2x8Data[] = {8,8,1, 1, 0xf80d, 0, 0xf80d,0xf80d,0xf80d,0xacd0,0xffff,0xfeb2,0xf80d,0xf80d,0xf80d,0xf80d,0x210,0xffff,0xacd0,0xf80d,0xf80d,0xf80d,0xd8e4,0x4439,0x4439,0xfd42,0xf720,0xf720,0xf80d,0xf80d,0xf80d,0x210,0x4439,0xf720,0xfd42,0xfd42,0xfeb2,0xf80d,0xf80d,0x210,0x4439,0xfd42,0xfd42,0xfd42,0xcc68,0xf80d,0xf80d,0x210,0x210,0xd8e4,0xd8e4,0xd8e4,0xf80d,0xf80d,0xf80d,0xf80d,0x210,0xffff,0xacd0,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xacd0,0xffff,0xfeb2,0xf80d,0xf80d};
Image Marche_droite2 = Image(Personnage_marche_droite_anim2x8Data);

const uint16_t Personnage_marche_gauche_anim1x8Data[] = {8,8,1, 1, 0xf80d, 0, 0xf80d,0xf80d,0xfeb2,0xffff,0xacd0,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xacd0,0xffff,0x210,0xf80d,0xf80d,0xf80d,0xf80d,0xf720,0xf720,0xfd42,0x210,0x210,0xf80d,0xf80d,0xfeb2,0xfd42,0xfd42,0xf720,0x4439,0x210,0xf80d,0xf80d,0xcc68,0xfd42,0xfd42,0xfd42,0x4439,0x210,0xf80d,0xf80d,0xf80d,0xd8e4,0xd8e4,0xd8e4,0x4439,0x4439,0xd8e4,0xf80d,0xf80d,0xf80d,0xacd0,0xffff,0x210,0xf80d,0xf80d,0xf80d,0xf80d,0xfeb2,0xffff,0xacd0,0xf80d,0xf80d,0xf80d};
Image Marche_gauche1 = Image(Personnage_marche_gauche_anim1x8Data);

const uint16_t Personnage_marche_gauche_anim2x8Data[] = {8,8,1, 1, 0xf80d, 0, 0xf80d,0xf80d,0xfeb2,0xffff,0xacd0,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xacd0,0xffff,0x210,0xf80d,0xf80d,0xf80d,0xf80d,0xf720,0xf720,0xfd42,0x4439,0x4439,0xd8e4,0xf80d,0xfeb2,0xfd42,0xfd42,0xf720,0x4439,0x210,0xf80d,0xf80d,0xcc68,0xfd42,0xfd42,0xfd42,0x4439,0x210,0xf80d,0xf80d,0xf80d,0xd8e4,0xd8e4,0xd8e4,0x210,0x210,0xf80d,0xf80d,0xf80d,0xf80d,0xacd0,0xffff,0x210,0xf80d,0xf80d,0xf80d,0xf80d,0xfeb2,0xffff,0xacd0,0xf80d,0xf80d,0xf80d};
Image Marche_gauche2 = Image(Personnage_marche_gauche_anim2x8Data);

const uint16_t Personnage_marche_haut_anim1x8Data[] = {8,8,1, 1, 0xf80d, 0, 0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xfeb2,0xcc68,0xf80d,0xf80d,0xf80d,0xfeb2,0xf80d,0xfd42,0xf720,0xfd42,0xfd42,0xf80d,0xfeb2,0xffff,0xacd0,0xfd42,0xf720,0xfd42,0xfd42,0xacd0,0xffff,0xacd0,0xffff,0xfd42,0xfd42,0xfd42,0xa8a3,0xffff,0xacd0,0xf80d,0x210,0x4439,0x4439,0x4439,0x210,0x210,0xf80d,0xf80d,0xf80d,0x4439,0x210,0x210,0x210,0xf80d,0xf80d,0xf80d,0xf80d,0xa8a3,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d};
Image Marche_haut1 = Image(Personnage_marche_haut_anim1x8Data);

const uint16_t Personnage_marche_haut_anim2x8Data[] = {8,8,1, 1, 0xf80d, 0, 0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xfeb2,0xcc68,0xf80d,0xf80d,0xf80d,0xfeb2,0xf80d,0xfd42,0xf720,0xfd42,0xfd42,0xf80d,0xfeb2,0xffff,0xacd0,0xfd42,0xf720,0xfd42,0xfd42,0xacd0,0xffff,0xacd0,0xffff,0xfd42,0xfd42,0xfd42,0xa8a3,0xffff,0xacd0,0xf80d,0x210,0x210,0x4439,0x4439,0x4439,0x210,0xf80d,0xf80d,0xf80d,0x210,0x210,0x210,0x4439,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xf80d,0xa8a3,0xf80d,0xf80d};
Image Marche_haut2 = Image(Personnage_marche_haut_anim2x8Data);


Notre programme était simple mais il va commencer à se compliquer un peu, alors nous allons sortir toute la partie concernant le joueur dans un onglet spécifique que nous allons appeler: player.ino Oui je sais ce n'est pas très original mais on s'en moque, au contraire car le but c'est de voir au premier coup d’œil que ce sera dans cette partie que nous trouverons tout ce qui se réfère au joueur (l'initialisation de sa position de départ, la gestion de ses mouvements, etc...). Pendant que l'on y est, je sépare aussi les informations des niveaux dans le fichier Level.ino

De même, on va changer le X et Y que nous utilisions pour le personnage pour les mettre dans la structure correspondant au personnage.

On va y ajouter la composante: En_mouvement qui nous indiquera quand le personnage est en train de bouger. Dans cette variable, on stockera la direction du mouvement.

On va en ajouter une autre pour garder la direction dans laquelle regarde le personnage (quand il a fini de se déplacer ou si l'on tourne dans une direction occupée)

On va aussi rajouter une variable Anime dans cette structure pour pouvoir gérer l'animation du personnage (Cette animation pourrait être également gérée automatiquement comme Sorunome l'explique ici: Tutoriel sur les images)


Voilà. Après c'est simple: dans la procédure MAJ_Joueur():

On teste si le joueur est en mouvement

    S'il l'est, on avance dans la direction indiquée dans En_mouvement.

          On testera alors si le personnage est arrivé grâce à la variable Anime s'il l'est:

                  On vide l'ancienne case

                  On met à jour la nouvelle position du joueur

                  On indique que l'on a fini en mettant STOP dans En_mouvement 

   Si le personnage n'est pas en mouvement, on teste si une touche a été enfoncée et on stocke la direction demandée pour tourner le personnage en conséquence.

   Si oui, on regarde si le personnage peut aller dans cette direction (pour le moment, on n'autorise que les déplacement dans les cases vides identifiées comme ' ' dans notre tableau niveau.

  Si le mouvement est possible, on met la direction dans En_mouvement.


Ca donne ça dans Player.ino:

// Déclaration de notre joueur
Type_Joueur Joueur;


// Procédure de recherche de la position initiale du joueur (le @ dans le tableau)
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] == '@') {
        Joueur.X = colonne;
        Joueur.Y = ligne;
      }
    }
  }
}


void MAJ_Joueur() {
  if (Joueur.En_mouvement != STOP) {   // Si le joueur est en mouvement
    Joueur.Anime += 2;                 // On avance dans l'animation du mouvement
    if (Joueur.Anime > (TailleSprite * VITESSE_ANIME)) {  // VITESSE_ANIME permet de régler la vitesse du déplacement en restant immobile dans une boucle

      // On retire dans le tableau du niveau le personnage de la position où il se trouvait
      switch (niveau[Joueur.Y][Joueur.X]) {
        case '@':
          niveau[Joueur.Y][Joueur.X] = ' ';
          break;
        case '+':
          niveau[Joueur.Y][Joueur.X] = '.';
          break;
      }

      // On calcule la nouvelle position du joueur
      switch (Joueur.En_mouvement) {
        case BAS :
          if (Joueur.Y < NB_LIGNES_NIVEAUX) Joueur.Y++;
          break;
        case HAUT :
          if (Joueur.Y > 1) Joueur.Y--;
          break;
        case DROITE :
          if (Joueur.X < NB_COLONNES_NIVEAUX)Joueur.X++;
          break;
        case GAUCHE :
          if (Joueur.X > 1) Joueur.X--;
          break;
      }

      // On enregistre cette nouvelle poition du joueur dans le tableau du niveau
      switch (niveau[Joueur.Y][Joueur.X]) {
        case ' ':
          niveau[Joueur.Y][Joueur.X]  = '@';
          break;
        case '.':
          niveau[Joueur.Y ][Joueur.X] = '+';
          break;
      }


      // On met à jour les variables indiquant que le mouvement est terminé
      Joueur.En_mouvement = STOP; 
      Joueur.Anime = 0;

    }
  } else {
    if (gb.buttons.pressed(BUTTON_UP)) {
      Joueur.Direction = HAUT;
      if (niveau[Joueur.Y - 1][Joueur.X] == ' ' || niveau[Joueur.Y - 1][Joueur.X] == '.') Joueur.En_mouvement = HAUT;
    }
    if (gb.buttons.pressed(BUTTON_DOWN)) {
      Joueur.Direction = BAS;
      if (niveau[Joueur.Y + 1][Joueur.X] == ' ' || niveau[Joueur.Y + 1][Joueur.X] == '.') Joueur.En_mouvement = BAS;
    }
    if (gb.buttons.pressed(BUTTON_LEFT)) {
      Joueur.Direction = GAUCHE;
      if (niveau[Joueur.Y][Joueur.X - 1] == ' ' || niveau[Joueur.Y][Joueur.X - 1] == '.') Joueur.En_mouvement = GAUCHE;
    }
    if (gb.buttons.pressed(BUTTON_RIGHT)) {
      Joueur.Direction = DROITE;
      if (niveau[Joueur.Y][Joueur.X + 1] == ' ' || niveau[Joueur.Y][Joueur.X + 1] == '.') Joueur.En_mouvement = DROITE;
    }
  }
}



Pour l'affichage du personnage, de la façon dont j'ai procédé, le personnage  empiète sur la case voisine à un moment donné, pour éviter tout problème d'affichage, on va d'abord afficher complètement le niveau puis afficher le personnage.

Note: j'ai modifié un peu le système de caméra pour le simplifier (enfin j'espère ;) ), modifié le sprite de la caisse par ce que je n'aimait pas son rendu et supprimé des sprites inutiles (caisse sur arrivée n'est pas un sprite particulier mais c'est le sprite de l'arrivée d'une caisse + le sprite de la caisse... Comme quoi quand on réfléchit à son jeu, on peut faire des erreurs de logique ...)

Je mets le code ci dessous (Graphics.ino) mais si vous avez de meilleurs idées, ce tutoriel peut toujours être amélioré avec vos idées et vos conseils.

Type_Camera Camera;

/* ////////////////////////////////////////////////////////////////
  // Sprites du décor
  //////////////////////////////////////////////////////////////// */

const uint16_t CaisseData[] = {8, 8, 1, 1, 0xca30, 0, 0xca30, 0xca30, 0xca30, 0xf5ce, 0xed6c, 0xca30, 0xca30, 0xca30, 0xca30, 0xca30, 0xf5ce, 0xf5ce, 0xed6c, 0xe54c, 0xca30, 0xca30, 0xc363, 0xd447, 0xe50a, 0xf5ac, 0xfded, 0xf58b, 0xcc47, 0xa283, 0xbb22, 0xaaa2, 0xf5ac, 0xf5ac, 0xf5ac, 0xaac2, 0xa241, 0x91e0, 0xbb02, 0xaaa2, 0xa241, 0xf5ac, 0xbb03, 0xaac2, 0xa241, 0x91c0, 0xbb02, 0xaaa2, 0xa241, 0x91c0, 0xbb03, 0xaac2, 0xa241, 0x91c0, 0xca30, 0xaaa2, 0xa241, 0x91e0, 0xbb23, 0xaaa2, 0xa241, 0xca30, 0xca30, 0xca30, 0xa241, 0x9a63, 0xb326, 0xca30, 0xca30, 0xca30};
Image Caisse = Image(CaisseData);

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);


/* ////////////////////////////////////////////////////////////////
  // Ajout des sprites du personnage
  //////////////////////////////////////////////////////////////// */

const uint16_t Personnage_marche_bas_anim1x8Data[] = {8, 8, 1, 1, 0xf80d, 0, 0xf80d, 0xf80d, 0xa8a3, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0x4439, 0x210, 0x210, 0x210, 0xf80d, 0xf80d, 0xf80d, 0x210, 0x4439, 0x4439, 0x4439, 0x210, 0x210, 0xf80d, 0xacd0, 0xffff, 0xfd42, 0xfd42, 0xfd42, 0xa8a3, 0xffff, 0xacd0, 0xffff, 0xacd0, 0xfd42, 0xf720, 0xfd42, 0xfd42, 0xacd0, 0xffff, 0xfeb2, 0xf80d, 0xfd42, 0xf720, 0xfd42, 0xfd42, 0xf80d, 0xfeb2, 0xf80d, 0xf80d, 0xf80d, 0xfeb2, 0xcc68, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d};
Image Marche_bas1 = Image(Personnage_marche_bas_anim1x8Data);

const uint16_t Personnage_marche_bas_anim2x8Data[] = {8, 8, 1, 1, 0xf80d, 0, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xd8e4, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0x210, 0x210, 0x210, 0x4439, 0xf80d, 0xf80d, 0xf80d, 0x210, 0x210, 0x4439, 0x4439, 0x4439, 0x210, 0xf80d, 0xacd0, 0xffff, 0xfd42, 0xfd42, 0xfd42, 0xd8e4, 0xffff, 0xacd0, 0xffff, 0xacd0, 0xfd42, 0xf720, 0xfd42, 0xfd42, 0xacd0, 0xffff, 0xfeb2, 0xf80d, 0xfd42, 0xf720, 0xfd42, 0xfd42, 0xf80d, 0xfeb2, 0xf80d, 0xf80d, 0xf80d, 0xfeb2, 0xcc68, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d};
Image Marche_bas2 = Image(Personnage_marche_bas_anim2x8Data);

const uint16_t Personnage_marche_droite_anim1x8Data[] = {8, 8, 1, 1, 0xf80d, 0, 0xf80d, 0xf80d, 0xf80d, 0xacd0, 0xffff, 0xfeb2, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0x210, 0xffff, 0xacd0, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0x210, 0x210, 0xfd42, 0xf720, 0xf720, 0xf80d, 0xf80d, 0xf80d, 0x210, 0x4439, 0xf720, 0xfd42, 0xfd42, 0xfeb2, 0xf80d, 0xf80d, 0x210, 0x4439, 0xfd42, 0xfd42, 0xfd42, 0xcc68, 0xf80d, 0xd8e4, 0x4439, 0x4439, 0xd8e4, 0xd8e4, 0xd8e4, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0x210, 0xffff, 0xacd0, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xacd0, 0xffff, 0xfeb2, 0xf80d, 0xf80d};
Image Marche_droite1 = Image(Personnage_marche_droite_anim1x8Data);

const uint16_t Personnage_marche_droite_anim2x8Data[] = {8, 8, 1, 1, 0xf80d, 0, 0xf80d, 0xf80d, 0xf80d, 0xacd0, 0xffff, 0xfeb2, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0x210, 0xffff, 0xacd0, 0xf80d, 0xf80d, 0xf80d, 0xd8e4, 0x4439, 0x4439, 0xfd42, 0xf720, 0xf720, 0xf80d, 0xf80d, 0xf80d, 0x210, 0x4439, 0xf720, 0xfd42, 0xfd42, 0xfeb2, 0xf80d, 0xf80d, 0x210, 0x4439, 0xfd42, 0xfd42, 0xfd42, 0xcc68, 0xf80d, 0xf80d, 0x210, 0x210, 0xd8e4, 0xd8e4, 0xd8e4, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0x210, 0xffff, 0xacd0, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xacd0, 0xffff, 0xfeb2, 0xf80d, 0xf80d};
Image Marche_droite2 = Image(Personnage_marche_droite_anim2x8Data);

const uint16_t Personnage_marche_gauche_anim1x8Data[] = {8, 8, 1, 1, 0xf80d, 0, 0xf80d, 0xf80d, 0xfeb2, 0xffff, 0xacd0, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xacd0, 0xffff, 0x210, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf720, 0xf720, 0xfd42, 0x210, 0x210, 0xf80d, 0xf80d, 0xfeb2, 0xfd42, 0xfd42, 0xf720, 0x4439, 0x210, 0xf80d, 0xf80d, 0xcc68, 0xfd42, 0xfd42, 0xfd42, 0x4439, 0x210, 0xf80d, 0xf80d, 0xf80d, 0xd8e4, 0xd8e4, 0xd8e4, 0x4439, 0x4439, 0xd8e4, 0xf80d, 0xf80d, 0xf80d, 0xacd0, 0xffff, 0x210, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xfeb2, 0xffff, 0xacd0, 0xf80d, 0xf80d, 0xf80d};
Image Marche_gauche1 = Image(Personnage_marche_gauche_anim1x8Data);

const uint16_t Personnage_marche_gauche_anim2x8Data[] = {8, 8, 1, 1, 0xf80d, 0, 0xf80d, 0xf80d, 0xfeb2, 0xffff, 0xacd0, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xacd0, 0xffff, 0x210, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf720, 0xf720, 0xfd42, 0x4439, 0x4439, 0xd8e4, 0xf80d, 0xfeb2, 0xfd42, 0xfd42, 0xf720, 0x4439, 0x210, 0xf80d, 0xf80d, 0xcc68, 0xfd42, 0xfd42, 0xfd42, 0x4439, 0x210, 0xf80d, 0xf80d, 0xf80d, 0xd8e4, 0xd8e4, 0xd8e4, 0x210, 0x210, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xacd0, 0xffff, 0x210, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xfeb2, 0xffff, 0xacd0, 0xf80d, 0xf80d, 0xf80d};
Image Marche_gauche2 = Image(Personnage_marche_gauche_anim2x8Data);

const uint16_t Personnage_marche_haut_anim1x8Data[] = {8, 8, 1, 1, 0xf80d, 0, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xfeb2, 0xcc68, 0xf80d, 0xf80d, 0xf80d, 0xfeb2, 0xf80d, 0xfd42, 0xf720, 0xfd42, 0xfd42, 0xf80d, 0xfeb2, 0xffff, 0xacd0, 0xfd42, 0xf720, 0xfd42, 0xfd42, 0xacd0, 0xffff, 0xacd0, 0xffff, 0xfd42, 0xfd42, 0xfd42, 0xa8a3, 0xffff, 0xacd0, 0xf80d, 0x210, 0x4439, 0x4439, 0x4439, 0x210, 0x210, 0xf80d, 0xf80d, 0xf80d, 0x4439, 0x210, 0x210, 0x210, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xa8a3, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d};
Image Marche_haut1 = Image(Personnage_marche_haut_anim1x8Data);

const uint16_t Personnage_marche_haut_anim2x8Data[] = {8, 8, 1, 1, 0xf80d, 0, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xfeb2, 0xcc68, 0xf80d, 0xf80d, 0xf80d, 0xfeb2, 0xf80d, 0xfd42, 0xf720, 0xfd42, 0xfd42, 0xf80d, 0xfeb2, 0xffff, 0xacd0, 0xfd42, 0xf720, 0xfd42, 0xfd42, 0xacd0, 0xffff, 0xacd0, 0xffff, 0xfd42, 0xfd42, 0xfd42, 0xa8a3, 0xffff, 0xacd0, 0xf80d, 0x210, 0x210, 0x4439, 0x4439, 0x4439, 0x210, 0xf80d, 0xf80d, 0xf80d, 0x210, 0x210, 0x210, 0x4439, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xf80d, 0xa8a3, 0xf80d, 0xf80d};
Image Marche_haut2 = Image(Personnage_marche_haut_anim2x8Data);


/* ////////////////////////////////////////////////////////////////
  // Procédures
  //////////////////////////////////////////////////////////////// */

const char TailleSprite = 8;

void Position_camera(uint8_t Xp, uint8_t  Yp) {

  // Calcul de la position de la zone à afficher
  if (Xp >= 3) {
    Camera.X = Xp - 3;
  } else {
    Camera.X = 0;
  }

  if (Yp >= 3) {
    Camera.Y = Yp - 3;
  } else {
    Camera.Y = 0;
  }

  if (Xp > NB_COLONNES_NIVEAUX - 3) {
    Camera.X = NB_COLONNES_NIVEAUX - 5;
  }

  if (Yp > NB_LIGNES_NIVEAUX - 3) {
    Camera.Y = NB_LIGNES_NIVEAUX - 5;
  }
}

void DessineNiveau() {
  for (int ligne = 0 ; ligne < 7; ligne++) {
    for (int colonne = 0; colonne < 7; colonne++) {
      DessineSprite(niveau[Camera.Y + ligne][Camera.X + colonne], colonne , ligne);
    }
  }
}


void DessineSprite(char Type, char Xs, char Ys) {
  switch (Type) {
    case ' ' : // Sol vide
      gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Sol);
      break;
    case '#' : // Mur
      gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Mur);
      break;
    case '.' : // Zone d'arrivée des caisse
      gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Cible);
      break;
    case '$' : // Une caisse sur le sol
      gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Sol);
      gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Caisse);
      break;
    case '*' : // Une caisse sur une zone d'arrivée
      gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Cible);
      gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Caisse);
      break;
    case '@' : // Le personnage est sur un sol vide
      gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Sol);
      break;
    case '+' : // Le personnage est sur une zone cible pour les caises
      gb.display.drawImage(Xs * TailleSprite, Ys * TailleSprite, Cible);
      break;
  } // end Switch
}

void DessinePerso() {
  int Delta = Joueur.Anime / VITESSE_ANIME;
  int Delta2 = TailleSprite / 2;
  int Xp;
  int Yp;

  Xp = Joueur.X - Camera.X;
  Yp = Joueur.Y - Camera.Y;

  switch (Joueur.En_mouvement) {

    case STOP :
      switch (Joueur.Direction) {
        case BAS :
          gb.display.drawImage(Xp * TailleSprite, Yp * TailleSprite, Marche_bas1);
          break;
        case HAUT:
          gb.display.drawImage(Xp * TailleSprite, Yp * TailleSprite - Delta, Marche_haut1);
          break;
        case GAUCHE :
          gb.display.drawImage(Xp * TailleSprite - Delta, Yp* TailleSprite, Marche_gauche1);
          break;
        case DROITE :
          gb.display.drawImage(Xp * TailleSprite + Delta, Yp * TailleSprite, Marche_droite1);
          break;
      }
      break;

    case BAS :
      if (Joueur.Anime % TailleSprite >= Delta2) {
        gb.display.drawImage(Xp * TailleSprite, Yp * TailleSprite + Delta, Marche_bas2);
      } else {
        gb.display.drawImage(Xp * TailleSprite, Yp * TailleSprite + Delta, Marche_bas1);
      }
      break;

    case HAUT :
      if (Joueur.Anime % TailleSprite >= Delta2) {
        gb.display.drawImage(Xp * TailleSprite, Yp * TailleSprite - Delta, Marche_haut2);
      } else {
        gb.display.drawImage(Xp * TailleSprite, Yp * TailleSprite - Delta, Marche_haut1);
      }
      break;

    case DROITE :
      if (Joueur.Anime % TailleSprite >= Delta2) {
        gb.display.drawImage(Xp * TailleSprite + Delta, Yp * TailleSprite, Marche_droite2);
      } else {
        gb.display.drawImage(Xp * TailleSprite + Delta, Yp * TailleSprite, Marche_droite1);
      }
      break;
    case GAUCHE :
      if (Joueur.Anime % TailleSprite >= Delta2) {
        gb.display.drawImage(Xp * TailleSprite  - Delta, Yp * TailleSprite, Marche_gauche2);
      } else {
        gb.display.drawImage(Xp * TailleSprite - Delta, Yp * TailleSprite, Marche_gauche1);
      }
      break;
  }
}


Enfin, modifion Sokoban.ino pour afficher le personnage

// Sokoban (partie 3 sans gestion des caisses)
// Donnons vie à notre gardien d'entrepot
// Au programme: 
//           - ajout du joueur
//           - gestion des mouvements
//

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


void setup() {
  gb.begin();
  init_level();
}

void init_level() {
  // initialisation du niveau
  Joueur.En_mouvement = STOP;
  Joueur.Direction = BAS;
  Joueur.Anime = 0;
  trouve_position_perso();
}

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

  // On actualise l'état du joueur
  MAJ_Joueur();

  // On actualise la position de la zone du tableau à afficher
  Position_camera(Joueur.X, Joueur.Y);

  // On affiche le contenu de chacune des cases
  DessineNiveau();
  DessinePerso();
}


Voilà, je vous laisse compléter les morceaux de programmes que je ne copie pas dans ce tuto (les .h à mettre à jour)


Exercice: Ajoutez la gestion des caisses comme vous le souhaitez.

Je ne publie pas de solution ici, cherchez mais après, si vous n'y arrivez pas ou pour voir ma solution, regardez le fichier zip joint à ce tuto. Tout est dedans

Voilà, je mets le zip à jour et je vous laisse essayer et proposer des améliorations.

Maintenant que l'on gère les déplacements des caisses, la prochaine étape sera de vérifier si l'utilisateur a gagner, de lui permettre de recommencer le niveau en appuyant sur b et on commencera à gérer un menu multilingue et l'état du programme. Ca suffira bien pour une 4ème étape, non ?


Retrouvez la partie 1 de ce tutoriel ici: Partie 1
ou la partie 2: Partie 2

Le jeu complet fait à partir de cette série de tutoriels peut être retrouvé ici: Jeu terminé

Voir la création

Juice_Lizard

NEW il y a 5 ans

Merci pour ce tuto. Pour information, Square Nose est un "nez-cureuil" (ou "squarrel" en anglais). Est-ce qu'il y a déjà un tuto pour expliquer comment associer des fichiers dans le code? J'essaye de stocker mes sprites à part mais j'ai du mal. Comment gérer les #include, les .ino, .h, .cpp, etc?

jicehel

NEW il y a 5 ans

oui, .ino et .cpp c'est pareil mais normalement tu as l'explication dans mon précédent tuto sur le SOKOBAN. Je n'ais pas essayé d'être exhaustif mais d'expliquer le fonctionnement simplement pour ceux qui commencent avec ça (comme je l'tais il y a quelques mois) mais ton avis sera interessant pour voir si c'est compréhensible et s'il y a des manques d'explications sur certains points. (https://gamebuino.com/creations/sokoban-partie-2-ajoutons-les-graphismes)