Chapitre 29 :

Ajoutons une deuxième couche de tiles !



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

Dernière mise à jour:  1er novembre 2014
Difficulté :




Avec notre double couche de tiles, on peut maintenant passer derrière des éléments du décor ! Si c'est pas beau, ça !




      Notre jeu commence à prendre de plus en plus d'ampleur, et pour le rendre encore plus beau graphiquement, nous allons rajouter une seconde couche de tiles, qui viendra se superposer à la première !
      Mais pourquoi ?!?

     Comment ça, pourquoi ?! Mais, grâce à cette seconde couche, nos niveaux seront plus détaillés et on va enfin pouvoir mettre des éléments au 1er plan, pour faire passer notre lapin-ninja derrière ! Vous n'avez jamais trouvé ça bizarre, qu'il passe devant l'eau quand il tombe dedans ?

     Eh, bien maintenant, on aura vraiment l'impression qu'il prend un bain !

     Allez, c'est parti !       


 I - Comment ça marche ?


      Eh, bien, comme on veut superposer 2 couches de tiles, il va nous falloir créer un deuxième tableau de tiles. En plus de map.tile[Y][X], on va donc créer map.tile2[Y][X] qui s'affichera par dessus.

    Comme ces tiles ne seront que du décor, on ne va pas s'amuser à tester les tiles spéciales. Celles-ci devront IMPERATIVEMENT être placées dans la 1ère couche si on veut qu'elles soit initialisées par le moteur. On va ainsi gagner un peu de ressources CPU .

    Maintenant, pour charger et sauvegarder nos nouvelles maps, il va nous falloir un nouveau format de fichier. Le début restera le même, sauf qu'à la fin du premier tableau de tiles on rajoutera nos deuxième tableau. Il va donc falloir modifier notre fonction de chargement et de sauvegarde (dans le level editor).

    En parlant de level editor, il va bien sûr falloir le modifier en même temps que le jeu, et donc aussi modifier les fonctions qui chargent / sauvegardent et réinitialisent les niveaux !

    On va donc commencer par mettre à jour le level editor et nos maps, avant de passer au moteur du jeu. C'est ce qui semble le plus logique .

Allez, hop ! Un peu de ménage dans le code !


II - Modifions notre level editor


    Commençons donc par créer notre deuxième tableau de tiles. Pour cela, modifions notre structure Map dans le fichier structs.h    

Fichier : structs.h

 

typedef struct Map
{

	SDL_Surface *background, *tileSet;

	/* 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];

	//Deuxième couche de tiles
	int tile2[MAX_MAP_Y][MAX_MAP_X];

} Map;


     Eh, voilà ! Notre deuxième couche existe maintenant !

     On va maintenant s'occuper de son affichage ! Pour cela, on va simplement effectuer une seconde boucle, comme la première, mais cette fois-ci, on va afficher tile2 !

 

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


	/* 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)
		{

            /* 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 */
		    drawTile(map.tileSet, x, y, xsource, ysource);

            mapX++;
		}

		mapY++;
	}
	
	//On reinitialise nos valeurs de départ	
	mapX = map.startX / TILE_SIZE;
	mapY = map.startY / TILE_SIZE;

	//Deuxième couche de tiles ;)
	for (y = y1; y < y2; y += TILE_SIZE)
	{
		mapX = map.startX / TILE_SIZE;

		for (x = x1; x < x2; x += TILE_SIZE)
		{
            a = map.tile2[mapY][mapX];
            ysource = a / 10 * TILE_SIZE;
            xsource = a % 10 * TILE_SIZE;
		    drawTile(map.tileSet, x, y, xsource, ysource);
            mapX++;
		}
		mapY++;
	}

	/* On affiche la tile sélectionnée à côté du curseur */
	ysource = cursor.tileID / 10 * TILE_SIZE;
    xsource = cursor.tileID % 10 * TILE_SIZE;
    drawTile(map.tileSet, cursor.x, cursor.y, xsource, ysource);


}

     Voilà, maintenant, l'affichage devrait être bon ! Plus qu'à modifier les fonctions de chargement / sauvegarde et réinitialisation ! Mais, pas besoin d'aller les chercher bien loin, elles se trouvent toutes dans le même fichier map.c !


Fichier : map.c


 
void loadMap(char *name)
{
	int x, y;
	FILE *fp;

	fp = fopen(name, "rb");

	/* Si on ne peut pas ouvrir le fichier, on quitte */

	if (fp == NULL)
	{
		printf("Failed to open map %s\n", name);

		exit(1);
	}

	/* Lit les données du fichier dans la map */

	map.maxX = map.maxY = 0;

	for (y = 0; y < MAX_MAP_Y; y++)
	{
		for (x = 0; x < MAX_MAP_X; x++)
		{
			/* On lit le numéro de la tile et on le copie dans notre tableau */
			fscanf(fp, "%d", &map.tile[y][x]);

			/* Permet de déterminer la taille de la map (voir plus bas) */
			if (map.tile[y][x] > 0)
			{
				if (x > map.maxX)
				{
					map.maxX = x;
				}

				if (y > map.maxY)
				{
					map.maxY = y;
				}
			}
		}
	}

	//Deuxième couche de tiles
	for (y = 0; y < MAX_MAP_Y; y++)
	{
		for (x = 0; x < MAX_MAP_X; x++)
		{
			/* On lit le numéro de la tile et on le copie dans notre tableau */
			fscanf(fp, "%d", &map.tile2[y][x]);
		}
	}


	/* On change ces coordonnées pour qu'on puisse scroller et éditer la map
	au maximum */

	map.maxX = MAX_MAP_X * TILE_SIZE;
	map.maxY = MAX_MAP_Y * TILE_SIZE;


	/* Remet à 0 les coordonnées de départ de la map */

	map.startX = map.startY = 0;


	/* Et on referme le fichier */

	fclose(fp);

}


Les fonctions saveMap() et reinitMap() ne seront pas plus difficiles à modifier. Il s'agit simplement de rajouter une boucle comme la première .

Voilà quand même le code :

 

Fichier : map.c

 

 

void saveMap(char *name)
{
	int x, y;
	FILE *fp;

	fp = fopen(name, "wb");

	/* Si on ne peut pas charger la map, on quitte */

	if (fp == NULL)
	{
		printf("Failed to open map %s\n", name);

		exit(1);
	}


    /* Sauvegarde la map : couche 1 */
	for (y = 0; y < MAX_MAP_Y; y++)
	{
		for (x = 0; x < MAX_MAP_X; x++)
		{
			fprintf(fp, "%d ", map.tile[y][x]);
		}

		fprintf(fp, "\n");
	}

	/* Sauvegarde la map : couche 2 */
	for (y = 0; y < MAX_MAP_Y; y++)
	{
		for (x = 0; x < MAX_MAP_X; x++)
		{
			fprintf(fp, "%d ", map.tile2[y][x]);
		}

		fprintf(fp, "\n");
	}


	/* On referme le fichier */

	fclose(fp);
}


void reinitMap(char *name)
{
	int x, y;
	FILE *fp;

	fp = fopen(name, "wb+");

	/* Si on ne peut pas charger la map, on quitte */

	if (fp == NULL)
	{
		printf("Failed to open map %s\n", name);

		exit(1);
	}


	/* Remplit la map de 0 */
	for (y = 0; y < MAX_MAP_Y; y++)
    {
        for (x = 0; x < MAX_MAP_X; x++)
        {
            fprintf(fp, "0 ");
        }

        fprintf(fp, "\n");
    }

    for (y = 0; y < MAX_MAP_Y; y++)
    {
        for (x = 0; x < MAX_MAP_X; x++)
        {
            fprintf(fp, "0 ");
        }

        fprintf(fp, "\n");
    }


	/* On referme le fichier */

	fclose(fp);
}




     A noter, que pour la fonction reinitMap(), au lieu de faire du copier/coller, on aurait pu imbriquer les deux boucles dans une troisième (qui aurait fait 2 tours), mais j'ai pas voulu vous emmêler l'esprit !

     Bon, maintenant que c'est fait, vous pouvez lancer le programme. Passez alors en revue tous vos niveaux et sauvegardez-les (touche S), le fichier va être alors mis à jour au nouveau format ! Si vous voulez le vérifier, il vous suffit de regarder la taille de vos fichiers qui va passer du simple au double : de 237ko à 471ko !

     Voilà, c'est bon...

     Mais, attendez, y'a un problème, là !!! Comment je fais, moi, pour éditer la seconde couche ?! Cela sert à rien, du coup !!!

     Eh, minute, j'allais y venir ! La mise à jour de notre éditeur n'est pas finie. Il va en effet maintenant falloir rajouter une fonction qui va nous permettre d'afficher/modifier soit la couche 1, soit la couche 2 et aussi d'afficher le résultat final (les deux couches l'une par dessus l'autre pour voir ce que ça donne - mais sans qu'on ne puisse rien modifier dans cet état-là ).

     Alors, allons-y ! Et commençons par attribuer une nouvelle touche qui va nous permettre de passer d'une couche à l'autre : que dites-vous de la touche F3 ?

 

    Pour cela, retournons d'abord dans notre fichier structs.h et modifions la structure input comme suit :

 


Fichier : structs.h

 

typedef struct Input
{
	int left, right, up, down, add, remove;
	int previous, next, load, save, copy, reinit;
	int mouseX, mouseY;
	int leveldown, levelup;
	int layerDrawn, changeLayer;

} Input;

 

   Rajoutons ensuite la gestion de notre touche F3 dans la fonction getInput() :


Fichier : input.c

void getInput(void)
{
	SDL_Event event;

	/* Keymapping : gère les appuis sur les touches et les enregistre
	dans la structure input */

	while (SDL_PollEvent(&event))
	{
		switch (event.type)
		{

			case SDL_QUIT:
                exit(0);
			break;

			case SDL_KEYDOWN:
				switch (event.key.keysym.sym)
				{
					case SDLK_ESCAPE:
						exit(0);
					break;

					case SDLK_LEFT:
						input.left = 1;
					break;

					case SDLK_RIGHT:
						input.right = 1;
					break;

					case SDLK_UP:
						input.up = 1;
					break;

					case SDLK_DOWN:
						input.down = 1;
					break;

					/* La touche S sauvegardera */
					case SDLK_s:
						input.save = 1;
					break;

					/* La touche L chargera la map */
					case SDLK_l:
						input.load = 1;
					break;

					/* La touche DEL/Suppr réinitialisera la map */
					case SDLK_DELETE:
						input.reinit = 1;
					break;

					case SDLK_F1:
						input.levelup = 1;
					break;

					case SDLK_F2:
						input.leveldown = 1;
					break;
					
					case SDLK_F3:
						input.changeLayer = 1;
					break;

					default:
					break;
				}
			break;

    On va ensuite initialiser notre variable input.layerDrawn à 3, ce qui correspondra à l"affichage des deux layers l'une sur l'autre (pour voir le résultat in-game). Et on retiendra les états suivants :

        -   input.layerDrawn = 1 -> affichage de la layer (/du calque) 1.
        -   input.layerDrawn = 2 -> affichage de la layer (/du calque) 2.
        -   input.layerDrawn = 3 -> affichage des deux layers (/calques) 1 et 2.

    Pour cela, on va dans le main et on rajoute simplement :


Fichier : main.c


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

   	/* Initialisation de la SDL */

	init("Level editor");
	
	/* Affichage des 2 layers */
	input.layerDrawn = 3;

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



 Voilà, il ne nous reste plus qu'à gérer l'appui sur la touche F3 dans la ction update() et à changer la valeur de input.LayerDrawn de 1 à 3 :

Fichier : input.c


void update(void)
{

    /* Pour l'affichage du curseur (indiquant la tile à copier, mais on reviendra dessus
    dans la fonction draw() ), on récupère les coordonnées de la souris */

    cursor.x = input.mouseX;
	cursor.y = input.mouseY;

	char file[120];


    /* Gestion de notre scrolling du chapitre précédent */
 	if (input.left == 1)
	{
		map.startX -= TILE_SIZE;

		if (map.startX < 0)
		{
			map.startX = 0;
		}
	}

	else if (input.right == 1)
	{
		map.startX += TILE_SIZE;

		if (map.startX + SCREEN_WIDTH > = map.maxX)
		{
			map.startX = map.maxX - SCREEN_WIDTH;
		}
	}

	if (input.up == 1)
	{
		map.startY -= TILE_SIZE;

		if (map.startY < 0)
		{
			map.startY = 0;
		}
	}

	else if (input.down == 1)
	{
		map.startY += TILE_SIZE;

		if (map.startY + SCREEN_HEIGHT > = map.maxY)
		{
			map.startY = map.maxY - SCREEN_HEIGHT;
		}
	}

	//Gestion du passage d'une map à l'autre
	if (input.levelup == 1)
	{
		jeu.level++;
		if (jeu.level > LEVEL_MAX )
            jeu.level = 1;
        loadGame();

		input.levelup = 0;
	}

	if (input.leveldown == 1)
	{
		jeu.level--;
		if (jeu.level < 1 )
            jeu.level = LEVEL_MAX;
        loadGame();

		input.leveldown = 0;
	}

    /* Gestion de la copie de tile :
    on retrouve les coordonnées de la tile pointée par la souris et on remplace
    sa valeur par celle de la tile sélectionnée dans le curseur */

    if (input.add == 1)
	{

		map.tile[(map.startY + cursor.y) / TILE_SIZE][(map.startX + cursor.x) / TILE_SIZE] = cursor.tileID;

	}

	/* Même fonctionnement, sauf qu'on réinitialise la tile pointée en lui donnant
	la valeur BLANK_TILE (0 par défaut) */

	else if (input.remove == 1)
	{

		map.tile[(map.startY + cursor.y) / TILE_SIZE][(map.startX + cursor.x) / TILE_SIZE] = BLANK_TILE;

		cursor.tileID = 0;
	}

    /* On fait défiler les tiles dans un sens ou dans l'autre */

	if (input.previous == 1)
	{
		cursor.tileID--;

        if (cursor.tileID < 0)
        {
            cursor.tileID = MAX_TILES - 1;
        }
        else if (cursor.tileID > MAX_TILES)
        {
            cursor.tileID = 0;
        }


		input.previous = 0;
	}

	if (input.next == 1)
	{
		cursor.tileID++;

        if (cursor.tileID < 0)
        {
            cursor.tileID = MAX_TILES - 1;
        }
        else if (cursor.tileID > MAX_TILES)
        {
            cursor.tileID = 0;
        }

		input.next = 0;
	}

    /* On copie le numéro de la tile pointée dans le curseur pour qu'il affiche et colle
    désormais cette tile */

    if (input.copy == 1)
	{
		cursor.tileID = map.tile[(map.startY + cursor.y) / TILE_SIZE] [(map.startX + cursor.x) / TILE_SIZE];

		input.copy = 0;
	}

    /* Pour réinitialiser la map, on appelle la fonction reinitMap puis on recharge la map */

    if (input.reinit == 1)
	{
	    sprintf(file, "map/map%d.txt", jeu.level);
		reinitMap(file);
		loadMap(file);

		input.reinit = 0;
	}

    /* Sauvegarde la map (cf. plus loin) */

	if (input.save == 1)
	{
		sprintf(file, "map/map%d.txt", jeu.level);
		saveMap(file);

		input.save = 0;
	}

    /* Charge la map */

	if (input.load == 1)
	{
        sprintf(file, "map/map%d.txt", jeu.level);
		loadMap(file);

		input.load = 0;
	}
	
	if (input.changeLayer == 1)
	{
		input.layerDrawn++;
		if ( input.layerDrawn > 3 )
            input.layerDrawn = 1;      

		input.changeLayer = 0;
	}

	/* On rajoute un délai entre 2 tours de boucle pour que le scrolling soit moins rapide */

	if (input.left == 1 || input.right == 1 || input.up == 1 || input.down == 1)
	{
		SDL_Delay(30);
	}

}





 Bon, maintenant, il va falloir de nouveau modifier notre fonction drawMap() pour qu'elle affiche soit la première layer, soit la seconde, soit les deux, en fonction de la valeur de input.layerDrawn.
 Ce n'est pas bien difficile, il ne faut simplement pas oublier de réinitialiser mapX et mapY entre les deux boucles, si on veut pouvoir afficher correctement les 2 couches l'une par-dessus l'autre en même temps.


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

    //Si input.layerDrawn vaut 1, ou 3, on affiche la layer 1 :
    if(input.layerDrawn == 1 || input.layerDrawn == 3)
    {
        /* 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)
            {

                /* 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 */
                drawTile(map.tileSet, x, y, xsource, ysource);

                mapX++;
            }

            mapY++;
        }
    }
	

    mapX = map.startX / TILE_SIZE;
	mapY = map.startY / TILE_SIZE;

	
    //Si input.layerDrawn vaut 2, ou 3, on affiche la layer 2 :
    if(input.layerDrawn == 2 || input.layerDrawn == 3)
    {
        //Deuxième couche de tiles ;)
        for (y = y1; y < y2; y += TILE_SIZE)
        {
            mapX = map.startX / TILE_SIZE;

            for (x = x1; x < x2; x += TILE_SIZE)
            {
                a = map.tile2[mapY][mapX];
                ysource = a / 10 * TILE_SIZE;
                xsource = a % 10 * TILE_SIZE;
                drawTile(map.tileSet, x, y, xsource, ysource);
                mapX++;
            }
            mapY++;
        }
		
	}

    /* On affiche la tile sélectionnée à côté du curseur */
    ysource = cursor.tileID / 10 * TILE_SIZE;
    xsource = cursor.tileID % 10 * TILE_SIZE;
    drawTile(map.tileSet, cursor.x, cursor.y, xsource, ysource);
    


}



 Voilà, n'oublions pas maintenant de rajouter la déclaration de notre struct input dans l'en-tête map.h :


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 Gestion jeu;
extern Map map;
extern Cursor cursor;
extern Input input;



 Si vous compilez maintenant, vous verrez que ça marche : on passe bien d'une layer à l'autre puis aux deux, mais comme tout est pour l'instant sur la layer 1, ça ne va pas changer grand chose...
Il va donc falloir d'une part, gérer l'édition des tiles sur la bonne couche (1 ou 2, et pas simplement sur la couche 1 comme c'est encore le cas ), mais aussi, d'autre part, indiquer sur quelle layer on se trouve, pour qu'on puisse s'y retrouver plus facilement !

 Commençons par faire évoluer notre code pour qu'il gère la copie de tiles sur les deux couches. Pour cela, il nous siginfie de modifier un peu nos fonctions du fichier input.c :


Fichier : input.c - Fonction update()


    /* Gestion de la copie de tile :
    on retrouve les coordonnées de la tile pointée par la souris et on remplace
    sa valeur par celle de la tile sélectionnée dans le curseur */

    if (input.add == 1)
	{
	    
	    if ( input.layerDrawn == 1 )
		map.tile[(map.startY + cursor.y) / TILE_SIZE][(map.startX + cursor.x) / TILE_SIZE] = cursor.tileID;
        else if ( input.layerDrawn == 2 )
		map.tile2[(map.startY + cursor.y) / TILE_SIZE][(map.startX + cursor.x) / TILE_SIZE] = cursor.tileID;
        
	}
	
	/* Même fonctionnement, sauf qu'on réinitialise la tile pointée en lui donnant
	la valeur BLANK_TILE (0 par défaut) */

	else if (input.remove == 1)
	{

		if ( input.layerDrawn == 1 )
		map.tile[(map.startY + cursor.y) / TILE_SIZE][(map.startX + cursor.x) / TILE_SIZE] = BLANK_TILE;
        else if ( input.layerDrawn == 2 )
		map.tile2[(map.startY + cursor.y) / TILE_SIZE][(map.startX + cursor.x) / TILE_SIZE] = BLANK_TILE;

		cursor.tileID = 0;
	}
	


    /* On copie le numéro de la tile pointée dans le curseur pour qu'il affiche et colle
    désormais cette tile */

    if (input.copy == 1)
	{
	    if ( input.layerDrawn == 1 )
		cursor.tileID = map.tile[(map.startY + cursor.y) / TILE_SIZE] [(map.startX + cursor.x) / TILE_SIZE];
        else if ( input.layerDrawn == 2 )
		cursor.tileID = map.tile2[(map.startY + cursor.y) / TILE_SIZE] [(map.startX + cursor.x) / TILE_SIZE];
		
		input.copy = 0;
	}
	



 Voilà, maintenant ça fonctionne, et on peut déjà s'amuser à modifier les tiles sur les deux couches différentes. Cependant, on édite un peu nos niveaux à l'aveuglette, et il va donc falloir installer la lib SDL_TTF, pour écrire du texte, comme on l'a déjà fait pour le jeu lui-même .

 Copions donc les libs : libfreetype-3.dll, SDL_ttf.dll et zlib1.dll dans le dossier du level editor.

 Ensuite, il ne reste plus qu'à configurer le projet. Allez dans le menu Project / Build Options. Là, dans l'onglet Linker, cliquez sur Add et sélectionnez le fichier SDL_ttf.lib (suivant là où vous l'avez mis). Cliquez ensuite sur OK (sans oublier de répéter l'opération pour debug et release, suivant ce que vous souhaitez compiler, bien sûr).

 Et voilà, on va maintenant modifier notre fichier defs.h pour que le compilateur puisse trouver le fichier en-tête contenant les prototypes des fonctions de SDL_TTF, en rajoutant :


Fichier : defs.h


   #include < SDL / SDL_ttf.h >




 On va ensuite, recopiez la font de notre jeu dans le répertoire du level editor (le répertoire "font" ). Et on va créer une variable pour la contenir :



Fichier : main.h


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




 On va ensuite rajouter l'initialisation de notre lib après celle de la SDL puis on va charger notre police de caractères.

Fichier : init.c - Fonction init()


   
    /* Initialise SDL_TTF */
    if (TTF_Init() < 0)
    {
        printf("Couldn't initialize SDL TTF: %s\n", SDL_GetError());

        exit(1);
    }
	
    /* Charge la police en 32 points (taille) */
    font = loadFont("font/GenBasB.ttf", 32);




 Sans oublier de faire le ménage à la fin en libérant la mémoire allouée à notre police et en fermant la lib !


Fichier : init.c - Fonction cleanup()


   
void cleanup()
{

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

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


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

	if (map.tileSet != NULL)
	{
		SDL_FreeSurface(map.tileSet);
	}
	
	/* Close the font */
    closeFont(font);


    /* Close SDL_TTF */
    TTF_Quit();

	/* Quitte la SDL */
	SDL_Quit();

}




 Sans oublier l'en-tête comme d'habitude :

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 Gestion jeu;
extern Map map;
extern TTF_Font *font;





 Créons maintenant 2 fichiers : font.c et font.h comme pour le jeu :

Fichier : font.c


   
#include "font.h"

 TTF_Font *loadFont(char *name, int size)
 {
     /* Utilise SDL_TTF pour charger la police à la taille désirée */

     TTF_Font *font = TTF_OpenFont(name, size);

     if (font == NULL)
     {
         printf("Failed to open Font %s: %s\n", name, TTF_GetError());

         exit(1);
     }

     return font;
 }


 void closeFont(TTF_Font *font)
 {
     /* Ferme la police quand on n'en a plus besoin (avant de quitter) */

     if (font != NULL)
     {
         TTF_CloseFont(font);
     }
 }


 void drawString(char *text, int x, int y, TTF_Font *font)
 {
     SDL_Rect dest;
     SDL_Surface *surface;
     SDL_Color foregroundColor;

     /* On choisit d'écrire le texte en noir */
     foregroundColor.r = 0;
     foregroundColor.g = 0;
     foregroundColor.b = 0;


     /* On utilise SDL_TTF pour générer une SDL_Surface à partir d'une chaîne de caractères (string) */

     surface = TTF_RenderUTF8_Blended(font, text, foregroundColor);


     if (surface == NULL)
     {
         printf("Couldn't create String %s: %s\n", text, SDL_GetError());

         return;
     }

     /* On blitte cette SDL_Surface à l'écran */

     dest.x = x;
     dest.y = y;
     dest.w = surface->w;
     dest.h = surface->h;

     SDL_BlitSurface(surface, NULL, jeu.screen, &dest);

     /* On libère la SDL_Surface temporaire (pour éviter les fuites de mémoire - cf. chapitre dédié) */
     SDL_FreeSurface(surface);
 }
          




Fichier : font.h


   
    #include "structs.h"

    extern Gestion jeu;
    extern TTF_Font *font;   




 Voilà, SDL_TTF est enfin installé ! Youpi !!!
 Plus qu'à afficher la layer en cours en haut à droite de notre écran, et on sera bon pour le level editor !!!


 Pour cela, on va rajouter ce morceau de code dans le fichier draw.c :

Fichier : draw.c


void draw(void)
{
    char text[200];

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

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

    /* Affiche le numero de la layer (couche) affichée */
    if(input.layerDrawn == 3)
        sprintf(text, "Layer 1+2");
    else
        sprintf(text, "Layer %d", input.layerDrawn);
		
    drawString(text, 475, 10, font);


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

    /* Delai */

    SDL_Delay(1);

}




 Sans oublier l'en-tête :

Fichier : draw.h


#include "structs.h"

/* Prototypes des fonctions utilisées */
extern void drawMap(void);
extern void drawString(char *text, int x, int y, TTF_Font *font);

/* Structures globales */
extern Gestion jeu;
extern Map map;
extern Input input;
extern TTF_Font *font;




 Ouf ! On y est ! Notre éditeur est enfin prêt !
On va maintenant pouvoir passer à l'édition de notre jeu. Mais, rassurez-vous, à côté, ce sera beaucoup plus simple !



 III - Mettons le jeu à jour


 Fermons maintenant notre level editor, copions nos nouveaux fichiers de map dans le jeu et ouvrons le projet !
 Voilà, maintenant, il va simplement falloir mettre à jour nos fonctions loadMap() et drawMap() en introduisant la deuxième couche de tiles, comme on a fait au tout début pour notre level editor :

 Commençons donc par créer notre deuxième tableau de tiles. Pour cela, modifions notre structure Map dans le fichier structs.h :

Fichier : structs.h

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];
	
	//Deuxième couche de tiles
    int tile2[MAX_MAP_Y][MAX_MAP_X];

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

} Map;


     Eh, voilà ! Notre deuxième couche existe !

     Voilà, maintenant, plus qu'à modifier la fonction de chargement de la map :


Fichier : map.c

 

 
void loadMap(char *name)
{
	int x, y;
	FILE *fp;

	fp = fopen(name, "rb");

	/* Si on ne peut pas ouvrir le fichier, on quitte */
	if (fp == NULL)
	{
		printf("Failed to open map %s\n", name);
		exit(1);
	}

	/* Lit les données du fichier dans la map */
	map.maxX = map.maxY = 0;

	for (y = 0; y < MAX_MAP_Y; y++)
	{
		for (x = 0; x < MAX_MAP_X; x++)
		{
			/* On lit le numéro de la tile et on le copie dans notre tableau */
			fscanf(fp, "%d", &map.tile[y][x]);

			/* Permet de déterminer la taille de la map (voir plus bas) */
			if (map.tile[y][x] > 0)
			{
				if (x > map.maxX)
				{
					map.maxX = x;
				}

				if (y > map.maxY)
				{
					map.maxY = y;
				}
			}
		}
	}

	//Deuxième couche de tiles
	for (y = 0; y < MAX_MAP_Y; y++)
	{
		for (x = 0; x < MAX_MAP_X; x++)
		{
			/* On lit le numéro de la tile et on le copie dans notre tableau */
			fscanf(fp, "%d", &map.tile2[y][x]);
		}
	}

	/* maxX et maxY sont les coordonnées de fin de la map.
	On les trouve dès qu'il n'y a plus que des zéros à la suite.
	Comme ça, on peut faire des maps de tailles différentes avec la même
	structure de fichier. */
	map.maxX = (map.maxX + 1) * TILE_SIZE;
	map.maxY = (map.maxY + 1) * TILE_SIZE;

	/* Remet à 0 les coordonnées de départ de la map */
	map.startX = map.startY = 0;

	/* Et on referme le fichier */
	fclose(fp);

}




     On va maintenant s'occuper de l'affichage de la map ! Pour cela, on va simplement effectuer une seconde boucle, comme la première, mais cette fois-ci, on va afficher tile2 !
Mais attention, tout n'est pas si simple !
On va désormais envoyer un "int layer" à notre fonction, pour lui dire quelle couche afficher. En effet, on ne veut pas que les 2 couches s'affichent à la suite, sinon, ça ne changerait rien (vous pouvez essayer ) ! On va donc appeler notre fonction 2 fois : une fois avant de dessiner les sprites et une fois après : comme ça, on aura l'impression qu'ils passent derrière le foreground !

 

Fichier : map.c

 

void drawMap(int layer)
{
    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) */
    if(layer == 1)
    {
        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++;
        }
    }

    else
    {
        //Deuxième couche de tiles ;)
        for (y = y1; y < y2; y += TILE_SIZE)
        {
            mapX = map.startX / TILE_SIZE;

            for (x = x1; x < x2; x += TILE_SIZE)
            {
                //Si la tile à dessiner n'est pas une tile vide
                if (map.tile2[mapY][mapX] != 0)
                {
                    /*On teste si c'est une tile monstre (tile numéro 10) */
                    if (map.tile2[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.tile2[mapY][mapX] = 0;
                    }

                    /*On teste si c'est une tile plateforme flottante (tiles numéros 15/16) */
                    else if (map.tile2[mapY][mapX] > = TILE_PLATEFORME_DEBUT
                             && map.tile2[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.tile2[mapY][mapX] - TILE_PLATEFORME_DEBUT +1);
                        //Et on efface cette tile de notre tableau pour éviter un spawn de plateformes
                        //infini !
                        map.tile2[mapY][mapX] = 0;
                    }
                }

                /* Suivant le numéro de notre tile, on découpe le tileset (a = le numéro
                de la tile */
                a = map.tile2[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à, plus qu'à modifier les appels à notre fonction drawMap() : dans le main (pour le menu pause) et dans le fichier draw.c :

Fichier : main.c


   
    else if(jeu.menuType == PAUSE)
            {
                drawImage(map.background, 0, 0);
                drawMap(1);
                drawAnimatedEntity(&player);
                for(i = 0 ; i < jeu.nombreMonstres ; i++)
                {
                    drawAnimatedEntity(&monster[i]);
                }
                drawPlateforme();
                drawFireballs();
                drawMap(2);
                drawHud();
                drawPauseMenu();
                SDL_Flip(jeu.screen);
                SDL_Delay(1);
            }



Fichier : main.h - changer :


   
    extern void drawMap(int);



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 : layer 1 */
    drawMap(1);

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

    /* Affiche la map de tiles : layer 2 */
    drawMap(2);

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

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

    /* Delai */
    SDL_Delay(1);

}



Fichier : draw.h - changer :


   
    extern void drawMap(int);




 Eh voilà, plus qu'à compiler et : tadaaaa ! Notre lapin ninja passe derrière les éléments du décor !
Ouf ! Ce chapitre n'était pas une mince affaire ! Mais voilà une bonne chose de faite ! Je vous laisse maintenant vous amuser à redessiner vos nouveaux niveaux et je vous dis à bientôt pour un prochain chapitre !






 

Connexion

CoalaWeb Traffic

Today107
Yesterday238
This week1030
This month508
Total1745407

3/05/24