Créons un jeu de plateformes de A à Z !



Tutoriel présenté par : Jérémie F. Bellanger

Dernière mise à jour : 30 octobre 2011

Difficulté :  




Chapitre 19
Un ressort et une map animée
!


    Continuons d'améliorer notre petit jeu de plateformes, qui commence enfin à ressembler à quelque chose !

    Nous allons maintenant programmer le ressort (pour qu'il envoie notre lapin valser en l'air !  ) et ensuite nous nous occuperons d'animer la map en variant l'affichage de deux tilesets !
   

 

Résultat à la fin de ce chapitre : la map s'anime sous vos yeux ébahis et le ressort fonctionne !



    Commençons par nous occuper de notre ressort !

    Pour cela, il va nous falloir, une fois encore, modifier la 
fonction mapCollision() qui gère les collisions du joueur avec la map.

    En effet, on va rajouter un nouveau test pour détecter les collisions avec la tile Ressort. Et, si c'est le cas, on fera simplement sauter notre petit lapinou !


Nom du fichier : map.c


  void mapCollision(GameObject *entity)
{

    int i, x1, x2, y1, y2;

    entity->onGround = 0;

    if(entity->h > TILE_SIZE)
        i = TILE_SIZE;
    else
        i = entity->h;

    for (;;)
    {
       
        x1 = (entity->x + entity->dirX) / TILE_SIZE;
        x2 = (entity->x + entity->dirX + entity->w - 1) / TILE_SIZE;

        y1 = (entity->y) / TILE_SIZE;
        y2 = (entity->y + i - 1) / TILE_SIZE;

        if (x1 >= 0 && x2 < MAX_MAP_X && y1 >= 0 && y2 < MAX_MAP_Y)
        {
            //Si on a un mouvement à droite
            if (entity->dirX > 0)
            {

                //Test de la tile Power-up : Etoile (= tile N°5)
                if (map.tile[y1][x2] == 5)
                {
                    //On appelle la fonction getItem()
                    getItem();

                    //On remplace la tile power-up par une tile transparente
                    map.tile[y1][x2] = 0;
                }
                else if(map.tile[y2][x2] == 5)
                {
                    //On appelle la fonction getItem()
                    getItem();
                    //On remplace la tile power-up par une tile transparente
                    map.tile[y2][x2] = 0;
                }

                //On vérifie si les tiles recouvertes sont solides
                if (map.tile[y1][x2] > BLANK_TILE || map.tile[y2][x2] > BLANK_TILE)
                {
                    // Si c'est le cas, on place le joueur aussi près que possible
                    // de ces tiles, en mettant à jour ses coordonnées. Enfin, on réinitialise
                    //son vecteur déplacement (dirX).

                    entity->x = x2 * TILE_SIZE;

                    entity->x -= entity->w + 1;

                    entity->dirX = 0;

                }

            }

            //Même chose à gauche
            else if (entity->dirX < 0)
            {

                //Test de la tile Power-up : Etoile (= tile N°5)
                if (map.tile[y1][x1] == 5)
                {
                    //On appelle la fonction getItem()
                    getItem();

                    //On remplace la tile power-up par une tile transparente
                    map.tile[y1][x1] = 0;
                }
                else if(map.tile[y2][x1] == 5)
                {
                    //On appelle la fonction getItem()
                    getItem();
                    //On remplace la tile power-up par une tile transparente
                    map.tile[y2][x1] = 0;
                }
               
               
                if (map.tile[y1][x1] > BLANK_TILE || map.tile[y2][x1] > BLANK_TILE)
                {

                    entity->x = (x1 + 1) * TILE_SIZE;

                    entity->dirX = 0;
                }


            }

        }

        //On sort de la boucle si on a testé toutes les tiles le long de la hauteur du sprite.
        if (i == entity->h)
        {
            break;
        }

        //Sinon, on teste les tiles supérieures en se limitant à la heuteur du sprite.
        i += TILE_SIZE;

        if (i > entity->h)
        {
            i = entity->h;
        }
    }

    //On recommence la même chose avec le mouvement vertical (axe des Y)
    if(entity->w > TILE_SIZE)
        i = TILE_SIZE;
    else
        i = entity->w;


    for (;;)
    {
        x1 = (entity->x) / TILE_SIZE;
        x2 = (entity->x + i) / TILE_SIZE;

        y1 = (entity->y + entity->dirY) / TILE_SIZE;
        y2 = (entity->y + entity->dirY + entity->h) / TILE_SIZE;

        if (x1 >= 0 && x2 < MAX_MAP_X && y1 >= 0 && y2 < MAX_MAP_Y)
        {
            if (entity->dirY > 0)
            {

                /* Déplacement en bas */
               
                //Test de la tile Power-up : Etoile (= tile N°5)
                if (map.tile[y2][x1] == 5)
                {
                    //On appelle la fonction getItem()
                    getItem();

                    //On remplace la tile power-up par une tile transparente
                    map.tile[y2][x1] = 0;
                }
                else if(map.tile[y2][x2] == 5)
                {
                    //On appelle la fonction getItem()
                    getItem();
                    //On remplace la tile power-up par une tile transparente
                    map.tile[y2][x2] = 0;
                }

               /* Gestion du ressort */
                if ((map.tile[y2][x1] == 7 ) || (map.tile[y2][x2] == 7 ))
                {
                     //On met -20, pour lui faire faire un méga saut ;)
                      entity->dirY = -20;

                    //On indique au jeu qu'il a atterri pour réinitialiser le double saut
                    entity->onGround = 1;
                }


                else  if (map.tile[y2][x1] > BLANK_TILE || map.tile[y2][x2] > BLANK_TILE)
                {
                    //Si la tile est solide, on y colle le joueur et
                    //on le déclare sur le sol (onGround).
                    entity->y = y2 * TILE_SIZE;
                    entity->y -= entity->h;

                    entity->dirY = 0;

                    entity->onGround = 1;
                }

            }

            else if (entity->dirY < 0)
            {

                /* Déplacement vers le haut */
               
                //Test de la tile Power-up : Etoile (= tile N°5)
                if (map.tile[y1][x1] == 5)
                {
                    //On appelle la fonction getItem()
                    getItem();

                    //On remplace la tile power-up par une tile transparente
                    map.tile[y1][x1] = 0;
                }
                else if(map.tile[y1][x2] == 5)
                {
                    //On appelle la fonction getItem()
                    getItem();
                    //On remplace la tile power-up par une tile transparente
                    map.tile[y1][x2] = 0;
                }
               

                if (map.tile[y1][x1] > BLANK_TILE || map.tile[y1][x2] > BLANK_TILE)
                {

                    entity->y = (y1 + 1) * TILE_SIZE;

                    entity->dirY = 0;

                }

            }
        }

        //On teste la largeur du sprite (même technique que pour la hauteur précédemment)
        if (i == entity->w)
        {
            break;
        }

        i += TILE_SIZE;

        if (i > entity->w)
        {
            i = entity->w;
        }
    }

    /* Maintenant, on applique les vecteurs de mouvement si le sprite n'est pas bloqué */
    entity->x += entity->dirX;
    entity->y += entity->dirY;

    //Et on contraint son déplacement aux limites de l'écran, comme avant.
    if (entity->x < 0)
    {
        entity->x = 0;
    }

    else if (entity->x + entity->w >= map.maxX)
    {
        entity->x = map.maxX - entity->w - 1;
    }

    //Maintenant, s'il sort de l'écran par le bas (chute dans un trou sans fond), on lance le timer
    //qui gère sa mort et sa réinitialisation (plus tard on gèrera aussi les vies).
    if (entity->y > map.maxY)
    {
        entity->timerMort = 60;
    }
}

         

   
    Bon, voilà, à peine trois lignes encore et le tour est joué ! 
    On teste simplement si on a un ressort sous les pieds de notre super lapin, et si c'est le cas, on le fait sauter !

    Attention toutefois à ne pas oublier le else après, sinon le jeu va tester que notre lapin est sur une tile solide et arrêter son saut !


    Bon, il ne reste plus qu'à nous occuper de l'animation de la map maintenant !


    Première chose, on va avoir besoin d'un second tileset ! Et comme je suis vraiment formidable , je vous le donne encore une fois ! Enregistrez-le simplement dans le dossier graphics :



tilesetB.png


    Voilà, il ne reste maintenant plus qu'à charger notre tileset dans une nouvelle variable. Pour ça, on se rend dans notre fichier structs.h et on va créer une nouvelle SDL_Surface pour notre second tileset. On va aussi créer un timer et une variable qui contiendra le numéro du tileset à afficher pour gérer l'animation.

Nom du fichier : structs.h


   /* Structure pour gérer la map à afficher (à compléter plus tard) */

  typedef struct Map
 {

    SDL_Surface *background, *tileSet;
    SDL_Surface *tileSetB;


    /* Coordonnées de début, lorsqu'on doit dessiner la map */
    int startX, startY;

    /* Coordonnées max de fin de la map */
    int maxX, maxY;

    /* Tableau à double dimension représentant la map de tiles */
    int tile[MAX_MAP_Y][MAX_MAP_X];

    /* Timer et numéro du tileset à afficher pour animer la map */
    int mapTimer, tileSetNumber;

 } Map;


    Maintenant que notre structure est à jour, on va charger notre nouveau tileset dans la fonction loadGame(), et initialiser notre timer :  


Nom du fichier init.c


  void loadGame(void)
{

    /* Charge l'image du fond et le tileset */
    map.background = loadImage("graphics/background.png");
    map.tileSet = loadImage("graphics/tileset.png");
    map.tileSetB = loadImage("graphics/tilesetB.png");

    //On initialise le timer
     map.mapTimer = TIME_BETWEEN_2_FRAMES*3;
     map.tileSetNumber = 0;

    loadMap("map/map1.txt");

    /* On initialise les variables du jeu */
    jeu.vies = 3;
    jeu.etoiles = 0;

    /* On charge le HUD */
    jeu.HUD_vie = loadImage("graphics/life.png");
    jeu.HUD_etoiles = loadImage("graphics/stars.png");

}

         

    Sans oublier de libérer le tileset à la fin, comme on l'a déjà vu !
    


Nom du fichier : init.c


 void cleanup()
{

    int i;

    /* Libère l'image du background */

    if (map.background != NULL)
    {
        SDL_FreeSurface(map.background);
    }


    /* Libère l'image des tilesets */
    if (map.tileSet != NULL)
    {
        SDL_FreeSurface(map.tileSet);
    }
    if (map.tileSetB != NULL)
    {
        SDL_FreeSurface(map.tileSetB);
    }

    /* Libère le sprite du héros */
    if (player.sprite != NULL)
    {
        SDL_FreeSurface(player.sprite);
    }

    /* Libère le sprite des monstres */
    for(i = 0 ; i < MONSTRES_MAX ; i++)
    {
        if (monster[i].sprite != NULL)
    {
        SDL_FreeSurface(monster[i].sprite);
    }
    }


    //Libère le HUD
    if (jeu.HUD_etoiles != NULL)
    {
        SDL_FreeSurface(jeu.HUD_etoiles);
    }
    if (jeu.HUD_vie != NULL)
    {
        SDL_FreeSurface(jeu.HUD_vie);
    }

    /* Close the font */
    closeFont(font);


    /* Close SDL_TTF */
    TTF_Quit();

    /* Quitte la SDL */
    SDL_Quit();

}

         

    Voilà, notre nouveau tileset est désormais chargé et opérationnel ! Il ne reste plus qu"à l'afficher à l'aide d'un timer, en alternance avec le premier. Un peu comme on avait fait pour animer nos sprites, quoi !

    Pour cela, on va utiliser nos nouvelles variables dans la fonction drawMap() :


Nom du fichier map.c


  void drawMap(void)
{
    int x, y, mapX, x1, x2, mapY, y1, y2, xsource, ysource, a;

    /* On initialise mapX à la 1ère colonne qu'on doit blitter.
    Celle-ci correspond au x de la map (en pixels) divisés par la taille d'une tile (32)
    pour obtenir la bonne colonne de notre map
    Exemple : si x du début de la map = 1026, on fait 1026 / 32
    et on sait qu'on doit commencer par afficher la 32eme colonne de tiles de notre map */
    mapX = map.startX / TILE_SIZE;

    /* Coordonnées de départ pour l'affichage de la map : permet
    de déterminer à quels coordonnées blitter la 1ère colonne de tiles au pixel près
    (par exemple, si la 1ère colonne n'est visible qu'en partie, on devra commencer à blitter
    hors écran, donc avoir des coordonnées négatives - d'où le -1). */
    x1 = (map.startX % TILE_SIZE) * -1;

    /* Calcul des coordonnées de la fin de la map : jusqu'où doit-on blitter ?
    Logiquement, on doit aller à x1 (départ) + SCREEN_WIDTH (la largeur de l'écran).
    Mais si on a commencé à blitter en dehors de l'écran la première colonne, il
    va falloir rajouter une autre colonne de tiles sinon on va avoir des pixels
    blancs. C'est ce que fait : x1 == 0 ? 0 : TILE_SIZE qu'on pourrait traduire par:
    if(x1 != 0)
        x2 = x1 + SCREEN_WIDTH + TILE_SIZE , mais forcément, c'est plus long ;)*/
    x2 = x1 + SCREEN_WIDTH + (x1 == 0 ? 0 : TILE_SIZE);

    /* On fait exactement pareil pour calculer y */
    mapY = map.startY / TILE_SIZE;
    y1 = (map.startY % TILE_SIZE) * -1;
    y2 = y1 + SCREEN_HEIGHT + (y1 == 0 ? 0 : TILE_SIZE);


    //On met en place un timer pour animer la map (chapitre 19)
    if(map.mapTimer <= 0)
    {
        if(map.tileSetNumber == 0)
        {
            map.tileSetNumber = 1;
            map.mapTimer = TIME_BETWEEN_2_FRAMES*3;
        }
        else
        {
            map.tileSetNumber = 0;
            map.mapTimer = TIME_BETWEEN_2_FRAMES*3;
        }

    }

    else
        map.mapTimer--;


    /* Dessine la carte en commençant par startX et startY */

    /* On dessine ligne par ligne en commençant par y1 (0) jusqu'à y2 (480)
    A chaque fois, on rajoute TILE_SIZE (donc 32), car on descend d'une ligne
    de tile (qui fait 32 pixels de hauteur) */
    for (y = y1; y < y2; y += TILE_SIZE)
    {
        /* A chaque début de ligne, on réinitialise mapX qui contient la colonne
        (0 au début puisqu'on ne scrolle pas) */
        mapX = map.startX / TILE_SIZE;

        /* A chaque colonne de tile, on dessine la bonne tile en allant
        de x = 0 à x = 640 */
        for (x = x1; x < x2; x += TILE_SIZE)
        {
            //Si la tile à dessiner n'est pas une tile vide
            if (map.tile[mapY][mapX] != 0)
            {
                /*On teste si c'est une tile monstre (tile numéro 10) */
                if (map.tile[mapY][mapX] == 10)
                {
                    //On initialise un monstre en envoyant les coordonnées de la tile
                    initializeMonster(mapX * TILE_SIZE, mapY * TILE_SIZE);
                    //Et on efface cette tile de notre tableau pour éviter un spawn de monstres
                    //infini !
                    map.tile[mapY][mapX] = 0;
                }
            }

            /* Suivant le numéro de notre tile, on découpe le tileset (a = le numéro
            de la tile */
            a = map.tile[mapY][mapX];

            /* Calcul pour obtenir son y (pour un tileset de 10 tiles
            par ligne, d'où le 10 */
            ysource = a / 10 * TILE_SIZE;
            /* Et son x */
            xsource = a % 10 * TILE_SIZE;

            /* Fonction qui blitte la bonne tile au bon endroit suivant le timer */
            if(map.tileSetNumber == 0)
                drawTile(map.tileSet, x, y, xsource, ysource);
            else
                drawTile(map.tileSetB, x, y, xsource, ysource);

            mapX++;
        }

        mapY++;
    }
}

         


    Voilà, donc suivant la valeur du timer, on passe du tileset 0 au 1 et on l'affiche.

    Notez que j'ai utilisé une tempo 3 fois plus lente pour l'animation de la map que pour celle des persos. C'est une question de goût. Vous pouvez la changer en modifiant TIME_BETWEEN_2_FRAMES * 3. Vous pouvez aussi créer une nouvelle def, si vous le souhaitez.


    Bon, il ne reste plus maintenant qu'à compiler et tadaaa ! Notre map s'anime et le ressort envoie notre lapin vers d'autres cieux !

    On commence vraiment à avoir un bon petit jeu quand même !


    Bon, allez, et si maintenant on rajoutait quelques sons pour faire mieux ? Cool, non ?
    Mais on verra ça dans le prochain chapitre !
 
   


   




 

 

 

Connexion

CoalaWeb Traffic

Today40
Yesterday238
This week963
This month441
Total1745340

3/05/24