Animer une scène : des balles qui rebondissent

Écrit le 23/06/2004 par Kurim
Dernière mise à jour : 02/02/2006

Introduction

Bien. Dans ce tutorial, on va réaliser un programme qui va dessiner un certain nombre de boules a l'écran et qui va les déplacer. Nous aborderons donc la gestion de l'animation (en fonction du temps), la gestion de la transparence, et la gestion du blit de surface en temps réel. Pour notre programme, on va partir de ce squelette tiré du tutorial précedent (NB : la méthode pour gérer les events à un peu changé : on "attend" plus un évenement, on "regarde si un évenement c'est produit" ; le but étant de ne pas bloquer le programme quand il ne se passe rien)

#include "sdl/SDL.h" 

int Init( void ) 
{ 
   if ( SDL_Init( SDL_INIT_VIDEO ) == -1 ) 
   { 
      printf( "Echec lors du chargement de la vidéo : %s", SDL_GetError() ); 
      SDL_Quit(); 
   } 
   else 
   { 
      if ( Screen = SDL_SetVideoMode( 640, 480, 16, SDL_HWSURFACE | SDL_DOUBLEBUF ) ) 
         return 1; 
      else 
         SDL_Quit(); 
   } 
   return 0; 
} 

void Frame( void ) 
{ 
   SDL_Event event; 

    while (1)
    {
        SDL_PollEvent( &event );
        switch (event.type)
        {
            case SDL_KEYDOWN:
            if ( event.key.keysym.sym == SDLK_ESCAPE )
            {
                SDL_Quit();
                return;
            }
            break;
            case SDL_QUIT:
                SDL_Quit();
                return;
            break;
        }
    }
} 

int main( int argc, char* argv[] ) 
{ 
   if ( Init() ) 
   {
      Frame(); 
   } 
    
   return 0; 
}

D'abord, on précache !

Tout d'abord, nous allons définir le nombre de boules que l'on veut créer dans notre programme, puis on va déclarer autant de pointeur de surface que ce nombre, plus une surface pour y mettre une image de fond. On déclare également des variables pour indiquer la position horizontale et verticale de chaque boule, ainsi que leur direction. Ici j'ai pris 4 images que voici :

http://www.game-lab.com/images/tuts/sdl_bounce/boule1.bmp http://www.game-lab.com/images/tuts/sdl_bounce/boule2.bmp http://www.game-lab.com/images/tuts/sdl_bounce/boule3.bmp http://www.game-lab.com/images/tuts/sdl_bounce/boule4.bmp

Mais vous pouvez évidement en prendre plus. Donc en début de fichier, mettez :

#define NB_BOULES    4

SDL_Surface      *boules[NB_BOULES];
SDL_Surface   *background;

short        pos_x[NB_BOULES];
short        pos_y[NB_BOULES];
Uint8        dir[NB_BOULES];

Parlons un peu de la méthode : On a un certain nombre d'image à aller chercher dans des fichiers. Il est de toute évidence préferable de charger ces images en mémoire au début du programme, pour y accéder par la memoire à tout instant, plutot que de rouvrir les fichiers a chaque execution de la boucle (l'accès en memoire se fait quelque chose comme un million de fois plus rapidement que l'acces sur disque dur). On va donc réaliser une fonction Precache, qui va charger nos images, et qui retournera 1 en cas de succès.
Nos fichiers devront être placé dans le meme dossier que l'executable, et devrons porter ces noms :
pour le fond :
bg.bmp
pour les boules :
boule1.bmp
boule2.bmp
boule3.bmp
...

Voici la fonction Precache : elle charge les images et initialise les positions des boules à des endroits differents et avec une direction differente. Rien de bien compliqué, les images sont chargés avec SDL_LoadBMP() et une boucle permet de charger toutes les images de boule. On peut quand meme remarquer que l'on affecte une couleur pour la transparence : on utilise SDL_SetColorKey qui va définir la couleur a "changer" en transparent. Le deuxieme parametre est un flag. S'il contient SDL_SRCCOLORKEY alors la couleur donnée est défini comme transparente. S'il ne contient pas ce flag, la couleur est défini comme non transparent. On peut le coupler avec le flag SDL_RLEACCEL qui est sensé "accelérer" le rendu dans certain cas, mais on obtient plus souvent l'effet inverse (ou un plantage), c'est pourquoi je déconseille d'utiliser cette accélération. Enfin le dernier parametre est la couleur établie sur 32 bits, en RGB (8 bits par couleur).

int Precache( void )
{
    char path[32];
    int i;
    if ( (background = SDL_LoadBMP( "bg.bmp" )) == NULL )
        return 0;

    for ( i = 0 ; i < NB_BOULES ; i++ )
    {
        sprintf( path, "boule%d.bmp", i+1 );
        if ( (boules[i] = SDL_LoadBMP( path )) == NULL )
            return 0;

        pos_x[i] = i * (int)(640 / NB_BOULES);
        pos_y[i] = i * (int)(480 / NB_BOULES);
        dir[i] = i % 4;
        SDL_SetColorKey( boules[i], SDL_SRCCOLORKEY , 0x00FF00FF );
    }
    return 1;
}

Maintenant mettons à jour la fonction main :

int main( int argc, char* argv[] ) 
{ 
   if ( Init() ) 
   {
       if ( Precache() )
          Frame(); 
   } 
    
   return 0; 
}

et modifions la fonction Frame. On va créer une fonction qui prend comme parametre un entier, le temps, et qui va modifier les coordonnées en fonction du temps qui s'est écoulé depuis sa derniere execution. Voici déja la fonction Frame() :

void Frame( void ) 
{ 
   SDL_Event event; 
   Uint32 curtime;
   Uint32 deltatime;

            curtime = SDL_GetTicks();
    while (1)
    {
        deltatime = SDL_GetTicks() - curtime;
        UpdateScreen( deltatime );
        curtime += deltatime;

        [...] // Code relatif aux events
    }
}

Mise à jour de l'écran en temps réel

On déclare donc 2 variables entières positives, la première, curtime, récupère la valeur du temps avec SDL_GetTicks(). Cette fonction retourne le nombre de milisecondes executés depuis l'appel de SDL_Init(). Puis on amorce une boucle, dont les events se chargeront de nous sortir, et dans laquel on calcule le temps depuis la précedente execution, temps qu'on passe en parametre a la fonction UpdateScreen que nous allons coder maintenant.

#define VITESSE_BOULE    100    

void UpdateScreen( Uint32 deltatime )
{
    int i;
    SDL_Rect boule_rect;
}

On part de la, on aura juste besoin d'un rectangle et d'un compteur. Je vais détailler le remplissage ; on commence par faire un blit du background :

SDL_BlitSurface( background, NULL, Screen, NULL );

Puis on va amorcer une boucle :

for ( i = 0 ; i < NB_BOULES ; i++ )
{

On a définit une variable dir[] qui indique la direction dans laquelle se déplace chaque balle. On va donc définir le sens comme ce :
dir = 0 : vers le haut-droit
dir = 1 : vers le haut-gauche
dir = 2 : vers le bas-gauche
dir = 3 : vers le bas-droit
Après coup j'ai remarqué que ce n'était pas le sens le plus facile à coder, mais bon c'est pas très grave...
Ce qui nous donne :

if (dir[i] == 0 || dir[i] == 3) // dir = 0 ou 3
    pos_x[i] += (int)(VITESSE_BOULE * deltatime / 1000 );
else                            // dir = 1 ou 2
    pos_x[i] -= (int)(VITESSE_BOULE * deltatime / 1000 );
if (dir[i] < 2)                    // dir = 0 ou 1
    pos_y[i] -= (int)(VITESSE_BOULE * deltatime / 1000 );
else                            // dir = 2 ou 3
    pos_y[i] += (int)(VITESSE_BOULE * deltatime / 1000 );

voila, et afin de permettre aux boules de rebondir contre les murs :

if (pos_x[i] < 0 )
{
    if ( dir[i] == 1 ) dir[i] = 0;
    if ( dir[i] == 2 ) dir[i] = 3;
}
if (pos_x[i] > 640 - boules[i]->w)
{
    if ( dir[i] == 0 ) dir[i] = 1;
    if ( dir[i] == 3 ) dir[i] = 2;
}
if (pos_y[i] < 0 )
{
    if ( dir[i] == 0 ) dir[i] = 3;
    if ( dir[i] == 1 ) dir[i] = 2;
}
if (pos_y[i] > 480 - boules[i]->h)
{
    if ( dir[i] == 2 ) dir[i] = 1;
    if ( dir[i] == 3 ) dir[i] = 0;
}

Voila, on a plus qu'a définir le rectangle pour le blit :

boule_rect.x = pos_x[i];
boule_rect.y = pos_y[i];
boule_rect.w = boules[i]->w;
boule_rect.h = boules[i]->h;

    SDL_BlitSurface( boules[i], NULL, Screen, &boule_rect );
}

SDL_Flip( Screen );

Et on flip le tout. Voici le résultat (mon fond est très moche mais j'ai jamais été très doué avec photoshop...) :

http://www.game-lab.com/images/tuts/sdl_bounce/final.jpg

Le code au final

Voila je remet le code dans son intégralité :

#include "sdl/SDL.h" 

#define NB_BOULES    4

SDL_Surface   *Screen; 
SDL_Surface      *boules[NB_BOULES];
SDL_Surface   *background;

short        pos_x[NB_BOULES];
short        pos_y[NB_BOULES];
Uint8        dir[NB_BOULES];

int Init( void ) 
{ 
   if ( SDL_Init( SDL_INIT_VIDEO ) == -1 ) 
   { 
      printf( "Echec lors du chargement de la vidéo : %s", SDL_GetError() ); 
      SDL_Quit(); 
   } 
   else 
   { 
      if ( Screen = SDL_SetVideoMode( 640, 480, 16, SDL_HWSURFACE | SDL_DOUBLEBUF ) ) 
         return 1; 
      else 
         SDL_Quit(); 
   } 
   return 0; 
} 

int Precache( void )
{
    char path[32];
    int i;
    if ( (background = SDL_LoadBMP( "bg.bmp" )) == NULL )
        return 0;

    for ( i = 0 ; i < NB_BOULES ; i++ )
    {
        sprintf( path, "boule%d.bmp", i+1 );
        if ( (boules[i] = SDL_LoadBMP( path )) == NULL )
            return 0;

        pos_x[i] = i * (int)(640 / NB_BOULES);
        pos_y[i] = i * (int)(480 / NB_BOULES);
        dir[i] = i % 4;
        SDL_SetColorKey( boules[i], SDL_SRCCOLORKEY , 0x00FF00FF );
    }
    return 1;
}

#define VITESSE_BOULE    100    

void UpdateScreen( Uint32 deltatime )
{
    int i;
    SDL_Rect boule_rect;

    SDL_BlitSurface( background, NULL, Screen, NULL );

    for ( i = 0 ; i < NB_BOULES ; i++ )
    {
        if (dir[i] == 0 || dir[i] == 3) // dir = 0 ou 3
            pos_x[i] += (int)(VITESSE_BOULE * deltatime / 1000 );
        else                            // dir = 1 ou 2
            pos_x[i] -= (int)(VITESSE_BOULE * deltatime / 1000 );

        if (dir[i] < 2)                    // dir = 0 ou 1
            pos_y[i] -= (int)(VITESSE_BOULE * deltatime / 1000 );
        else                            // dir = 2 ou 3
            pos_y[i] += (int)(VITESSE_BOULE * deltatime / 1000 );

        if (pos_x[i] < 0 )
        {
            if ( dir[i] == 1 ) dir[i] = 0;
            if ( dir[i] == 2 ) dir[i] = 3;
        }
        if (pos_x[i] > 640 - boules[i]->w)
        {
            if ( dir[i] == 0 ) dir[i] = 1;
            if ( dir[i] == 3 ) dir[i] = 2;
        }
        if (pos_y[i] < 0 )
        {
            if ( dir[i] == 0 ) dir[i] = 3;
            if ( dir[i] == 1 ) dir[i] = 2;
        }
        if (pos_y[i] > 480 - boules[i]->h)
        {
            if ( dir[i] == 2 ) dir[i] = 1;
            if ( dir[i] == 3 ) dir[i] = 0;
        }
            
        boule_rect.x = pos_x[i];
        boule_rect.y = pos_y[i];
        boule_rect.w = boules[i]->w;
        boule_rect.h = boules[i]->h;

        SDL_BlitSurface( boules[i], NULL, Screen, &boule_rect );
    }
    SDL_Flip( Screen );
}
void Frame( void ) 
{ 
   SDL_Event event; 
   Uint32 curtime;
   Uint32 deltatime;


   curtime = SDL_GetTicks();
    while (1)
    {
        deltatime = SDL_GetTicks() - curtime;
        UpdateScreen( deltatime );
        curtime += deltatime;


        SDL_PollEvent( &event );
        switch (event.type)
        {
            case SDL_KEYDOWN:
            if ( event.key.keysym.sym == SDLK_ESCAPE )
            {
                SDL_Quit();
                return;
            }
            break;
            case SDL_QUIT:
                SDL_Quit();
                return;
            break;
        }
    }
} 

int main( int argc, char* argv[] ) 
{ 
   if ( Init() ) 
   {
       if ( Precache() )
          Frame(); 
   } 
    
   return 0; 
}