Chapitre 26 :

Les boules de feu !



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

Dernière mise à jour: 28 janvier 2014
Difficulté :




Vous remarquerez sur les screenshots que les icônes pour la vie et les étoiles ont un peu changé.
C'est juste un changement d'images  . Vous les trouverez dans le projet complet à télécharger, si vous voulez le même rendu final.



    On va maintenant s'attaquer à quelque chose qui m'a souvent été demandée : la gestion des boules de feu !     Pour cela, nous allons faire lancer des boules de feu à notre lapin en pressant la touche d'attaque V, déjà configurée dans le code source de notre jeu (eh oui, j'avais déjà tout prévu pour vous faciliter la tâche !). On est partis !


I - Rajoutons un nouveau sprite !


    Bon, avant d'afficher des boules de feu, il nous faut déjà un sprite ! Sachant que notre lapin est un ninja, j'ai préféré transformer les boules de feu en étoiles de ninja (shuriken), ça fera mieux - mais ça reste le même principe pour des boules de feu - seule l'image change !

    Copiez donc l'image suivante dans le répertoire graphics d'Aron, sous le nom shuriken.png :


shuriken.png

    Maintenant que c'est fait, on va créer une nouvelle variable pour charger l'image.
    Pour cela, on va modifier notre structure Gestion qui regroupe toutes les infos de base sur le jeu, et rajouter une SDL_Surface :


 Fichier : structs.h

/* Structure pour gérer le niveau (à compléter plus tard) */

typedef struct Gestion
{

    SDL_Surface *screen;
    int nombreMonstres;
    int level;

    //HUD
	SDL_Surface *HUD_vie, *HUD_etoiles, *Shuriken_image;
    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;




    On va ensuite charger l'image dans la fonction loadGame() du fichier init.c comme les autres fichiers du HUD, etc... :


 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 la musique
    loadSong("music/RabidjaGo.mp3");




    Et on va aussi tout de suite la libérer à la sortie du jeu, dans la fonction cleanup() du même fichier :

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

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




    Voilà, maintenant notre image est opérationnelle : chargée / déchargée et prête à être blittée !


II - Gestion des boules de feu / shurikens

    Bon, il ne nous reste maintenant plus qu'à gérer nos shurikens.
    Pour cela, nous allons créer un tableau de GameObjects comme pour les monstres, sauf que là, ce sera des shurikens.
    Alors, on va dans le fichier main.h et on rajoute la ligne suivante :


Fichier : main.h

GameObject shuriken[10];



    On va utiliser un tableau de 10 shuriken max (0 à 9), mais dans la pratique on ne permettra que 3 shurikens max à l'écran (ils sont gros !). J'ai pris le tableau plus grand exprès, comme ça, si vous voulez adapter le jeu pour tirer des petites balles de Gatling, vous aurez de la marge !

    Il va maintenant nous falloir un compteur pour savoir combien on a de boules de feu / shurikens dans la nature !
    Pour cela, on va retourner dans notre structure Gestion et rajouter :

Fichier : structs.h

typedef struct Gestion
{

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




    On va ensuite initialiser ce nombre à 0, car de base, il n'y a pas de boules de feu à l'écran quand on charge un level (sinon, ça va faire bizarre ! ). Pour cela on va rajouter une ligne à notre fonction initializePlayer(void) dans le fichier player.c :

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;

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




    Voilà, il ne nous reste plus qu'à créer une nouvelle def pour indiquer le nombre de boules de feu max qu'on veut à l'écran et on pourra s'attaquer à leur gestion !
    Notez qu'il vous suffira ensuite de mofier cette valeur pour permettre plus ou moins de boules de feu / shurikens à l'écran !


Fichier : defs.h

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




    Voilà, on va maintenant créer 2 nouveaux fichiers pour héberger notre code (ce sera plus clair ainsi ) : fireballs.c et fireballs.h (euh oui, j'appelle les fichiers fireballs même si on tire des shurikens, parce que c'est un peu le titre du chapitre - après vous ferez tirer ce que vous voudrez ! )

    Bon, dans ce fichier, on va avoir besoin de 3 fonctions basiques :
  • - une fonction qui va créer des boules de feu, si c'est possible : ce sera createFireBall(void),
  • - une fonction qui va gérer les boules de feu (leur déplacement, leur mort) : ce sera doFireballs(void),
  • - et une fonction qui va les afficher à l'écran : ce sera drawFireballs(void).
    Je vous donne ci-dessous le code complet et commenté de ces 3 fonctions. Vous verrez qu'il n'y a rien de bien compliqué. On reprend un peu le même principe que pour les monstres, mais en plus simple.

Fichier : fireballs.c

#include "fireballs.h"


void createFireBall(void)
{
    /* Si on peut créer une boule de feu, on la crée */
    if (jeu.nombreFireballs < FIREBALLS_MAX)
    {
        //On enregistre la taille de l'image
        //(pour que vous puissiez la changer sans souci ;) )
        shuriken[jeu.nombreFireballs].w = jeu.Shuriken_image->w;
        shuriken[jeu.nombreFireballs].h = jeu.Shuriken_image->h;

        /* Direction de la boule de feu
        Les valeurs sont proportionnelles au perso - à adpater
        manuellement selon les cas :) */
        if ( player.direction == RIGHT )
            {
                shuriken[jeu.nombreFireballs].x = player.x + 15;
                shuriken[jeu.nombreFireballs].y = player.y + 35;
                shuriken[jeu.nombreFireballs].direction = 1;
            }
        else
            {
                shuriken[jeu.nombreFireballs].x = player.x - 15;
                shuriken[jeu.nombreFireballs].y = player.y + 35;
                shuriken[jeu.nombreFireballs].direction = 0;
            }
        jeu.nombreFireballs++;
    }

}


void doFireballs(void)
{
    int i;

    //On passe en boucle toutes les boules de feu ;)
    for ( i = 0; i < jeu.nombreFireballs; i++ )
    {
        /* On se déplace : vers la droite :  */
        if (shuriken[i].direction == 1)
        {
            shuriken[i].x += 10;
        }
        /* - vers la gauche : */
        else
        {
            shuriken[i].x -= 10;
        }


        /* Si elle sort de l'écran, on supprime la boule de feu */
        if ( shuriken[i].x < map.startX ||
             shuriken[i].x > map.startX + SCREEN_WIDTH
            )
        {
             shuriken[i] = shuriken[jeu.nombreFireballs-1];
             jeu.nombreFireballs--;
        }

    }

}


void drawFireballs(void)
{

    int i;

    //On affiche toutes les boules de feu
    for ( i = 0; i < jeu.nombreFireballs; i++ )
    {
        drawImage(jeu.Shuriken_image, shuriken[i].x - map.startX, shuriken[i].y - map.startY);
    }

}





    Sans oublier de compléter son en-tête :

Fichier : fireballs.h

#include "structs.h"

/* Structures globales */
extern Gestion jeu;
extern Map map;
extern GameObject player;
extern GameObject shuriken[];

/* Prototypes des fonctions utilisées */
extern void drawImage(SDL_Surface *, int, int);




    Bon, maintenant, que c'est fait, on va mettre à jour notre main() pour qu'il appelle les fonctions doFireballs() et drawFireballs() :

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



   
   Sans oublier de compléter son en-tête :

Fichier : main.h

extern void doFireballs(void);
extern void drawFireballs(void);



   
    On complète ensuite la fonction draw() dans draw.c :

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

}




   
   Sans oublier de compléter encore son en-tête :

Fichier : draw.h

extern void drawFireballs(void);




    Si on compile, on est bon !
    Mais ?! Rien ne se passe !
    Normal, notre jeu gère et affiche maintenant les boules de feu / shurikens, mais pour l'instant, on ne peut pas en créer !!
    Résolvons ce problème !


III - Créons des boules de feu / shurikens


    Il va maintenant nous falloir appeler la fonction createFireBall(void) à chaque fois qu'on va appuyer sur la touche d'attaque V.
    Pour cela, on retourne dans la fonction updatePlayer(void) du fichier player.c et on rajoute 3 lignes :

Fichier : player.c

void updatePlayer(void)
{

   //On rajoute un timer au cas où notre héros mourrait lamentablement en tombant dans un trou...
   //Si le timer vaut 0, c'est que tout va bien, sinon, on le décrémente jusqu'à 0, et là,
   //on réinitialise.
   //C'est pour ça qu'on ne gère le joueur que si ce timer vaut 0.
  if (player.timerMort == 0)
  {
      //On gère le timer de l'invincibilité
      if(player.invincibleTimer > 0)
        player.invincibleTimer--;

    //On réinitialise notre vecteur de déplacement latéral (X), pour éviter que le perso
    //ne fonce de plus en plus vite pour atteindre la vitesse de la lumière ! ;)
    //Essayez de le désactiver pour voir !
	player.dirX = 0;

    // La gravité fait toujours tomber le perso : on incrémente donc le vecteur Y
	player.dirY += GRAVITY_SPEED;

    //Mais on le limite pour ne pas que le joueur se mette à tomber trop vite quand même
	if (player.dirY >= MAX_FALL_SPEED)
	{
		player.dirY = MAX_FALL_SPEED;
	}

    //Voilà, au lieu de changer directement les coordonnées du joueur, on passe par un vecteur
    //qui sera utilisé par la fonction mapCollision(), qui regardera si on peut ou pas déplacer
    //le joueur selon ce vecteur et changera les coordonnées du player en fonction.
 	if (input.left == 1)
	{
		player.dirX -= PLAYER_SPEED;
		player.direction = LEFT;

		if(player.etat != WALK_LEFT && player.onGround == 1)
		{
		    player.etat = WALK_LEFT;
		    changeAnimation(&player, "graphics/walkleft.png");
		}
	}

	else if (input.right == 1)
	{
		player.dirX += PLAYER_SPEED;
		player.direction = RIGHT;

		if(player.etat != WALK_RIGHT && player.onGround == 1)
		{
		    player.etat = WALK_RIGHT;
		    changeAnimation(&player, "graphics/walkright.png");
		}
    }

    //Si on n'appuie sur rien et qu'on est sur le sol, on charge l'animation marquant l'inactivité (Idle)
    else if(input.right == 0 && input.left == 0 && player.onGround == 1)
    {
        //On teste si le joueur n'était pas déjà inactif, pour ne pas recharger l'animation
        //à chaque tour de boucle

        if(player.etat != IDLE)
        {
            player.etat = IDLE;
            //On change l'animation selon la direction
            if(player.direction == LEFT)
            {
		        changeAnimation(&player, "graphics/IdleLeft.png");
            }
            else
            {
		        changeAnimation(&player, "graphics/IdleRight.png");
            }

        }

    }


    //Et voici la fonction de saut très simple :
    //Si on appuie sur la touche saut et qu'on est sur le sol, alors on attribue une valeur
    //négative au vecteur Y
    //parce que sauter veut dire se rapprocher du haut de l'écran et donc de y=0.
    if (input.jump == 1)
    {
        if(player.onGround == 1)
        {
            player.dirY = -JUMP_HEIGHT;
            player.onGround = 0;
            player.jump = 1;
            playSoundFx(JUMP);
        }
        /* Si on est en saut 1, on peut faire un deuxième bond et on remet jump1 à 0 */
        else if (player.jump == 1)
        {
            player.dirY = -JUMP_HEIGHT;
            player.jump = 0;
            playSoundFx(JUMP);
        }
        input.jump = 0;
    }

    if(input.enter == 1)
    {
        //On met le jeu en pause
        jeu.onMenu = 1;
        jeu.menuType = PAUSE;
        input.enter = 0;
    }

    //On gère le lancer de shurikens
    if(input.attack == 1)
    {
        createFireBall();
        input.attack = 0;
    }


    /* Réactive la possibilité de double saut si on tombe sans sauter */
    if (player.onGround == 1)
		player.jump = 1;


	//On gère l'anim du saut
	if(player.onGround == 0)
	{
	    if(player.direction == RIGHT && player.etat != JUMP_RIGHT)
        {
            player.etat = JUMP_RIGHT;
            changeAnimation(&player, "graphics/JumpRight.png");
        }
        else if(player.direction == LEFT && player.etat != JUMP_LEFT)
        {
            player.etat = JUMP_LEFT;
            changeAnimation(&player, "graphics/JumpLeft.png");
        }

    }


    //On rajoute notre fonction de détection des collisions qui va mettre à jour les coordonnées
    //de notre super lapin, puis on centre le scrolling comme avant.
    mapCollision(&player);
    centerScrollingOnPlayer();

  }

    //Gestion de la mort quand le héros tombe dans un trou :
    //Si timerMort est différent de 0, c'est qu'il faut réinitialiser le joueur.
    //On ignore alors ce qui précède et on joue cette boucle (un wait en fait) jusqu'à ce que
    // timerMort == 1. A ce moment-là, on le décrémente encore -> il vaut 0 et on réinitialise
    //le jeu avec notre bonne vieille fonction d'initialisation ;) !
    if (player.timerMort > 0)
	{
		player.timerMort--;

		if (player.timerMort == 0)
		{
			/* Si on est mort */
			jeu.vies--;
			if(jeu.vies < 0)
			{
			    //On retourne au menu start
                jeu.onMenu = 1;
                jeu.menuType = START;
			}
			initializePlayer();
			changeLevel();
		}
	}

}





   Sans oublier de compléter son en-tête :

Fichier : player.h

extern void createFireBall(void);




    Voilà, compilez maintenant, et ça marche !!! Notre lapin balance des shurikens !!!
    Mais, ça touche pas les monstres !?!
    Normal, on ne teste pas encore les collisions !

    Alors réglons ça dans la fonction updateMonsters(void) dans monster.c. Pour ça, on va réutiliser notre fonction collide(). Normalement, elle était destinée au player et au monstre, qui sont deux GameObjects, mais comme notre boule de feu est aussi un GameObject, ça marche aussi .

    Rajoutons donc ces quelques lignes :

   
Fichier : monster.c

	// Test de collision monstre-boule de feu
            int a;
            for ( a = 0; a < jeu.nombreFireballs; a++ )
            {
                if (collide(&monster[i], &shuriken[a]))
                {
                    //On met le timer à 1 pour tuer le monstre intantanément
                    monster[i].timerMort = 1;
                    playSoundFx(DESTROY);

                    shuriken[a] = shuriken[jeu.nombreFireballs-1];
                    jeu.nombreFireballs--;
                }
            }




   Sans oublier de rajouter cette ligne à son en-tête :

Fichier : player.h

extern GameObject shuriken[];



   
    Eh voilà ! On compile, et ça marche !!! Hourra !


    Derniers réglages :

    Vous pouvez maintenant adapter le lancer des shurikens à vos besoins :
  • - en changeant l'image Shuriken_image,
  • - leur nombre en changeant la def : FIREBALLS_MAX,
  • - leur vitesse en changeant la valeur 10 dans la fonction doFireballs(void) avec : shuriken[i].x += 10; et shuriken[i].x -= 10;
  • - leur position de départ en changeant les x et y relatifs à la position (et à la taille) de votre héros.

     A bientôt pour la suite !
 




Télécharger le projet complet !



 

Connexion

CoalaWeb Traffic

Today89
Yesterday238
This week1012
This month490
Total1745389

3/05/24