Chapitre 28 :

Ajoutons des plateformes flottantes !



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

Dernière mise à jour:  17 octobre 2014
Difficulté :




Et voilà nos plateformes en action !



      Bon, passons maintenant aux plateformes flottantes !
      Comme vous n'aurez pas manqué de le remarquer, on se retrouve pour l'instant dans notre jeu avec des tiles de plateformes flottantes, mais elles ne fonctionnent pas !


       Pour rappel, ce sont ces tiles rouges avec une flèche sur la deuxième ligne de notre tileset.
      Et voilà, comment on va les faire fonctionner : quand le jeu va rencontrer une de ces tiles à afficher, il ne va pas l'afficher, mais il va l'effacer et initialiser une plateforme à sa place. Cela correspond à peu de choses près à ce qu'on a déjà fait pour créer nos monstres.   Ensuite, suivant le numéro de la tile (et donc suivant la flèche sur le petit dessin de la tile), on déplacera notre plateforme soit de gauche à droite, soit de haut en bas.

      Bien entendu, il va aussi falloir qu'on teste les collisions avec notre lapin-sauteur, si on ne veut pas qu'il passe au-travers !
      Bon, donc, avant de commencer, on va rajouter un fichier image pour notre plateforme. Le voilà :

plateforme.png

      Enregistrez-le simplement dans le dossier graphics de votre projet, sous le nom : plateforme.png.
      Et on est parti !


 I - Préparons nos nouvelles variables


         Là encore, on va commencer par modifier notre structure GameObject pour rajouter les variables dont on va avoir besoin pour nos plateformes :
          - beginX, beginY : les coordonnées de départ de notre plateforme (son point de spawn correspondant au coin haut gauche de notre tile),
          - type : va nous indiquer s'il s'agit de la plateforme de type 1 (qui va de gauche à droite) ou de type 2 (qui va de haut en bas)
          - player_dessus : on l'utilisera comme un booléen, pour savoir si le joueur se trouve sur la plateforme ou non.

  Ajoutons donc tout cela :

Fichier : structs.h

typedef struct GameObject
{
    //Sprite du héros
	SDL_Surface *sprite;

	//Points de vie/santé
	int life, invincibleTimer;

	/* Coordonnées du héros */
	int x, y;
	/* Largeur, hauteur du sprite */
	int h, w;

	//Checkpoint
	int checkpointActif;
	int respawnX, respawnY;

	/* Variables utiles pour l'animation */
	int frameNumber, frameTimer;
	int etat, direction;

	/* Variables utiles pour la gestion des collisions */
	int onGround, timerMort;
	float dirX, dirY;
	int saveX, saveY;

	//Variable pour le double saut
	int jump;

    //Nouvelles variables pour gérer les plateformes flottantes
    int beginX, beginY;
    int type, player_dessus;


} GameObject;




    Maintenant que notre structure est prête, il nous faut créer nos plateformes. Pour cela, on va faire comme pour les monstres et les shurikens et créer un nouveau tableau de GameObjects.
    Pour ne pas être limité, au niveau du level design, on va carrément créer un tableau de 50 cases, comme ça, on pourra créer jusqu'à 50 plateformes par niveau !
    Mais, bien sûr, vous pouvez moduler selon vos besoins.


Fichier : main.h

/* Déclaration des structures globales utilisées par le jeu */
Input input;
Gestion jeu;
Map map;
GameObject player;
GameObject monster[MONSTRES_MAX];
GameObject shuriken[10];
GameObject plateforme[50];

/* Déclaration de notre police de caractères */
TTF_Font *font;



    Revenons ensuite dans notre fichier structs.h, et rajoutons un compteur (comme pour les monstres) que nous nommerons nombrePlateformes, et un SDL_Surface qui contiendra le sprite de notre plateforme.
    N.B. : On aurait pu utiliser la variable sprite de notre GameObject, mais cela aurait créé plein de copies inutiles de notre plateforme en mémoire. Cela dit, si vous envisagez de créer des plateformes avec des sprites différents, cela peut être utile de l'utiliser, à vous d'adapter.

Fichier structs.h

typedef struct

typedef struct Gestion
{

    int level;
    SDL_Surface *screen;
    int nombreMonstres;
    int nombreFireballs;
    int nombrePlateformes;

    //HUD
	SDL_Surface *HUD_vie, *HUD_etoiles, *Shuriken_image, *plateforme;
    int vies, etoiles;

    //Sons
    Mix_Music  *musique;

    //Sounds Fx
    Mix_Chunk  *bumper_sound, *destroy_sound, *jump_sound, *star_sound;

    //Gestion des menus
    int onMenu, menuType, choice;


} Gestion;



    Voilà, créons maintenant plusieurs defines, qui vont nous aider à gérer nos plateformes : leur nombre max, les numéros des tiles qui les déclenchent, leur vitesse, ainsi que les directions Haut et Bas qu'elles peuvent prendre (notez que vous pouvez en créer qui vont aussi en diagonale  ).


Fichier defs.h

//Nombre max de monstres à l'écran
#define MONSTRES_MAX 50

//Nombre max de boules de feu à l'écran
#define FIREBALLS_MAX 3

//Nombre max de levels
#define LEVEL_MAX 2

/* Gestion des plateformes mobiles */
#define PLATEFORMES_MAX 50
#define PLATEFORMES_TILES_DEBUT 15
#define PLATEFORMES_TILES_FIN 16
#define PLATEFORM_SPEED 2
#define UP 3
#define DOWN 4

enum
{
BUMPER,
DESTROY,
JUMP,
STAR
};

enum
{
START,
PAUSE
};





La prog', c'est pas sorcier !

II - Créons des plateformes


    Bon, maintenant, il va nous falloir créer 2 nouveaux fichiers pour accueillir le code de gestion de nos plateformes.
    Créez donc 2 nouveaux fichiers dans votre projet, que vous nommerez plateformes.c et plateformes.h !

    Voilà, nous allons maintenant y écrire notre code. Le fichier plateformes.c contiendra ainsi 4 fonctions :
    - loadPlateforme() chargera simplement le sprite de notre plateforme, il faudra donc l'appeler dans l'initialisation de notre jeu (tout au début),
    - initPlateforme(int x, int y, int type) sera appelée depuis la fonction drawMap() et initialisera une nouvelle plateforme aux coordonnées passées, et avec le bon type (gauche/droite ou bas/haut),
    - doPlateforme() s'occupera de déplacer nos plateformes à la vitesse indiquée,
    - et enfin drawPlateforme() affichera nos plateformes.

Fichier : plateformes.c

#include "plateformes.h"


void loadPlateforme(void)
{
    //On charge l'image de la plateforme
    jeu.plateforme = loadImage("graphics/plateforme.png");

	/* Si on ne peut pas, on quitte, et on l'indique en erreur ;) */
	if (jeu.plateforme == NULL)
	{
		printf("Impossible de charger l'image de la plateforme : graphics/plateforme.png/n");
		exit(1);
	}
}


void initPlateforme(int x, int y, int type)
{

    /* On rajoute une plateforme à la liste des plateformes */
    jeu.nombrePlateformes++;

    //S'il y en a trop, on fait une erreur et on quitte
    if (jeu.nombrePlateformes > PLATEFORMES_MAX)
    {
        printf("Trop de plateformes initialisees !\nEnlevez-en ou changez leur nombre max.\n");
		exit(1);
    }

    //On remplit notre structure
    plateforme[jeu.nombrePlateformes].x = plateforme[jeu.nombrePlateformes].beginX = x;
    plateforme[jeu.nombrePlateformes].y = plateforme[jeu.nombrePlateformes].beginY = y;
    plateforme[jeu.nombrePlateformes].w = jeu.plateforme->w;
    plateforme[jeu.nombrePlateformes].h = jeu.plateforme->h;
    plateforme[jeu.nombrePlateformes].type = type;

    //Si elle est du type 2, elle monte, sinon elle va à droite
    if (plateforme[jeu.nombrePlateformes].type == 2)
        plateforme[jeu.nombrePlateformes].direction = UP;
    else
        plateforme[jeu.nombrePlateformes].direction = RIGHT;



}


void doPlateforme(void)
{

    int i;

    for ( i = 1; i < = jeu.nombrePlateformes; i++ )
    {
        /* Déplacement UP/DOWN (haut/bas) */
        if (plateforme[i].type == 2)
        {
            if (plateforme[i].direction == UP)
            {
                plateforme[i].y -= PLATEFORM_SPEED;
                 /* Si le joueur est dessus, on le déplace avec
                 pour éviter qu'il ne reste immobile et que la
                 plateforme se barre comme sur certains vieux
                 (mauvais) jeux...*/
                if (plateforme[i].player_dessus == 1)
                    player.y -= PLATEFORM_SPEED;
            }
            else
            {
                plateforme[i].y += PLATEFORM_SPEED;
                 /* Si le joueur est dessus, on le déplace avec */
                if (plateforme[i].player_dessus == 1)
                    player.y += PLATEFORM_SPEED;
            }

            if (plateforme[i].y > plateforme[i].beginY + 5 * TILE_SIZE)
               plateforme[i].direction = UP;


            if (plateforme[i].y < plateforme[i].beginY)
                plateforme[i].direction = DOWN;
        }

        /* Déplacement RIGHT/LEFT */
        else
        {
            if (plateforme[i].direction == RIGHT)
            {
                plateforme[i].x += PLATEFORM_SPEED;
                /* Si le joueur est dessus, on le déplace avec */
                if (plateforme[i].player_dessus == 1)
                    player.x += PLATEFORM_SPEED;
            }
            else
            {
                plateforme[i].x -= PLATEFORM_SPEED;
                /* Si le joueur est dessus, on le déplace avec */
                if (plateforme[i].player_dessus == 1)
                    player.x -= PLATEFORM_SPEED;
            }

            //Test : si la plateforme dépasse 5 tiles de longueur,
            //on lui fait faire demi-tour pour ne pas qu'elle
            //fasse toute la map ! :D
            if (plateforme[i].x > plateforme[i].beginX + 5 * TILE_SIZE)
               plateforme[i].direction = LEFT;

            if (plateforme[i].x < plateforme[i].beginX)
                plateforme[i].direction = RIGHT;
        }



    }



}


void drawPlateforme(void)
{

    int i;

    for ( i = 1; i < = jeu.nombrePlateformes; i++ )
    {
        drawImage(jeu.plateforme, plateforme[i].x - map.startX, plateforme[i].y - map.startY);
    }

}





    Voilà, tout est expliqué dans le code. Le seul point un peu délicat, c'est qu'il faut déplacer le joueur en même temps que la plateforme quand il est dessus, sinon, la plateforme va filer sans lui !
    Et bien sûr, voilà l'en-tête :

Fichier : plateformes.h

#include "structs.h"

extern Gestion jeu;
extern Map map;
extern struct GameObject plateforme[];
extern GameObject player;
extern void drawImage(SDL_Surface *image, int x, int y);
extern SDL_Surface *loadImage(char *name);



III - Initialisons nos plateformes


    Maintenant que nos plateformes existent et sont prêtes à être gérées, il nous faut encore les initialiser, sinon elles n'apparaîtront jamais !
    Commençons donc par charger notre sprite de plateforme, que vous avez bien copié sous le dossier /graphics. On va appeler notre fonction loadPlateforme() depuis la fonction loadGame() :


 Fichier : init.c

void loadGame(void)
{
/* Charge l'image du fond et le tileset */
map.background = loadImage("graphics/background.png");

//On commence au premier niveau
jeu.level = 1;
changeLevel();

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

/* 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");

//On charge le shuriken
jeu.Shuriken_image = loadImage("graphics/shuriken.png");

//On charge notre plateforme
loadPlateforme();

//On charge la musique
loadSong("music/RabidjaGo.mp3");

/* On charge les sounds Fx */
loadSound();

//On commence par le menu start
jeu.onMenu = 1;
jeu.menuType = START;

}



    Sans oublier, une fois encore, le cleanup(), à la fin :

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

	//Libère le shuriken
	if (jeu.Shuriken_image != NULL)
	{
		SDL_FreeSurface(jeu.Shuriken_image);
	}

	//Libère la plateforme
	if (jeu.plateforme != NULL)
	{
		SDL_FreeSurface(jeu.plateforme);
	}

	/* On libère la chanson */
    if ( jeu.musique != NULL )
        Mix_FreeMusic(jeu.musique);

    //On libère les sons
    freeSound();

	// Quitte SDL_Mixer et décharge la mémoire
    Mix_CloseAudio();
    Mix_Quit();

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

	/* Close SDL_TTF */
	TTF_Quit();

	/* Quitte la SDL */
	SDL_Quit();

}




    Et, bien sûr : l'en-tête :

 Fichier : init.h

#include "structs.h"
/* Prototypes des fonctions utilisées */
extern SDL_Surface *loadImage(char *name);
extern void loadMap(char *name);
extern void closeFont(TTF_Font *font);
extern TTF_Font *loadFont(char *, int);
extern void loadSong( char filename[200] );
extern void loadSound(void);
extern void freeSound(void);
extern void changeLevel(void);
extern void loadPlateforme(void);


extern Gestion jeu;
extern Map map;
extern TTF_Font *font;
extern GameObject player;
extern GameObject monster[MONSTRES_MAX];




    Voilà, maintenant, que c'est fait, allons dans la fonction drawMap() et initialisons nos premières plateformes !


 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] == TILE_MONSTRE)
                {
                    //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;
                }

                /*On teste si c'est une tile plateforme flottante (tiles numéros 15/16) */
                else if (map.tile[mapY][mapX] > = TILE_PLATEFORME_DEBUT
                         && map.tile[mapY][mapX] < = TILE_PLATEFORME_FIN)
                {
                    //On initialise une plateforme flottante
                    //Pour obtenir le type (dernier arg), on enlève le numéro de la 1ère tile plateforme
                    //(TILE_PLATEFORME_DEBUT) au numéro de la til en cours (map.tile[mapY][mapX])
                    //et on rajoute 1 pour que le premier type soit le 1 et pas le 0 ;)
                    initPlateforme(mapX * TILE_SIZE, mapY * TILE_SIZE, map.tile[mapY][mapX] - TILE_PLATEFORME_DEBUT +1);
                    //Et on efface cette tile de notre tableau pour éviter un spawn de plateformes
                    //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à, on rajoute simplement un système de détection comme pour les monstres et on initialise une plateforme.
    Là encore, on n'oublie pas notre en-tête :


 Fichier : map.h

#include "structs.h"
/* Prototypes des fonctions utilisées */
extern void drawImage(SDL_Surface *, int, int);
extern void drawTile(SDL_Surface *image, int destx, int desty, int
srcx, int srcy);
extern void initializeMonster(int x, int y);
extern void getItem(int itemNumber);
extern void playSoundFx(int type);
extern SDL_Surface *loadImage(char *name);
extern void changeLevel(void);
extern void initializePlayer(void);
extern void initPlateforme(int x, int y, int type);


extern Gestion jeu;
extern Map map;



    Voilà, si vous lancez le jeu maintenant, vous remarquerez que les tiles plateformes ont bel et bien disparu, mais pas de plateformes à l'horizon, mon capitaine !
    Normal, on n'a pas encore fait appel à nos fonctions doPlateforme() et drawPlateforme() ! 
    Alors, allons-y, et que ça saute !

    On va commencer par mettre à jour le main() :

 Fichier : main.c

int main(int argc, char *argv[])
{
	unsigned int frameLimit = SDL_GetTicks() + 16;
	int go, i;

   	/* Initialisation de la SDL */
	init("Aron");

	/* On initialise le joueur */
	initializePlayer();

	/* Chargement des ressources (graphismes, sons) */
	loadGame();

	/* Appelle la fonction cleanup à la fin du programme */
	atexit(cleanup);

	go = 1;


	/* Boucle infinie, principale, du jeu */

	while (go == 1)
	{
		/* On prend en compte les input (clavier, joystick... */
		getInput();

        //Si on n'est pas dans un menu
        if(jeu.onMenu == 0)
        {
            /* On met à jour le jeu */
            updatePlayer();
            doFireballs();
            updateMonsters();
            doPlateforme();
        }
        else
        {
            if(jeu.menuType == START)
                updateStartMenu();
            else if(jeu.menuType == PAUSE)
                updatePauseMenu();
        }


        //Si on n'est pas dans un menu
        if(jeu.onMenu == 0)
        {
            /* On affiche tout */
            draw();
        }
        else
        {
            if(jeu.menuType == START)
            {
                drawImage(map.background, 0, 0);
                drawStartMenu();
                SDL_Flip(jeu.screen);
                SDL_Delay(1);
            }
            else if(jeu.menuType == PAUSE)
            {
                drawImage(map.background, 0, 0);
                drawMap();
                drawAnimatedEntity(&player);
                for(i = 0 ; i < jeu.nombreMonstres ; i++)
                {
                    drawAnimatedEntity(&monster[i]);
                }
                drawPlateforme();
                drawFireballs();
                drawHud();
                drawPauseMenu();
                SDL_Flip(jeu.screen);
                SDL_Delay(1);
            }

        }


		/* Gestion des 60 fps (1000ms/60 = 16.6 -> 16 */
		delay(frameLimit);
		frameLimit = SDL_GetTicks() + 16;
	}

	/* Exit */
	exit(0);
}




    Et son en-tête :

 Fichier : main.h

#include "structs.h"
/* Prototypes des fonctions utilisées */
extern void init(char *);
extern void cleanup(void);
extern void getInput(void);
extern void draw(void);
extern void loadGame(void);
extern void delay(unsigned int frameLimit);
extern void updatePlayer(void);
extern void initializePlayer(void);
extern void updateMonsters(void);
extern void drawStartMenu(void);
extern void updateStartMenu(void);
extern void drawImage(SDL_Surface *image, int x, int y);
extern void drawPauseMenu(void);
extern void updatePauseMenu(void);
extern void drawMap(void);
extern void drawAnimatedEntity(GameObject *entity);
extern void drawHud(void);
extern void doFireballs(void);
extern void drawFireballs(void);
extern void doPlateforme(void);
extern void drawPlateforme(void);


/* Déclaration des structures globales utilisées par le jeu */
Input input;
Gestion jeu;
Map map;
GameObject player;
GameObject monster[MONSTRES_MAX];
GameObject shuriken[10];
GameObject plateforme[50];

/* Déclaration de notre police de caractères */
TTF_Font *font;




    Voilà, il nous manque encore la fonction draw(), qu'on doit mettre à jour :

 Fichier : draw.c

void draw(void)
{

    int i;

    /* Affiche le fond (background) aux coordonnées (0,0) */
    drawImage(map.background, 0, 0);

    /* Affiche la map de tiles */
    drawMap();

    /* Affiche le joueur */
    drawAnimatedEntity(&player);

    /* Affiche les monstres */
    for(i = 0 ; i < jeu.nombreMonstres ; i++)
    {
        drawAnimatedEntity(&monster[i]);
    }

    //Affiche les plateformes flottantes
    drawPlateforme();

    //Affiche les boules de feu
    drawFireballs();

    //On affiche le HUD par-dessus tout le reste
    drawHud();

    /* Affiche l'écran */
    SDL_Flip(jeu.screen);

    /* Delai */
    SDL_Delay(1);

}





    Ainsi que son en-tête :

 Fichier : map.h

#include "structs.h"
/* Prototypes des fonctions utilisées */
extern void drawMap(void);
extern void drawAnimatedEntity(GameObject *entity);
extern void drawHud(void);
extern void drawString(char *text, int x, int y, TTF_Font *font);
extern void drawFireballs(void);
extern void drawPlateforme(void);


/* Structures globales */
extern Gestion jeu;
extern Map map;
extern GameObject player;
extern GameObject monster[];
extern TTF_Font *font;



    Relancez le jeu et ça marche !!!!!!
    Mais, mais... Attendez !!! Le lapin passe au-travers !!! Du coup, ça sert à rien, ces bidules flottants !!!!!
   
    Du calme, c'est normal, car on n'a pas encore testé les collisions avec le joueur, donc forcément, il passe au-travers.
    Mais on va vite résoudre le problème, en allant dans notre big fonction : mapCollision() et en rajoutant le code suivant :

 Fichier : map.c

/* Test de collision avec la plateforme mobile */
int j;

if (jeu.nombrePlateformes > 0)
{
for ( j=1 ; j < = jeu.nombrePlateformes ; j++ )
{
	if ( entity->x + entity->w > = plateforme[j].x
		&& entity->x < = plateforme[j].x + plateforme[j].w
		&& entity->y + entity->h > = plateforme[j].y
		&& entity->y + entity->h < plateforme[j].y + 32 )
        {
            /* Place the player as close to the plateform as possible */
            entity->y = plateforme[j].y - entity->h;
            entity->dirY = 0;
            entity->onGround = 1;
            plateforme[j].player_dessus = 1;
        }
        else
            plateforme[j].player_dessus = 0;
	}
}



    Voilà la fonction complète, pour rappel :

 Fichier : map.c

void mapCollision(GameObject *entity)
{

	int i, x1, x2, y1, y2;

	/* D'abord, on place le joueur en l'air jusqu'à temps d'être sûr qu'il touche le sol */
	entity->onGround = 0;

	/* Ensuite, on va tester les mouvements horizontaux en premier (axe des X) */
	//On va se servir de i comme compteur pour notre boucle. En fait, on va découper notre sprite
	//en blocs de tiles pour voir quelles tiles il est susceptible de recouvrir.
	//On va donc commencer en donnant la valeur de Tile_Size à i pour qu'il teste la tile où se trouve
	//le x du joueur mais aussi la suivante SAUF dans le cas où notre sprite serait inférieur à
	//la taille d'une tile. Dans ce cas, on lui donnera la vraie valeur de la taille du sprite
	//Et on testera ensuite 2 fois la même tile. Mais comme ça notre code sera opérationnel quelle que
	//soit la taille de nos sprites !

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

    //On lance alors une boucle for infinie car on l'interrompra selon les résultats de nos calculs
	for (;;)
	{
		//On va calculer ici les coins de notre sprite à gauche et à droite pour voir quelle tile ils
		//touchent.
		x1 = (entity->x + entity->dirX) / TILE_SIZE;
		x2 = (entity->x + entity->dirX + entity->w - 1) / TILE_SIZE;

        //Même chose avec y, sauf qu'on va monter au fur et à mesure pour tester toute la hauteur
        //de notre sprite, grâce à notre fameuse variable i.
		y1 = (entity->y) / TILE_SIZE;
		y2 = (entity->y + i - 1) / TILE_SIZE;

        //De là, on va tester les mouvements initiés dans updatePlayer grâce aux vecteurs
        //dirX et dirY, tout en testant avant qu'on se situe bien dans les limites de l'écran.
		if (x1 > = 0 && x2 < MAX_MAP_X && y1 > = 0 && y2 < MAX_MAP_Y)
		{
			//Si on a un mouvement à droite
			if (entity->dirX > 0)
			{

                //Test des tiles Power-up
				if (map.tile[y1][x2] > = TILE_POWER_UP_DEBUT
                    && map.tile[y1][x2] < = TILE_POWER_UP_FIN)
				{
				    //On appelle la fonction getItem()
				    getItem(map.tile[y1][x2] - TILE_POWER_UP_DEBUT + 1);

				    //On remplace la tile power-up par une tile transparente
				    map.tile[y1][x2] = 0;
				}
				else if (map.tile[y2][x2] > = TILE_POWER_UP_DEBUT
                    && map.tile[y2][x2] < = TILE_POWER_UP_FIN)
				{
				    //On appelle la fonction getItem()
				    getItem(map.tile[y2][x2] - TILE_POWER_UP_DEBUT + 1);

				    //On remplace la tile power-up par une tile transparente
				    map.tile[y2][x2] = 0;
				}


				//Test de la tile checkpoint
				if (map.tile[y1][x2] == TILE_CHECKPOINT)
				{
					//On active le booléen checkpoint
					entity->checkpointActif = 1;

					//On enregistre les coordonnées
                    entity->respawnX = x2 * TILE_SIZE;
                    entity->respawnY= (y1 * TILE_SIZE) - entity->h;

                    //On change la tile
                    map.tile[y1][x2] += 1;
				}
				else if (map.tile[y2][x2] == TILE_CHECKPOINT)
				{
					//On active le booléen checkpoint
					entity->checkpointActif = 1;

					//On enregistre les coordonnées
                    entity->respawnX = x2 * TILE_SIZE;
                    entity->respawnY= (y2 * TILE_SIZE) - entity->h;

                    //On change la tile
                    map.tile[y2][x2] += 1;
				}


				//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
                if (map.tile[y1][x1] > = TILE_POWER_UP_DEBUT
                    && map.tile[y1][x1] < = TILE_POWER_UP_FIN)
				{
				    //On appelle la fonction getItem()
				    getItem(map.tile[y1][x1] - TILE_POWER_UP_DEBUT + 1);

				    //On remplace la tile power-up par une tile transparente
				    map.tile[y1][x1] = 0;
				}
				else if (map.tile[y2][x1] > = TILE_POWER_UP_DEBUT
                    && map.tile[y2][x1] < = TILE_POWER_UP_FIN)
				{
				    //On appelle la fonction getItem()
				    getItem(map.tile[y2][x1] - TILE_POWER_UP_DEBUT + 1);

				    //On remplace la tile power-up par une tile transparente
				    map.tile[y2][x1] = 0;
				}


				//Test de la tile checkpoint
				if (map.tile[y1][x1] == TILE_CHECKPOINT)
				{
					//On active le booléen checkpoint
					entity->checkpointActif = 1;

					//On enregistre les coordonnées
                    entity->respawnX = x1 * TILE_SIZE;
                    entity->respawnY= (y1 * TILE_SIZE) - entity->h;

                    //On change la tile
                    map.tile[y1][x1] += 1;
				}
				else if (map.tile[y2][x1] == TILE_CHECKPOINT)
				{
					//On active le booléen checkpoint
					entity->checkpointActif = 1;

					//On enregistre les coordonnées
                    entity->respawnX = x1 * TILE_SIZE;
                    entity->respawnY= (y2 * TILE_SIZE) - entity->h;

                    //On change la tile
                    map.tile[y2][x1] += 1;
				}


				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] > = TILE_POWER_UP_DEBUT
                    && map.tile[y2][x1] < = TILE_POWER_UP_FIN)
				{
				    //On appelle la fonction getItem()
				    getItem(map.tile[y2][x1] - TILE_POWER_UP_DEBUT + 1);

				    //On remplace la tile power-up par une tile transparente
				    map.tile[y2][x1] = 0;
				}
				else if (map.tile[y2][x2] > = TILE_POWER_UP_DEBUT
                    && map.tile[y2][x2] < = TILE_POWER_UP_FIN)
				{
				    //On appelle la fonction getItem()
				    getItem(map.tile[y2][x2] - TILE_POWER_UP_DEBUT + 1);

				    //On remplace la tile power-up par une tile transparente
				    map.tile[y2][x2] = 0;
				}


				//Test de la tile checkpoint
				if (map.tile[y2][x1] == TILE_CHECKPOINT)
				{
					//On active le booléen checkpoint
					entity->checkpointActif = 1;

					//On enregistre les coordonnées
                    entity->respawnX = x1 * TILE_SIZE;
                    entity->respawnY= (y2 * TILE_SIZE) - entity->h;

                    //On change la tile
                    map.tile[y2][x1] += 1;
				}
				else if (map.tile[y2][x2] == TILE_CHECKPOINT)
				{
					//On active le booléen checkpoint
					entity->checkpointActif = 1;

					//On enregistre les coordonnées
                    entity->respawnX = x2 * TILE_SIZE;
                    entity->respawnY= (y2 * TILE_SIZE) - entity->h;

                    //On change la tile
                    map.tile[y2][x2] += 1;
				}


                /* Gestion du ressort */
				if ((map.tile[y2][x1] == TILE_RESSORT ) || (map.tile[y2][x2] == TILE_RESSORT ))
				{
					entity->dirY = -20;
					//On indique au jeu qu'il a atterri pour réinitialiser le double saut
					entity->onGround = 1;
					playSoundFx(BUMPER);
				}
				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;
				}


				/* Test de collsion avec la plateforme mobile */
				int j;

				if (jeu.nombrePlateformes > 0)
				{
					for ( j=1 ; j < = jeu.nombrePlateformes ; j++ )
					{
                        if ( entity->x + entity->w > = plateforme[j].x
                            && entity->x < = plateforme[j].x + plateforme[j].w
                            && entity->y + entity->h > = plateforme[j].y
                            && entity->y + entity->h < plateforme[j].y + 32 )
                        {
                            /* Place the player as close to the plateform as possible */
                            entity->y = plateforme[j].y - entity->h;
                            entity->dirY = 0;
                            entity->onGround = 1;
                            plateforme[j].player_dessus = 1;
                        }
                        else
                            plateforme[j].player_dessus = 0;
					}
				}

			}

			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] > = TILE_POWER_UP_DEBUT
                    && map.tile[y1][x1] < = TILE_POWER_UP_FIN)
				{
				    //On appelle la fonction getItem()
				    getItem(map.tile[y1][x1] - TILE_POWER_UP_DEBUT + 1);

				    //On remplace la tile power-up par une tile transparente
				    map.tile[y1][x1] = 0;
				}
				if (map.tile[y1][x2] > = TILE_POWER_UP_DEBUT
                    && map.tile[y1][x2] < = TILE_POWER_UP_FIN)
				{
				    //On appelle la fonction getItem()
				    getItem(map.tile[y1][x2] - TILE_POWER_UP_DEBUT + 1);

				    //On remplace la tile power-up par une tile transparente
				    map.tile[y1][x2] = 0;
				}


				//Test de la tile checkpoint
				if (map.tile[y1][x1] == TILE_CHECKPOINT)
				{
					//On active le booléen checkpoint
					entity->checkpointActif = 1;

					//On enregistre les coordonnées
                    entity->respawnX = x1 * TILE_SIZE;
                    entity->respawnY= (y1 * TILE_SIZE) - entity->h;

                    //On change la tile
                    map.tile[y1][x1] += 1;
				}
				else if (map.tile[y1][x2] == TILE_CHECKPOINT)
				{
					//On active le booléen checkpoint
					entity->checkpointActif = 1;

					//On enregistre les coordonnées
                    entity->respawnX = x2 * TILE_SIZE;
                    entity->respawnY= (y1 * TILE_SIZE) - entity->h;

                    //On change la tile
                    map.tile[y1][x2] += 1;
				}


				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)
	{
	    //Si on touche le bord droit de l'écran, on passe au niveau sup
	    jeu.level++;
	    //Si on dépasse le niveau max, on annule et on limite le déplacement du joueur
		if(jeu.level > LEVEL_MAX)
		{
		    jeu.level = LEVEL_MAX;
		    entity->x = map.maxX - entity->w - 1;
		}
		//Sion, on passe au niveau sup, on charge la nouvelle map et on réinitialise le joueur
		else
		{
		    entity->checkpointActif = 0;
		    changeLevel();
		    initializePlayer();
		}

	}

    //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;
	}
}





   Sans oublier l'en-tête :

 Fichier : map.h

#include "structs.h"
/* Prototypes des fonctions utilisées */
extern void drawImage(SDL_Surface *, int, int);
extern void drawTile(SDL_Surface *image, int destx, int desty, int srcx, int srcy);
extern void initializeMonster(int x, int y);
extern void getItem(int itemNumber);
extern void playSoundFx(int type);
extern SDL_Surface *loadImage(char *name);
extern void changeLevel(void);
extern void initializePlayer(void);
extern void initPlateforme(int x, int y, int type);


extern Gestion jeu;
extern Map map;
extern GameObject plateforme[];




   On a terminé ?
   Presque, mais pas tout à fait : il faut encore qu'on remette le compteur de plateformes à zéro quand on meurt ou on change de niveaux, sinon on risque de se retrouver avec des plateformes partout !!!

   Pour cela, c'est très simple, il suffit d'aller dans la fonction initializePlayer() :


 Fichier : player.c

void initializePlayer(void)
{
    //PV à 3
    player.life = 3;

    //Timer d'invincibilité à 0
    player.invincibleTimer = 0;

    //Nombre de boules de feu / shurikens à 0
    jeu.nombreFireballs = 0;

    //Nombre de plateformes flottantes à 0
    jeu.nombrePlateformes = 0;

    /* Charge le sprite de notre héros */
    changeAnimation(&player, "graphics/walkright.png");

    //Indique l'état et la direction de notre héros
    player.direction = RIGHT;
    player.etat = IDLE;

    /* Coordonnées de démarrage/respawn de notre héros */
    if(player.checkpointActif == 1)
    {
        player.x = player.respawnX;
        player.y = player.respawnY;
    }
    else
    {
        player.x = 0;
        player.y = 0;
    }


    /* Hauteur et largeur de notre héros */
    player.w = PLAYER_WIDTH;
    player.h = PLAYER_HEIGTH;

    //Variables nécessaires au fonctionnement de la gestion des collisions
    player.timerMort = 0;
    player.onGround = 0;

    //Nombre de monstres
    jeu.nombreMonstres = 0;

}




    Eh voilà ! On compile, et cette fois, tout marche !!! Hourra !
    Bon, faut dire aussi que ce chapitre était un gros morceau ! Et il fallait tenir bon !

    Maintenant, je vous laisse éditer vos niveaux et peaufiner vos plateformes pour les mettre où vous voulez.

     A bientôt pour la suite !
 




Télécharger le projet complet !



 

Connexion

CoalaWeb Traffic

Today49
Yesterday238
This week972
This month450
Total1745349

3/05/24