Controls: D-Pad: [Arrows / WASD] - A: [J] - B: [K] - Menu: [U] - Home: [I]
Enjoy games at full speed with sound and lights on the Gamebuino META!
Emulator by aoneill
Durée: 30 minutes (à la louche)
Niveau: 2 (+3 en charisme et +3 en intelligence ;) )
Prérequis :
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é
Bon en attendant de savoir ce que vous préférez, une version temporaire est disponible avec 4 niveaux, du son, l’affichage de la carte du niveau, la possibilité de recommencer un niveau et le changement de niveau quand le précédent est terminé.
Il me reste la page d’accueil permettant de jouer, d’afficher l’aide et peut être de changer la langue (français ou anglais dans un premier temps) et ajouter la sauvegarde du dernier niveau réussi et du coup de permettre de reprendre ou de commencer du début quand on lance une partie. Quand tout sera ok, je n’aurais plus qu’à rajouter des niveaux et peut être d’autres choses que vous m’aurez dit.
C’est disponible ici: GitHub - Jicehel/Sokoban: Reprise de mon Sokoban
Petite question / sondage. Vous préférez quoi comme titre:
V1:
ou V2:
Fini: SOKOBAN - Gamebuino