Day 12 - Démos rapides ← Précédent
C’est parti pour un tutorial assez dense, et il vaut mieux que vous ayez lu les autres tutoriaux/démos auparavant... Il est conseillé de lire les paragraphes sur les sprites, les backgrounds, les collisions, les animations et TOUS ceux concernant les maths avant de commencer...
Ceci vous apprendra les bases pour créer un jeu de plate-forme, avec Mario ! Eh oui, il est partout. Comme toujours, ce projet va se décomposer en plusieurs parties...
Ce sera assez simple, car nous allons réutiliser ce que nous avons déja vu... Je posterais le code complet ici. Pour le moment, j’ai décidé de couper le code en plusieurs sections. Je vais commencer avec les déclarations et la foncton “main”, avant de passer à la 2ème fonction :
Premièrement, prenons ce sympatique sprite de Mario:
Comme vous pouvez le voir, Il n’y a que 3 frames. Les 2 premières pour l’animation de la marche, et la 3ème pour le saut... Simple, mais efficace...
Maintenant, voici la première partie du code, basée sur les paragraphes Démos/Platforme:
// Inclusions, seulement un sprite #include <PA9.h> // Inclusion de PAGfxConverter #include "gfx/all_gfx.c" #include "gfx/all_gfx.h" typedef struct{ s32 x, y; s32 vy; // utilisé pour le saut... } mariotype; mariotype mario; #define GRAVITY 48 void MoveMario(void); // Fonction principale int main(void) { // Initialisation de PAlib PA_Init(); PA_InitVBL(); PA_InitText(1, 0); PA_LoadSpritePal(0, 0, (void*)sprite0_Pal); // Palette.... mario.x = 0<<8; mario.y = (192-32)<<8; // bas de l'écran... point fixe mario.vy = 0; // ne pas sauter PA_CreateSprite(0, 0,(void*)mario_Sprite, OBJ_SIZE_32X32,1, 0, mario.x>>8, mario.y>>8); // Sprite while(1) { MoveMario(); PA_SetSpriteXY(0, 0, mario.x>>8, mario.y>>8); PA_WaitForVBL(); } return 0; }
Je ne m’attarderais pas sur le début, car ce ne sont que des inclusions normales, mais plutôt sur la structure du mouvement de Mario (utilisation d’un point fixe), et sur la macro concernant la gravité...
Ensuite, nous avons un petit void MoveMario(void);, qui est juste une fonction pour faire bouger Mario... Si le code n’est pas là, pourquoi l’avoir marqué?? Parce que cette fonction se trouve APRES la fonction “main”; il y a d’abord la déclaration de la fonction, puis main, puis la fonction.
Après l’initialisation normale, on initialise le texte pour l’écran du haut...
Après ça, on charge juste le sprite (sur l’écran du bas) et on initialise la position... Notez que nous utilisons un point fixe mathématique, c’est pourquoi le «8 est là... Et le »8 pour reconvertir en position normale, quand on place le sprite...
while(1) { MoveMario(); PA_SetSpriteXY(0, 0, mario.x>>8, mario.y>>8); PA_WaitForVBL(); }
Avant tout, la boucle principale ne fait pas grand-chose... Elle appelle une fonction (pour déplacer Mario), place le sprite à l’écran, et attend la synchronisation de l’écran... Rien de plus.
Maintenant nous allons taper la fonction MoveMario(), c’est-à-dire une grosse partie du code:
void MoveMario(void){ if(Pad.Newpress.Right) { PA_StartSpriteAnim(0, 0, 0, 1, 6); PA_SetSpriteAnim(0, 0, 1); PA_SetSpriteHflip(0, 0, 0); } else if(Pad.Newpress.Left) { PA_StartSpriteAnim(0, 0, 0, 1, 6); PA_SetSpriteAnim(0, 0, 1); PA_SetSpriteHflip(0, 0, 1); } if ((Pad.Newpress.A) && (mario.vy == 0)){ // Si la touche A est pressée et que Mario est à terre mario.vy = -1000; // Début du saut } // Code du mouvement mario.x += (Pad.Held.Right - Pad.Held.Left)<<8; // le point fixe... // Et la gravité s'incruste...:) mario.vy += GRAVITY; mario.y += mario.vy; if (mario.y >= (192-32)<<8) { mario.y = (192-32)<<8; mario.vy = 0; } if (mario.vy != 0) PA_SetSpriteAnim(0, 0, 2); // Si la vitesse n'est pas nul, vérification que le sprite est celui du saut! else if(!((Pad.Held.Left)||(Pad.Held.Right))) PA_SetSpriteAnim(0, 0, 0);// L'image n'est ni dans les air ni en marche }
Ça devrait vous être familier, puisqu’une partie du code est copiée des exemples d’Animation dans PaLib...
if(Pad.Newpress.Right) { PA_StartSpriteAnim(0, 0, 0, 1, 6); PA_SetSpriteAnim(0, 0, 1); PA_SetSpriteHflip(0, 0, 0); }
Ça veut tout simplement dire que si on appuie sur “Droit” pour la première fois, le sprite de devrait pas être “flippé” et l’animation devrait débuter, de la frame 0 à 1 (puisque l’animation de marche n’a que 2 frames), à une vitesse de 6 frames par seconde. 6fps cest vraiment lent, mais avec seulement 2 frames, c’est très bien.
PA_SetSpriteAnim(0,0,1);
Cette ligne permet à notre Mario de commencer à marcher des que l’on appuie sur une direction. (frame 1 au lieu de 0) Sinon Mario va donner l’impression de glisser sur le sol (surtout si on appuie de manière répété sur les touches) car dans la première image de l’animation Mario est en attente.
Si on n’a pas appuyé sur Droit, ça vérifie si on a pressé Gauche, dans ce cas, c’est pratiquement la même chose, mais cette fois, le sprite doit être “flippé” pour qu’il regarde dans la bonne direction..
Voila la première partie du code de saut/gravité
if ((Pad.Newpress.A) && (mario.vy == 0)){ mario.vy = -1000; // Commence à sauter }
Lorsque vous pressez A, la vitesse vertical est fixée à son maximum (vous pouvez choisir de mettre plus si vous le souhaitez), alors mario saute... Pourquoi avons-nous besoin d’ajouter la condition mario.vy == 0 ? Si nous ne le faisions pas, vous pourriez pressez A autant de fois que vous le voulez, et le faire grimper encore et encore !! Avec ceci, vous ne pouvez pas démarrer un saut à moins d’être par terre...
Afin de bouger mario à gauche et à droite, il y a : ‘mario.x += (Pad.Held.Right - Pad.Held.Left)«8;’ ... A quoi sert le ‘«8’ ? Souvenez-vous que nous travaillons avec les nombres en points fixes, donc nous voulons juste avoir un mouvement de 1/256 pixel par tour ! Notez qu’avec ce code, vous pouvez changer de direction et bouger même dans les airs...
Viens ensuite la seconde partie du code de la gravité :
mario.vy += GRAVITY; mario.y += mario.vy; if (mario.y >= (192-32)<<8) { mario.y = (192-32)<<8; mario.vy = 0; }
C’est comme nous l’avons déjà vu... A chaque frame, vous changez la vitesse selon la valeur de la gravité, et ainsi bougez le sprite selon la vitesse... Si le sprite sort de l’écran, vous devez changer sa position et fixer sa vitesse à 0....
La dernière partie du code (wow, c’était vraiment court !) permet de changer l’animation si vous êtes en train de sauter, et la stoppe si vous n’êtes pas en train de marcher :
if (mario.vy != 0) PA_SetSpriteAnim(0, 0, 2); else if(!((Pad.Held.Left)||(Pad.Held.Right))) PA_SetSpriteAnim(0, 0, 0);
Premièrement, si votre vitesse n’est pas nulle (que vous montiez ou descendez... donc quand vous êtes en l’air...), cela fixe l’animation à la dernière frame (frame 2), qui est la frame avec mario et yoshi dans les air... Si vous n’êtes pas en train de sauter, et pas en train de vous déplacer à droite ou à gauche, alors ceci fixe juste l’animation à 0 (la frame normal d’arrêt)
Et c’est tout pour la pour la partie 1 !! Revenez bientot pour la suite, plus intéressante mais une partie plus difficile, avec un système de collisions de tiles simple...
Maintenant que notre petit mario peut se déplacer, nous allons ajouter un background, ainsi que les collisions avec lui... Voici les 2 backgrounds que j’ai ajoutés (1 pour les collisions, et l’autre pour l’arrière plan)
Comme vous pouvez le voir, ils sont exactement à la taille de l’écran de la DS, parce que nous n’allons pas faire de scrolling...
Je posterai le code complet, encore une fois, mais je vais expliquer pas à pas plusieurs parties :
// Includes, only one sprite #include <PA9.h> // PAGfxConverter Include #include "gfx/all_gfx.c" #include "gfx/all_gfx.h" typedef struct{ s32 x, y; s32 vy; // used for jumping... s32 flip; } mariotype; mariotype mario; #define GRAVITY 48 #define MARIO_SPEED 512 void MoveMario(void); void CheckCollisions(void); u8 GetTile(s16 x, s16 y); u8 LeftCollision(void); u8 RightCollision(void); u8 DownCollision(void); u8 TouchingGround(void);
C’est le code qui précède la fonction main(). Comme vous pouvez le voir, j’ai ajouté la variable ‘flip’ dans une structure, qui sera utilisée plus tard. Il y a également quelques nouvelles fonctions déclarées, qui ont des noms simples, je parie que vous pouvez deviner ce que fait chacune d’elle... Quelques unes n’ont pas été déclarées comme ‘void’, mais comme ‘u8’, ce qui signifie qu’elles retourneront une valeur....
Regardons maintenant le code du main :
// Fonction Main int main(void) { // PAlib init PA_Init(); PA_InitVBL(); PA_InitText(1, 0); PA_LoadSpritePal(0, 0, (void*)sprite0_Pal); // Palette.... PA_LoadPAGfxLargeBg(0, 1, mario_world); // plateformes... PA_LoadPAGfxLargeBg(0, 3, back); // back mario.x = 0<<8; mario.y = (128-32)<<8; // Bas de l'écran... point fixe mario.vy = 0; // ne saute pas mario.flip = 0; PA_CreateSprite(0, 0,(void*)mario_Sprite, OBJ_SIZE_32X32,1, 0, mario.x>>8, mario.y>>8); // Sprite while(1) { MoveMario(); PA_SetSpriteXY(0, 0, mario.x>>8, mario.y>>8); PA_OutputText(1, 2, 9, "X : %d ", mario.x >> 8); PA_OutputText(1, 2, 10, "Y : %d ", mario.y >> 8); PA_WaitForVBL(); } return 0; }
Je n’ai pas changé grand chose dans ce code. Il contient seulement 2 chargements de background supplémentaires :
PA_LoadPAGfxLargeBg(0, 1, mario_world); charge le background où l’on effectuera les collisions... J’ai utilisé un LargeBg parce que nous aurons besoin d’avoir plus de 512 pixels de large lorsque nous voudrons faire du scrolling...PA_LoadPAGfxLargeBg(0, 3, back); est l’arrière plan, avec quelques nuages. Il se situe à la position 4, parce que rien ne peut se trouver derrière lui.Pourquoi n’ai-je pas utilisé le background 0 pour mario_world ? Parce que nous en aurons besoin pour afficher le score, les pièces, etc ...
Dernier petit changement : J’ai ajouté 2 petits textes pour afficher la position de mario ...
Et maintenant, la fonction MoveMario() :
void MoveMario(void){ if(Pad.Newpress.Right) { PA_StartSpriteAnim(0, 0, 0, 1, 6); PA_SetSpriteAnim(0, 0, 1); PA_SetSpriteHflip(0, 0, 0); mario.flip = 0; } else if(Pad.Newpress.Left) { PA_StartSpriteAnim(0, 0, 0, 1, 6); PA_SetSpriteAnim(0, 0, 1); PA_SetSpriteHflip(0, 0, 1); mario.flip = 1; } if ((Pad.Newpress.A) && (TouchingGround())){ // Si A est pressé, et que mario n'est pas dans les airs mario.vy = -1200; // Start jumping } // Moving Code mario.x += (Pad.Held.Right - Pad.Held.Left)*MARIO_SPEED; // en point fixe // Add gravity mario.vy += GRAVITY; mario.y += mario.vy; CheckCollisions(); if (!TouchingGround()) PA_SetSpriteAnim(0, 0, 2); // Pas sur le sol else if(!((Pad.Held.Left)||(Pad.Held.Right))) PA_SetSpriteAnim(0, 0, 0);// Image s'il n'est pas dans les air, et qu'il ne marche pas }
Pas grand chose n’a changé, excepté :
if (Pad.Newpress.A) && (TouchingGround){ Maintenant, mario saute lorsque nous avons vérifié qu’il touche le sol.... En utilisant une nouvelle fonction, que nous verrons plus loin.mario.x += (Pad.Held.Right - Pad.Held.Left)*MARIO_SPEED; J’ai décidé qu’il ne se déplaçait pas assez vite, alors j’ai ajouté une macro MARIO_SPEED, et j’ai fait en sorte qu’il se déplace de 2 pixels par frame, ce qui semble meilleur.CheckCollisions(); est la nouvelle fonction de détection des collisions... nous la verrons juste aprèsif (!TouchingGround()) PA_SetSpriteAnim(0, 0, 2); est la commande qui montre mario en train de sauter... Plutôt que d’utiliser la vitesse comme précédemment, on vérifie maintenant si mario touche le sol ou non....C’est tout pour ces fonctions, pas plus, comme je l’avais dit. Mais maintenant, toutes les fonctions restantes sont nouvelles, je les détaillerai toutes 1 par 1...
u8 GetTile(s16 x, s16 y){ if (x < 0) return 1; // Say it was a collision... return mario_world_Map[((y>>3)*32) + (x>>3)]; }
Cette fonction très simple retourne le numéro du tile qui se situe à la position en pixels donnée en paramètre... Pourquoi n’ai-je mis que çà dans cette fonction ? Parce que lorsque nous ajouterons le scrolling, nous aurons juste besoin de changer cette fonction, et tout sera mis à jour... C’est simple, non ? Comment fait-elle pour trouver le bon tile ?
(x»3) est la position du tile. X est divisé par 8 (équivalent de »3) parce qu’un tile est large de 8 pixels...(y»3)*32 est composé de :(y»3) pour récupérer la hauteur du tile, tout comme x»3, parce que les tiles ont une hauteur de 8 pixels*32, parce que le background possède une largeur de 32 tiles, alors pour descendre d’un tile, vous devez ajouter 32 tiles... Nous devrons changer çà lorsque nous ajouterons le scrolling, pour que cela fonctionne avec des cartes plus grandes ....Donc cette fonction retourne le numéro du tile... S’il n’y a pas de tile (un tile transparent), le numéro retourné sera 0... dans cette exemple, nous aurons une collision pour tous les numéros (de tiles) sauf 0. Ce qui signifie que vous aurez une collision avec tout. Si vous avez besoin d’autres collisions, comme passer à travers certains murs, vous devrez adapter le code à vos besoins (ceci est juste un code de base pour les débutants...)
u8 LeftCollision(void){ return GetTile((mario.x>>8)+2, (mario.y>>8)+8+(mario.flip*13)); } u8 RightCollision(void){ return (GetTile((mario.x>>8)+29, (mario.y>>8)+8 + ((!mario.flip)*13))); }
Ces 2 fonctions seront utilisées pour vérifier qu’il y a une collision à gauche ou à droite. La première chose, c’est qu’elles utilisent la fonction GetTile() que nous avons déjà vue, en lui passant la position normale de mario, en point non fixe, en d’autres termes ces coordonnées.
Pourquoi devons-nous tester x=2 ou 29, et pas x = 0 et x = 31 ? Parce si vous regardez le sprite de mario, en haut de la page, vous verrez qu’il y a 2 pixels vides de chaque côté... Donc nous devons seulement tester la collision là où se situe le dessin...
u8 DownCollision(void){ return (mario.vy >= 0 && GetTile((mario.x>>8)+10 + (mario.flip*11), (mario.y>>8)+31)); }
Celui-ci teste si mario est en train de toucher un étage ou non. Si c’est le cas, nous devrons le remonter un petit peu... Ici, il y a encore le mario.flip !! Pourquoi cette fois-ci ? Parce que, regardez les pieds du sprite, ce qui déterminera la collision... Ils ne sont pas centrés ! Ce qui signifie qu’en fonction du coté où il regarde, ses pieds seront un peu plus à gauche ou à droite. C’est pris en compte en changeant la position actuelle de 10 à 21 s’il est flippé....
u8 TouchingGround(void){ return GetTile((mario.x>>8)+10 + (mario.flip*11), (mario.y>>8)+32); }
C’est la dernière vérification de collision. C’est exactement le même code que précédemment, mais 1 pixel plus bas (+32 au lieu de +31). Ça vérifie si le sol se trouve sous les pieds de yoshi...
Maintenant voici l’actuelle fonction CheckCollisions, qui utilisera toutes les fonctions dont nous avons parlé et ajustera les positions/vitesses...
void CheckCollisions(void){ while(LeftCollision()){ // Collision sur la gauche du sprite... mario.x+= 256; // Déplacement d'un 1 pixel... } while(RightCollision()){ // Collision sur la droite du sprite... mario.x-= 256; // Déplacement d'un 1 pixel... } while(DownCollision()){ // Collision en bas du sprite... mario.y -= 128; // Déplacement de 1/2 pixel... mario.vy = 0; // Un étage est atteint... } if(TouchingGround()) mario.vy = 0; }
Rien de plus à ajouter, en fait ! Tant que la sprite est touchée à gauche, déplacement à droite. Et tant qu’elle touche à droite, déplacement à gauche... Ça semble logique...
Pour la collision avec un étage, j’ai fait le déplacement d’1/2 pixel, juste pour le fun, lol... Si vous touchez un étage, alors la vitesse verticale devra être fixée à 0...
Il y a 2 parties différentes au niveau de la vérification du sol :
Et c’est tout !
Maintenant que ça fonctionne, nous devons juste ajouter le scrolling (et un parallax scrolling, pour un meilleur effet), puis voir le résultat
Encore une fois, c’est un système de colision de tile basique, car il traite toutes les tiles de la même façon. Vous pourriez avoir une vérification différente, et ainsi faire en sorte que le joueur réagisse différemment à chaque type de tile...
Voici ce que j’ai obtenu en dualis r12 (r11 ne fonctionne pas avec la LargeMaps) :
Maintenant que tout fonctionne, nous allons ajouter un défilement à l’arrière plan (défilement Parallax) pour donner une bonne impression de profondeur lorsqu’il y a défilement. Les 3 arrières plans qui seront utilisés sont :
Comme vous pouvez le remarquer, les trois images n’ont pas la même taille. Pourquoi ? Parce qu’elles ne vont pas défiler à la même vitesse ! Le premier plan (première image) défilera à une vitesse normale, c’est pourquoi il est plus grand et fait 1024px de largeur. Le second plan, défilera un peu plus doucement, et le plan contenant les nuages, défilera à une vitesse à peu près équivalente à 1/4 de la vitesse du premier plan, c’est pourquoi ce dernier plan n’a pas besoin d’être très long.
Je posterai le texte de PAGfx.ini une seule fois :
#TranspColor Magenta #Sprites : mario.png 256colors sprite0 #Backgrounds : mario_world.png LargeMap hills.png LargeMap back.png LargeMap
Comme vous pouvez le voir, la couleur transparente est le magenta... Dans l’arrière plan des plateformes et des montagnes, il y a beaucoup de magenta qui deviendra transparent... Le dernier (les nuages), cependant, ne possède pas de magenta.. Comme il se situe derrière, vous ne voulez pas qu’il soit transparent... Tous les arrières plans sont déclarés comme LargeMap, parce qu’ils ont besoin d’être plus larges que 512 pixels ...
Pour cette troisième partie, je posterai seulement des bouts de codes, parce qu’il y a très peu de changement...
Concernant la structure de mario, j’ai juste ajouté un paramètre de défilement :
typedef struct{ s32 x, y; s32 vy; // used for jumping... s32 flip; s32 scrollx; // Scroll value... } mariotype;
Rien de plus à dire à propos de çà je crois. Il démarre à 0...
Ensuite, nous avons besoin de charger les arrières plans, chacun à un niveau différent :
PA_LoadPAGfxLargeBg(0, 1, mario_world); // plateforme... PA_LoadPAGfxLargeBg(0, 2, hills); // montagne PA_LoadPAGfxLargeBg(0, 3, back); // dernier / nuages
Maintenant que les arrières plans sont chargés, nous devons initialiser le défilement parallax... Si vous n’êtes pas sûr de ce que c’est, allez voir le tutorial sur les arrières plans et relisez la partie sur le défilement parallax... Le code est simple :
PA_InitParallaxX(0, 0, 256, 128, 64);
Voici venir le seul code ajouté pour le défilement, lorsque mario atteint un bord :
MoveMario(); if ((((mario.x-mario.scrollx)>>8) > 160) && ((mario.x>>8) < 1024-128)){ // Scroll more... mario.scrollx = mario.x - (160<<8); } else if ((((mario.x-mario.scrollx)>>8) < 64) && ((mario.x>>8) > 64)){ mario.scrollx = mario.x - (64<<8); } PA_ParallaxScrollX(0, mario.scrollx>>8); PA_SetSpriteXY(0, 0, (mario.x-mario.scrollx)>>8, mario.y>>8);
Je l’ai ajouté après MoveMario, donc il possède la dernière position de la sprite...
Comme vous pouvez le remarquer, quand est-ce qu’il défile sur la droite ? Lorsque la position de mario sur l’écran est plus loin que 160 pixel, mais seulement si il n’a pas atteint la fin du niveau (1024-128 parce qu’on stoppe le défilement avant la fin...)
Et comment récupères-t-on la bonne valeur de défilement (en point fixé) ? mario.scrollx = mario.x - (160«8); Cela signifie que nous défilons assez pour positionner le sprite à une valeur maximum de x = 160... (excepté, encore une fois, si vous êtes à la fin du niveau)
La même chose est appliqué à l’autre direction, il y a défilement seulement si le sprite est à plus de 64 pixels du début, et le sprite n’est jamais placé avant les 64 pixels si possible...
PA_ParallaxScrollX(0, mario.scrollx>>8);
Ceci fait défiler les arrières plans par la valeur de défilement défini, on utilise » 8 parce que nous sommes en valeurs de point fixés, et le défilement parallax prends des valeurs normales...
Un autre changement est la position du sprite :
PA_SetSpriteXY(0, 0, (mario.x-mario.scrollx)>>8, mario.y>>8);
Ici, nous supprimons la valeur du défilement avant de positionner le sprite ... Cela veut dire que si mario.x est à 512, il sera placé avant l’écran pour concorder avec la valeur du défilement... n’est-pas propre ?
La dernière modification se situe dans la fonction GetTile() :
u8 GetTile(s16 x, s16 y){ if (x < 0) return 1; // Say it was a collision... return mario_world_Map[((y>>3)*128) + (x>>3)]; }
J’ai changé le nombre de tiles horizontal de 32 (256 pixels) à 128 (1024 pixels de large), pour que cela fonctionne à nouveau...
Et c’est tout !!
Noté que pour avoir plus de collisions prises en compte, nous devrions utiliser une carte de collision, qui est une carte que vous feriez comme n’importe quelle carte dans un éditeur, mais que vous ne pourriez pas voir sur l’écran...
yLes “«n” et “»n” sont des décalages à gauche et à droite des bits. ainsi si c = 1 «3, cela correspond à faire c=1 puis un décalage des bits de c vers la gauche (un décalage de 1 multplie par 2) on a donc c = 8 (Edit: Skoual)
Prochain → Jour 14 - Carré rouge