Chapitre 24 :

Reprenons en mains notre jeu !



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

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




I - Préambule

    Ce chapitre 24 commençait à l'origine une nouvelle série de tutos, reprenant ce qui avait déjà été fait dans les 23 chapitres précédents, mais en y intégrant des techniques plus avancées (d'où le titre). Cependant avec le développement du site et le nombre grandissant de tutos (sans compter l'arrivée de la SDL2), je trouvais que cela devenait un peu confus. C'est pourquoi j'ai choisi de regrouper tous les chapitres traitant de la création de notre jeu de plateformes en SDL 1.2.
Grâce à la lecture des 23 chapitres précédents, vous en savez maintenant assez pour créer plein de petits jeux de base !

    Quoi ?! Mais cela ne vous suffit pas ?!
    Vous voulez en plus connaître les petits secrets qui vous permettront de créer LE JEU en rajoutant plein de fonctionnalités futiles mais somme toute indispensables ?!
    Eh, bien, alors, on est parti pour quelques chapitres de plus !

    Dans ce chapitre, nous allons reprendre notre projet tel qu'il était au chapitre 23, et nous allons l'améliorer, le peaufiner, en rajoutant, notamment (et pas forcément dans l'ordre ) : une deuxième couche de tiles, des checkpoints, la possibilité de lancer des boules de feu, la gestion de la santé, des plateformes volantes, un petit boost avec OpenGl, etc... !!

    Donc, si vous n'avez plus votre projet opérationnel, pas de problème, il suffit de vous rendre dans la section Téléchargements et de retélécharger le projet du chapitre 23 !
  


II - Changement de Tilesets


    Nous allons commencer par changer les tilesets du jeu, pour permettre, à la fois plus de fantaisie, mais aussi l'ajout ultérieur de nouvelles fonctionnalités. Bien entendu, ces tilesets restent encore très petits. Un vrai tileset fait au moins 100 à 200 tiles, et vous pouvez ajouter votre propre tileset si cela vous chante (c'est même conseillé ), du moment que vous gardez les mêmes tiles clés que celles du tuto (après peu importe que la tile étoile soit la numéro 6 ou la numéro 241 !  ).

    Alors, voici les tilesets :


tileset1.png


tileset1B.png


tileset2.png


tileset2B.png

    Alors, enregistrez-les sous leurs noms respectifs, et placez-les 4 dans le répertoire graphics du projet Aron, et seulement les tilesets 1 et 2 dans le répertoire graphics du Level Editor.

    Avant d'aller plus loin, commentons un peu ces 2 tilesets. Les tiles sont toujours rangés dans un ordre logique :
  • - la première est vide et transparente (rien - tile 0),
  • - les tiles 1 à 4 sont des tiles de décor traversables,
  • - les tiles 5 et 6 représentent l'eau,
  • - les tiles 7 et 8 sont des power-ups : la classique étoile (refaite) + le coeur quand nous gèrerons la santé,
  • - les tiles 9 et 10 représentent notre checkpoint qui aura la forme d'un mat quand il n'est pas pris, et un drapeau apparaîtra si on le touche !
  • - les tiles 11 à 14 sont solides mais la tile ressort fait aussi rebondir,
  • - les tiles 15 et 16 démarreront notre système de plateformes flottantes,
  • - et enfin la tile 17 reste notre tile monstre.
    Rien de bien extraordinaire donc, et si vous ne voyez pas encore bien comment on va gérer tout ça, rassurez-vous, vous allez comprendre dans les chapitres à venir !

  

III - Mettons à jour notre Level Editor et nos maps ! 

    Voilà, on va donc commencer par mettre à jour le Level Editor et à modifier nos maps pour correspondre au nouveau tileset. Alors, bien sûr, on pourrait créer une fonction qui convertirait automatiquement nos levels de l'ancien tileset vers le nouveau (il s'agit seulement de changer le numéro des tiles), mais comme nous n'avons que deux petits niveaux et que nous voulons rajouter de nouvelles possibilités, nous allons procéder à la main, et ce sera le moment de laisser parler votre fibre créatrice !

    Ouvrons donc notre projet Level Editor dans Code::Blocks et lançons-le pour voir ce que ça donne :


    Ah ! Mais c'est quoi, ça !?!
    C'est normal ! Rassurez-vous, le tileset a changé, donc vos maps ne correspondent plus. Le plus simple reste sans doute de faire un reset et de recommencer !
    Mais avant, il faut qu'on règle un problème : notre tileset reste limité à sa taille précédente alors que nous avons plus de tiles ! Changeons donc cette variable dans defs.h et passons-la de 10 à 17 :

/* MAX_TILES = numéro de la dernière tile */
#define MAX_TILES 17
Recompilons notre level editor, lançons-le et c'est bon, je vous laisse maintenant changer les niveaux à votre guise et rendez-vous au chapitre suivant !



Notre map refaite et modifiée : c'est mieux comme ça !



IV - Mettons maintenant notre jeu à jour !


    Voilà, alors n'oubliez pas de sauvegarder vos niveaux en appuyant sur la touche S, puis copiez les fichiers map1.txt et map2.txt du répertoire map du Level Editor vers celui du jeu.
    Une fois que c'est fait, fermez le projet du Level Editor et ouvrez celui du jeu.

    Alors, avant de lancer notre jeu pour voir le résultat, il va d'abord nous falloir redéfinir les valeurs des tiles traversables ou non. Et aussi changer la valeur des tiles spéciales. Pour cela, nous allons créer de nouvelles defs, en dessous de BLANK_TILE. Voilà ce que ça donne :

Fichier : defs.h

// Constante définissant le seuil entre les tiles 
//traversables (blank) et les tiles solides
#define BLANK_TILE 10

//Autres Tiles spéciales

#define TILE_RESSORT 12
#define TILE_CHECKPOINT 9
#define TILE_MONSTRE 17

#define TILE_POWER_UP_DEBUT 7
#define TILE_POWER_UP_FIN 8

#define TILE_PLATEFORME_DEBUT 15
#define TILE_PLATEFORME_FIN 16



    Vous remarquerez qu'on a défini un début et une fin pour les tiles dont le nombre est supérieur à 1. Bon, ici, pour 2 tiles, ça ne fait pas gagner beaucoup de temps, mais quand vous aurez 25 tiles power-ups, ce sera plus simple que de les tester les unes après les autres en copiant/collant les test, croyez-moi ! 

    Bon, maintenant, qu'on a nos nouvelles defines, il va nous falloir mettre à jour notre fonction : void mapCollision(GameObject *entity), dans map.c. On va simplement remplacer les valeurs chiffrées par le nom de nos defines. Comme ça, si on change une nouvelle fois de tileset, on n'aura plus qu'à modifier nos defines pour tout le jeu ! Voilà le résultat (N.B.: Je vous donne le code avec un autre système car le highlighter habituel n'arrive pas à le gérer correctement - trop complexe pour lui sans doute !) :


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 de la tile Power-up : Etoile (= tile N°5)
				if (map.tile[y1][x2] == TILE_POWER_UP_DEBUT)
				{
				    //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] == TILE_POWER_UP_DEBUT)
				{
				    //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] == TILE_POWER_UP_DEBUT)
				{
				    //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] == TILE_POWER_UP_DEBUT)
				{
				    //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] == TILE_POWER_UP_DEBUT)
				{
				    //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] == TILE_POWER_UP_DEBUT)
				{
				    //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] == 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;
				}

			}

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

    Bon, vous aurez remarqué, que pour l'instant on ne se sert que de TILE_POWER_UP_DEBUT, vu qu'on a qu'un seul power-up. On verra comment rajouter le deuxième dans un prochain chapitre.
    Mais il  nous reste encore à changer la valeur de la tile monstre, dites-moi !
    Exactement, et rappelez-vous, on s'en servait dans la fonction void drawMap(void), du même fichier map.c. Ici, exactement :

Fichier : map.c

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


 
    Eh voilà si on compile et qu'on lance le jeu, tout fonctionne, sauf bien sûr nos nouvelles tiles que nous allons gérer dans les prochains chapitres ! Au chapitre suivant, on s'occupera tout d'abord de notre tile coeur et on donnera de la santé de notre lapin avec ses 3 coeurs réglementaires ! A bientôt pour la suite !
 




Télécharger le projet complet !


Jeu / Level Editor



 

Connexion

CoalaWeb Traffic

Today36
Yesterday185
This week36
This month5206
Total1744413

29/04/24