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




Tutoriel présenté par : Jérémie F. Bellanger
Dernière mise à jour : 07 novembre 2010
Difficulté :  





4. Afficher le background de notre jeu


     Maintenant qu'on a notre belle fenêtre, il va falloir la remplir (parce que le monochrome noir, c'est bien un moment, mais on s'en lasse ). On va donc s'occuper avant tout de l'affichage du niveau de jeu (la map). Mais avant de nous lancer à corps perdu là-dedans, nous allons procéder par étape (pour que ce soit moins difficile ). Dans ce chapitre, nous allons configurer SDL_image et mettre à jour l'ossature de notre moteur de jeu pour afficher le background (fond) de la map. Et dans le chapitre suivant, nous nous occuperons des tiles (si vous ne savez pas ce qu'est le tilemapping, c'est par ici !).


A. Configurer SDL_image

     SDL_image est une bibliothèque très pratique qui permet de charger des fichiers d'image dans d'autres formats que le simple BMP de la SDL, comme le jpg et le png par exemple. D'ailleurs, si vous devez choisir un format d'image pour votre jeu, je vous conseille le format png car il est non destructif (donc l'image reste la même) et gère la transparence.

      Nous allons donc l'installer dans notre projet. Pour cela, il faut commencer par la télécharger ici. Ce lien contient sa documentation (toujours utile) mais aussi les fichiers dont vous aurez besoin : choisissez SDL_image-devel-1.2.10-VC.zip si vous êtes sous Windows.

     Pour l'installer, le plus simple, c'est de copier le fichier
SDL_image.h du dossier include dans le dossier include de la SDL, et le fichier SDL_image.lib du dossier lib dans le dossier lib de la SDL (2 copier/coller donc).

      Copiez ensuite tous le fichiers .dll du dossier lib dans le dossier de votre projet (normalement Aron).

     Il ne reste plus qu'à configurer Code::Blocks. Faites un clic droit dans Code::Blocks sur le nom de votre projet (Aron) puis choisissez : Build options...

     Dans l'arborescence, choisissez Aron pour configurer tout le projet d'un coup et choisissez l'onglet Linker Settings.

     Dans Link libraries, choisissez add puis sélectionnez SDL_image.lib en recherchant là où vous l'avez installé (par exemple : C:\Program Files\CodeBlocks\SDL-1.2.14\lib\SDL_image.lib). Cliquez sur OK en bas, et normalement c'est bon  !



B. Retour à notre code


    Nous allons donc pouvoir retourner à notre code et le modifier en conséquence pour gérer : le chargement du background, sa libération de la mémoire à la fin du programme (à ne pas oublier !), et son affichage à chaque tour de boucle. Pour cela, nous allons reprendre nos fichiers précédents dans le même ordre (par souci de simplicité) et changer/rajouter ce qu'il faut. Les rajouts seront indiqués en italiques.

     Alors, allons-y ! Banzaï  ! Quoi ? Stop ?
     Ah oui, j'allais oublier  ! Comment peut-on afficher un background si on n'en a pas  ?
     Deux solutions : soit vous dessinez le vôtre (format 640 x 480 pixels, la taille de notre fenêtre), soit vous prenez celui-là et vous le copiez dans le répertoire de votre projet dans le dossier graphics, sous le nom background.png (c'est important pour le charger dans notre programme ensuite):

 


Nom du fichier : defs.h


  #include <stdio.h>
  #include <string.h>
  #include <stdlib.h>
  #include <math.h>
  #include <SDL.h>
 /* On inclut les libs supplémentaires */
  #include <SDL_image.h>

 /* Taille de la fenêtre / résolution en plein écran */
  #define SCREEN_WIDTH 640
  #define SCREEN_HEIGHT 480

 /* Valeur RGB pour la transparence (canal alpha) */
 #define TRANS_R 255
 #define TRANS_G 0
 #define TRANS_B 255


    Mise à jour de nos définitions : on rajoute l'entête de SDL_image puisqu'on va s'en servir (tant qu'à l'avoir installé ), et on rajoute les données R, G, B pour la transparence. La couleur choisie (255, 0, 255) est une sorte de mauve, qui deviendra donc transparente au blittage. Cette couleur n'a pas été choisie au hasard : c'est la couleur de sortie choisie par TileStudio. Si vous sous en servez pour dessiner vos tiles (ce que je vous conseille), vous n'aurez donc rien à changer  ! Elle est pas belle, la vie  ?!


Nom du fichier : structs.h


  #include "defs.h"

  /* Structures qui seront utilisées pour gérer le jeu */

  /* Structure pour gérer l'input (clavier puis joystick) */

   typedef struct Input
  {

    int left, right, up, down, jump, attack, enter, erase, pause;

  } Input;


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

  typedef struct Gestion
  {

    SDL_Surface *screen;

  } Gestion;


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

  typedef struct Map
  {

      SDL_Surface *background;

  } Map;



    Ici, pas de gros changements en perspective . On crée juste une nouvelle structure pour la map à afficher dans laquelle on trouvera une SDL_Surface qui contiendra le background (= fond) de notre niveau. Bien entendu, comme les autres, on étoffera cette structure par la suite.


Nom du fichier : main.c


    #include "main.h"

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

    /* Initialisation de la SDL dans une fonction séparée (voir après) */
        init("Aron");


     /* 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 vérifie l'état des entrées (clavier puis plus tard joystick */
        getInput();

        /* On affiche tout */
        draw();

        /* Gestion des 60 fps ( 60 images pas seconde : soit 1s ->1000ms/60 = 16.6 -> 16
        On doit donc attendre 16 ms entre chaque image (frame) */

        delay(frameLimit);
        frameLimit = SDL_GetTicks() + 16;

    }

    /* Exit */
    exit(0);

    }



    Et dans notre main, qu'est-ce qu'on rajoute  ? Eh bien pas grand chose en fait... Le principal est déjà là  !
    Avant la boucle de jeu principale, on va juste rajouter une fonction qui servira à charger les assets de notre jeu (les assets - terme anglais - désignent tout ce qui appartient au jeu mais n'est pas du code : les graphismes, les sons, les musiques, etc.) : loadGame().
     Pour l'instant, elle sera très basique puisqu'elle ne chargera que le background  !
   

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


  /* Déclaration des structures globales utilisées par le jeu */

  Input input;
  Gestion jeu;

  Map map;



    Logiquement, on rajoute dans l'en-tête le prototype de notre fonction loadGame() pour pouvoir nous en servir (et aussi la fonction delay(), qu'on avait oubliée dans le chapitre précédent ).

    Et, très important, on déclare notre structure map pour pouvoir nous en servir ! Désormais, on pourra donc accéder à notre background en faisant map.background ! Génial, non  ? (Euh, pas convaincu...  Vous verrez, quand on aura mis une vingtaine de variables là-dedans  !).


Nom du fichier : init.c


  #include "init.h"

   /* Fonction qui initialise le jeu */

   void init(char *title)
   {
  
    /* Initialise SDL Video. Si la valeur de retour est inférieure à zéro, la SDL n'a pas pu
     s'initialiser et on quite */


    if (SDL_Init(SDL_INIT_VIDEO ) < 0)
    {
        printf("Could not initialize SDL: %s\n", SDL_GetError());
        exit(1);
    }


     /* On crée la fenêtre, représentée par le pointeur jeu.screen en utilisant la largeur et la
     hauteur définies dans les defines (defs.h). On utilise aussi la mémoire vidéo
     (
SDL_HWPALETTE) et le double buffer pour éviter que ça scintille
     (
SDL_DOUBLEBUF) */

    jeu.screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, 0,
                                                        SDL_HWPALETTE|SDL_DOUBLEBUF);

     /* Si on y arrive pas, on quitte */

    if (jeu.screen == NULL)
        {
            printf("Couldn't set screen mode to %d x %d: %s\n", SCREEN_WIDTH,  
                     SCREEN_HEIGHT, SDL_GetError());
            exit(1);
        }


    /* On entre le titre de la fenêtre */

    SDL_WM_SetCaption(title, NULL);


    /* On cache le curseur de la souris */

    SDL_ShowCursor(SDL_DISABLE);

    }


     void loadGame(void)
    {

        /* Charge l'image du fond */
        map.background = loadImage("graphics/background.png");

     }



   /* Fonction qui quitte le jeu proprement */

    void cleanup()
   {

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

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


      /* Quitte la SDL */
      SDL_Quit();

    }


    Et c'est ici qu'on va trouver notre fonction loadGame(). Logique, non ? Alors comme vous pouvez le voir, elle ne fait qu'une chose : charger le background à l'aide de la fonction loadImage (on expliquera pourquoi après ). Remarque : on ne teste pas si map.background est null car on le sait déjà (on vient d'initialiser le programme). Cette fonction ne doit donc être utilisée qu'à l'initialisation (sinon, on se retrouverait avec plein de copies du background en mémoire  !).

     Dernière modification : on rajoute un SDL_FreeSurface à la sortie du programme pour libérer la mémoire allouée à notre background (si celle-ci n'est pas nulle, bien sûr). Indispensable !


Nom du fichier : init.h


   #include "structs.h"
   
    /* Prototypes des fonctions utilisées */
     extern SDL_Surface *loadImage(char *name);


    extern Gestion jeu;
   
extern Map map;



    Et ici on rajoute le prototype de notre fonction loadImage() ainsi que : extern Map map; qui indique au compilateur que la structure map de type Map a déjà été déclarée dans un autre fichier, et qu'il n'y a donc pas besoin de la redéclarer : c'est la même !


Nom du fichier : input.c


  #include "input.h"


  void getInput()
 {
    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_DELETE:
                        input.erase = 1;
                    break;

                    case SDLK_c:
                        input.jump = 1;
                    break;

                      case SDLK_v:
                        input.attack = 1;
                    break;

                    case SDLK_LEFT:
                        input.left = 1;
                    break;

                    case SDLK_RIGHT:
                        input.right = 1;
                    break;

                    case SDLK_DOWN:
                        input.down = 1;
                    break;

                    case SDLK_UP:
                        input.up = 1;
                    break;

                    case SDLK_RETURN:
                        input.enter = 1;
                    break;

                    default:
                    break;
                }
            break;

            case SDL_KEYUP:
                switch (event.key.keysym.sym)
                {
                    case SDLK_DELETE:
                        input.erase = 0;
                    break;

                    case SDLK_c:
                        input.jump = 0;
                    break;

                    case SDLK_LEFT:
                        input.left = 0;
                    break;

                    case SDLK_RIGHT:
                        input.right = 0;
                    break;

                    case SDLK_DOWN:
                        input.down = 0;
                    break;

                    case SDLK_UP:
                        input.up = 0;
                    break;

                    case SDLK_RETURN:
                        input.enter = 0;
                    break;

                    default:
                    break;
                }
            break;

        }

    }
 }


    Ne cherchez pas, il n'y a rien à voir  ! Aucune modification !


Nom du fichier : input.h


  #include "structs.h"

  extern Input input;


    Là non plus, aucune modification .
Pour l'instant, je laisse tout le code, le temps de s'y habituer, mais dans la suite du tutoriel, je ne réindiquerai plus les passages duc code qui ne changent pas.


Nom du fichier : draw.c


#include "draw.h"

   void drawImage(SDL_Surface *image, int x, int y)
  {
       SDL_Rect dest;

       /* Règle le rectangle à blitter selon la taille de l'image source */

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

       /* Blitte l'image entière sur l'écran aux coordonnées x et y */

       SDL_BlitSurface(image, NULL, jeu.screen, &dest);
   }



  void draw(void)
  {

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


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

    /* Delai */
    SDL_Delay(1);

  }


   SDL_Surface *loadImage(char *name)
   {
       /* Charge une image temporaire avec SDL Image */

       SDL_Surface *temp = IMG_Load(name);
       SDL_Surface *image;

       /* Si elle n'est pas chargée on quitte avec une erreur */
      if (temp == NULL)
      {
           printf("Failed to load image %s\n", name);

          return NULL;
       }

       /* Ajoute la transparence à l'image temporaire en accord avec les defines TRANS_R, G, B */

       SDL_SetColorKey(temp, (SDL_SRCCOLORKEY | SDL_RLEACCEL), SDL_MapRGB(temp->format,
                                                   TRANS_R, TRANS_G,    TRANS_B));


       /* Convertit l'image au format de l'écran (screen) pour un blit plus rapide */

       image = SDL_DisplayFormat(temp);
       
       /* Libère l'image temporaire */
       SDL_FreeSurface(temp);

       if (image == NULL)
       {
           printf("Failed to convert image %s to native format\n", name);

           return NULL;
       }

       /* Retourne l'image au format de l'écran pour accélérer son blit */

       return image;


   }



  void delay(unsigned int frameLimit)
  {

    /* Gestion des 60 fps (images/stories/seconde) */

    unsigned int ticks = SDL_GetTicks();

    if (frameLimit < ticks)
    {
        return;
    }

    if (frameLimit > ticks + 16)
    {
        SDL_Delay(16);
    }

    else
    {
        SDL_Delay(frameLimit - ticks);
    }
  }


    C'est ici que se trouve le gros de notre mise à jour avec deux fonctions qui vont nous servir intensivement.
    La fonction loadImage() charge une image avec SDL_Image mais pas seulement .

    En effet, elle lui ajoute la transparence définie en define mais optimise aussi cette image pour accélérer par la suite son blittage à l'écran : pour cela, elle convertit l'image au format de l'écran .

    Sans cette fonction, la SDL devrait convertir les images au format de l'écran à chaque blittage (soit 60 fois par seconde pour notre jeu). Avec cette fonction, elle le fait une fois pour toutes  ! Résultat : un affichage plus rapide .

    Pour un simple background, on ne verra pas la différence bien sûr, mais avec plsusieurs centaines de tiles/sprites à blitter toutes les 16 ms, je vous assure qu'on verra la différence  !

    P.S. : Autre avantage de cette fonction : si un jour vous deviez changer SDL_Image pour une autre lib, il n'y aurait qu'une ligne à changer dans tout votre programme. Alors que si vous aviez mis des IMG_Load() partout...
   
    La deuxième fonction est la fonction drawImage() que l'on a placée tout en haut, car la fonction suivante : draw() l'utilise et cela évite d'avoir à retaper son prototype ! Son utilisation est évidente : on lui envoie l'image à afficher et ses coordonnées x et y, et elle la blitte à l'écran, en créant un SDL_Rect pour l'occasion à la taille de l'image. Grâce à elle : adieu les SDL_Rect en pagaille (qui ne servent pas à grand chose  ).


Nom du fichier : draw.h


  #include "structs.h"

  extern Gestion jeu;
  extern Map map;


On rajoute notre map pour que le compilo soit au courant (faut tout lui dire, celui-là  !).

    Eh voilà, on est maintenant prêt à compiler... BUILD... RUN et tadaaaaam ! Notre background s'affiche !!!



    Et maintenant, prochaine étape : la conquête de la Lune l'affichage de la map  !!




 

 

 

Connexion

CoalaWeb Traffic

Today5
Yesterday197
This week565
This month3232
Total1748131

16/05/24