Allegro 5

Programmation de jeux en C ou C++


précédentsommairesuivant

X. Animations, sprites

X-A. Introduction

« Sprite » possède plusieurs traductions en français, dont lutins et farfadets. En programmation de jeux, c'est une petite image représentant un personnage, un élément fixe ou mobile dans le jeu. Souvent il désigne une petite séquence d'animation composée d'un ensemble de petites images formant un mini dessin animé, un personnage qui se déplace en marchant, un personnage qui tire au revolver, une figure de combat en arts martiaux, un monstre qui vole, une explosion, etc. Dans la suite de cet ouvrage, nous conserverons le terme de sprite sans traduction en français.

X-B. Un personnage marchant sur place

Pour tester les sprites, nous allons reprendre un ancien exemple de la bibliothèque Allegro : un personnage qui marche sur place. En voici les images :

Image non disponible

Comme nous avons besoin d'images nous devons inclure le fichier d'en-tête du module spécialisé pour les images :

 
Sélectionnez
#include <allegro5/allegro_image.h>

et sans oublier de l'initialiser dans le main() :

 
Sélectionnez
if(!al_init_image_addon())
    erreur("al_init_image_addon()");

Ensuite le premier point est de récupérer les images de l'animation. Elles sont stockées dans un tableau de pointeurs bitmap assorti d'une constante qui conserve le nombre des bitmaps :

 
Sélectionnez
const int nbimages=10;
ALLEGRO_BITMAP* anim[nbimages];

Chaque image de l'animation est sur un fichier .bmp à part et l'ensemble est dans un dossier nommé « personnage ». La récupération des images est simplifiée si les images portent toutes le même nom avec juste un numéro pour les différencier. Nous avons nommé nos images pers0.bmp, pers1.bmp, pers2.bmp, etc. jusqu'à pers9.bmp. De la sorte, toutes les images de l'animation peuvent être récupérées dans une boucle. L'indice donne le numéro de l'image et le nom complet est reconstitué avec la fonction standard sprintf() . Rappelons que sprintf() fonctionne comme un printf() mais, à la différence de printf() , elle sort une chaîne de caractères dans un tableau de char et non dans la fenêtre console.

Notons également que sprintf() est dans stdio.h et qu'il faut inclure cette en-tête pour pouvoir l'utiliser.

Ainsi la récupération des images dans le programme s'effectue de la façon suivante :

 
Sélectionnez
char nom[256] ;
for ( i=0; i<nbimages; i++){
    sprintf(nom,"personnage/pers%d.bmp",i);
    anim[i]=al_load_bitmap(nom);
    if(!anim[i])
        erreur("al_load_bitmap()");
}

À chaque tour de boucle, le chemin d'accès de l'image dont le numéro correspond à l'indice est stocké dans le tableau de char nom. La chaîne ainsi obtenue est utilisée ensuite pour récupérer l'image avec la fonction al_load_bitmap().

Dans notre programme d'expérimentation, la position de l'image est fixe. Mais dans la plupart des cas, elle sera amenée à varier avec les déplacements du personnage. Dans cette perspective future, la position de l'image est stockée avec deux variables posx et posy.

Le principe de l'animation est celui d'un petit dessin animé. Les images qui décomposent le mouvement doivent se succéder les unes après les autres dans l'ordre où elles sont rangées dans le tableau. C'est le rôle d'une variable nommée cmptimage . Au départ, cmptimage est à 0 et l'image 0 est affichée. Puis à chaque tour, cmptimage est incrémentée de un et l'image correspondante dans le tableau est affichée. Lorsque cmptimage dépasse le nombre d'images contenues dans le tableau, la variable cmptimage est remise à 0.

Nous avons utilisé les valeurs de permutation ALLEGRO_FLIP_HORIZONTAL et ALLEGRO_FLIP_VERTICAL afin que la fonction al_draw_bitmap() exécute des bascules (flip) horizontale, verticale et même les deux à la fois. Cette valeur est stockée dans la variable flag et cette valeur change si l'on tape sur la touche [Entrée]. L'image reste toujours centrée au milieu de l'écran.

Voici les initialisations de départ :

 
Sélectionnez
posx=(scrx-al_get_bitmap_width(anim[0]))/2;
posy=(scry-al_get_bitmap_height(anim[0]))/2;
// initialisation du compteur d'images à 0
cmptimage=0;
flag=0;

La succession des images est dirigée par le minuteur (timer). À chaque événement timer, nous affichons l'image courante de l'animation et incrémentons le compteur d'images :

 
Sélectionnez
al_draw_bitmap(anim[cmptimage],posx,posy,flag);
cmptimage=(cmptimage+1)%nbimages;
al_flip_display();

X-B-1. Expérimentation

Voici le code complet :

Un personnage marche sur place
Sélectionnez
// pour Visual C++ uniquement afin de lever l'interdiction 
// d'user de la fonction sprintf jugée "unsafe".
#define _CRT_SECURE_NO_WARNINGS

#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <allegro5/allegro_image.h>
#include <stdio.h>

void erreur(const char*txt)
{
    ALLEGRO_DISPLAY*d;
    d = al_is_system_installed() ? al_get_current_display() : NULL;
    al_show_native_message_box(d, "ERREUR", txt, NULL, NULL, 0);
    exit(EXIT_FAILURE);
}
/*****************************************************************
*****************************************************************/
int main()
{
    ALLEGRO_DISPLAY*display;
    ALLEGRO_EVENT_QUEUE*queue;
    ALLEGRO_TIMER*timer;
    int scrx = 800;
    int scry = 600;
    bool fin = 0;

    // stockage de l'animation
    const int nbimages = 10;
    ALLEGRO_BITMAP*anim[nbimages];
    // position, compteur
    int posx, posy, cmptimage, flag, i;
    char nom[256];

    if (!al_init())
        erreur("al_init()");

    if (!al_install_keyboard())
        erreur("al_install_keyboard()");

    if (!al_init_image_addon())
        erreur("al_init_image_addon()");

    display = al_create_display(scrx, scry);
    if (!display)
        erreur("al_create_display()");

    queue = al_create_event_queue();
    if (!queue)
        erreur("al_create_event_queue()");

    timer = al_create_timer(1.0 / 30);
    if (!timer)
        erreur("al_create_timer()");

    // enregistrement événements
    al_register_event_source(queue,
        al_get_display_event_source(display));
    al_register_event_source(queue,
        al_get_keyboard_event_source());
    al_register_event_source(queue,
        al_get_timer_event_source(timer));


    // charger l'animation
    for (i = 0; i<nbimages; i++){
        sprintf(nom, "personnage/pers%d.bmp", i);
        anim[i] = al_load_bitmap(nom);
        if (!anim[i])
            erreur("al_load_bitmap()");

    }
    // initialisation du personnage au centre
    posx = (scrx - al_get_bitmap_width(anim[0])) / 2;
    posy = (scry - al_get_bitmap_height(anim[0])) / 2;
    // initialisation du compteur d'images à 0
    cmptimage = 0;
    flag = 0;

    al_start_timer(timer);
    while (!fin){

        ALLEGRO_EVENT event;
        al_wait_for_event(queue, &event);

        if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
            fin = true;
        else if (event.type == ALLEGRO_EVENT_KEY_DOWN){
            switch (event.keyboard.keycode){

                // changer le sens de l'animation
            case ALLEGRO_KEY_ENTER:
                if (flag == 0)
                    flag = ALLEGRO_FLIP_HORIZONTAL;
                else if (flag == ALLEGRO_FLIP_HORIZONTAL)
                    flag = ALLEGRO_FLIP_VERTICAL;
                else if (flag == ALLEGRO_FLIP_VERTICAL)
                    flag |= ALLEGRO_FLIP_HORIZONTAL;
                else
                    flag = 0;
                break;

            case ALLEGRO_KEY_ESCAPE:
                fin = true;
                break;

            }
        }
        else if (event.type == ALLEGRO_EVENT_TIMER){

            // mouvement
            al_draw_bitmap(anim[cmptimage], posx, posy, flag);
            cmptimage = (cmptimage + 1) % nbimages;

            al_flip_display();
        }
    }
    for (i = 0; i<nbimages; i++)
        al_destroy_bitmap(anim[i]);
    al_destroy_display(display);
    al_destroy_timer(timer);
    al_destroy_event_queue(queue);
    return 0;
}

X-C. Un personnage piloté par le clavier

Par rapport au programme précédent, nous souhaitons faire avancer le personnage en le dirigeant avec les touches du clavier. Selon la direction choisie, l'animation n'est pas la même. Il y a quatre directions nord, est, sud et ouest ayant respectivement pour valeur 0, 1, 2, 3. Et pour expérimenter, nous avons pris une séquence d'animation minimaliste de deux images par direction, soit huit images au total.

Image non disponible

Elles sont récupérées et stockées dans un tableau rangées dans cet ordre :

nord : indices 0 et 1

est : indices 2 et 3

sud : indices 4 et 5,

ouest : indices 6 et 7.

Voici le tableau de huit pointeurs bitmap :

 
Sélectionnez
ALLEGRO_BITMAP* anim[8];

Le chargement des images se fait comme dans le programme précédent et il faut être attentif à l'ordre dans lequel les images sont rangées dans le tableau. Comme précédemment, nous avons numéroté soigneusement les fichiers en fonction des animations et par directions. Ils se nomment z0.bmp, z1.bmp, z2.bmp … z7.bmp. Ils sont tous regroupés dans un dossier nommé « zelda » et nous utilisons la fonction sprintf() pour reconstituer le chemin d'accès de chaque fichier. Voici la boucle de récupération des images :

 
Sélectionnez
for (i=0;i<8;i++){
    sprintf(nom,"zelda/z%d.bmp",i);
    anim[i]=al_load_bitmap(nom);
    if(!anim[i])
        erreur("al_load_bitmap()");
}

Le personnage a une position, une direction, un compteur d'image et l'indice de l'image courante, ce sont les variables :

 
Sélectionnez
int posx, posy; // position
int dir, cmptimage, image;// direction, compte image, indice
  • dir est une valeur entre 0 et 3 (Nord : 0, Est : 1, Sud : 2, Ouest : 3). La direction est mise à jour selon la touche flèche appuyée dans la boucle d'événements.
  • cmptimage compte les images de l'animation dans une direction, il y a deux images par direction alors cmptimage vaut 0 ou 1. La variable cmptimage est mise à jour à chaque événement du minuteur (timer) de la façon suivante :
 
Sélectionnez
cmptimage = 1-cmptimage ;
// Si cmptimage vaut 1 ça fait 0 et si cmptimage vaut 0 ça fait 1.
  • image vaut :
 
Sélectionnez
(dir*2) + cmptimage

dir*2 désigne la série pour une direction. En effet il y a deux images par direction alors :

 
Sélectionnez
si dir vaut 0 ce sont les images 0, 1
si dir vaut 1 ce sont les images 2, 3
si dir vaut 2 les images 4 et 5
et si dir vaut 3 les images 6 et 7.

À quoi s'ajoute cmptimage qui vaut 0 pour la première image et 1 pour la seconde. La variable image est utilisée au moment de l'affichage lors de l'appel de la fonction al_draw_bitmap() .

Ces variables direction, compteur et identificateur d'image sont initialisées à 0 au départ et le personnage centré. Soit l'initialisation :

 
Sélectionnez
posx=(scrx-al_get_bitmap_width(anim[0]))/2;
posy=(scry-al_get_bitmap_height(anim[0]))/2;
dir=cmptimage=image=0;

Pour ce qui est de la gestion du clavier, nous avons repris le clavier fluide vu dans la section Donner de la fluidité aux mouvements du rectangle du chapitre Les événementsÉvénements. L'état des touches flèche, appuyé ou levé, est stocké dans un tableau de booléens qui répond aux événements KEY_DOWN et KEY_UP . Chaque flèche correspond à un indice du tableau et ces indices sont désignés par des constantes nommées dans un enum :

 
Sélectionnez
enum{UP,RIGHT,DOWN,LEFT,KEYMAX};
bool key[KEYMAX]={false};

Voici le programme complet :

Un personnage piloté avec le clavier
Sélectionnez
// pour Visual C++ uniquement afin de lever l'interdiction 
// d'user de la fonction sprintf jugée "unsafe".
#define _CRT_SECURE_NO_WARNINGS

#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <allegro5/allegro_image.h>
#include <stdio.h>

void erreur(const char*txt)
{
    ALLEGRO_DISPLAY*d;
    d = al_is_system_installed() ? al_get_current_display() : NULL;
    al_show_native_message_box(d, "ERREUR", txt, NULL, NULL, 0);
    exit(EXIT_FAILURE);
}
/*****************************************************************
*****************************************************************/
int main()
{
    ALLEGRO_DISPLAY*display;
    ALLEGRO_EVENT_QUEUE*queue;
    ALLEGRO_TIMER*timer;

    int scrx = 800;
    int scry = 600;
    bool fin = 0;
    bool dessine = true;

    // clavier fluide
    enum{ UP, RIGHT, DOWN, LEFT, KEYMAX };
    bool key[KEYMAX] = { false };

    // images et sélection des images. Les images sont rangées
    // dans l'ordre : UP,RIGTH,DOWN,LEFT avec deux images par
    // direction.
    ALLEGRO_BITMAP*anim[8];
    int dir, cmptimage, image;
    int posx, posy, i;
    char nom[256];

    if (!al_init())
        erreur("al_init()");

    if (!al_install_keyboard())
        erreur("al_install_keyboard()");

    display = al_create_display(scrx, scry);
    if (!display)
        erreur("al_create_display()");

    // fond en rouge 
    // al_clear_to_color(al_map_rgb(255,0,0)) ;

    if (!al_init_image_addon())
        erreur("al_init_image_addon()");

    queue = al_create_event_queue();
    if (!queue)
        erreur("al_create_event_queue()");

    timer = al_create_timer(1.0 / 25);
    if (!timer)
        erreur("al_create_timer()");

    // enregistrement événements
    al_register_event_source(queue,
        al_get_display_event_source(display));
    al_register_event_source(queue,
        al_get_keyboard_event_source());
    al_register_event_source(queue,
        al_get_timer_event_source(timer));

    // initialisation personnage. Attention les images doivent
    // correspondre aux directions dans l'ordre
    for (i = 0; i<8; i++){
        sprintf(nom, "zelda/z%d.bmp", i);
        anim[i] = al_load_bitmap(nom);
        if (!anim[i])
            erreur("al_load_bitmap()");
    }
    // centré au départ
    posx = (scrx - al_get_bitmap_width(anim[0])) / 2;
    posy = (scry - al_get_bitmap_height(anim[0])) / 2;
    dir = cmptimage = image = 0;

    al_start_timer(timer);
    while (!fin){

        ALLEGRO_EVENT event;
        al_wait_for_event(queue, &event);

        if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
            fin = true;
        else if (event.type == ALLEGRO_EVENT_KEY_DOWN){
            switch (event.keyboard.keycode){
            case ALLEGRO_KEY_UP:
                key[UP] = true;
                dir = 0;
                break;
            case ALLEGRO_KEY_RIGHT:
                key[RIGHT] = true;
                dir = 1;
                break;
            case ALLEGRO_KEY_DOWN:
                key[DOWN] = true;
                dir = 2;
                break;
            case ALLEGRO_KEY_LEFT:
                key[LEFT] = true;
                dir = 3;
                break;
            case ALLEGRO_KEY_ESCAPE:
                fin = true;
                break;
            }
            // sélection de l'image selon direction et sur
            // événement key_up. 2 images par direction
            // rangées aux indices 01-23-45-67
            cmptimage = 1 - cmptimage; // 0 ou 1
            image = (dir * 2) + cmptimage;
        }
        else if (event.type == ALLEGRO_EVENT_KEY_UP){
            switch (event.keyboard.keycode){
            case ALLEGRO_KEY_UP: key[UP] = false; break;
            case ALLEGRO_KEY_RIGHT: key[RIGHT] = false; break;
            case ALLEGRO_KEY_DOWN: key[DOWN] = false; break;
            case ALLEGRO_KEY_LEFT: key[LEFT] = false; break;
            case ALLEGRO_KEY_ESCAPE: fin = true; break;
            }
        }
        else if (event.type == ALLEGRO_EVENT_TIMER){

            // avancer
            posy -= 10 * key[UP];
            posx += 10 * key[RIGHT];
            posy += 10 * key[DOWN];
            posx -= 10 * key[LEFT];

            // redessiner
            dessine = true;
        }

        if (dessine == true && al_is_event_queue_empty(queue)){

            al_clear_to_color(al_map_rgb(0, 0, 0));
            // al_clear_to_color(al_map_rgb(255,0,0));

            al_draw_bitmap(anim[image], posx, posy, 0);
            al_flip_display();

            dessine = false;
        }
    }
    for (i = 0; i<8; i++)
        al_destroy_bitmap(anim[i]);
    al_destroy_display(display);
    al_destroy_timer(timer);
    al_destroy_event_queue(queue);
    return 0;
}

Pendant le fonctionnement du programme nous pouvons constater deux problèmes :

  1. Si vous mettez la couleur de fond en rouge (en commentaire par défaut dans le code) avec al_clear_to_color() , vous constatez que le personnage se déplace dans un carré noir. Il faut faire en sorte que ce noir devienne transparent.
  2. Par ailleurs, l'animation est presque invisible. Certes, le personnage est petit mais ce n'est pas seulement à cause de sa petite taille, c'est à cause de la rapidité de l'animation. L'animation est trop rapide et la solution n'est pas de ralentir le minuteur, car cela ralentirait aussi tous les autres affichages du jeu.

X-D. Gestion de la couleur du masque

Dans une image, il est possible de masquer à l'affichage tous les pixels d'une couleur préalablement sélectionnée. Cette couleur est appelée la couleur de masque. Elle a pour effet d'être transparente (les pixels de cette couleur ne sont pas affichés). La sélection de cette couleur se fait avec un appel à :

 
Sélectionnez
void al_convert_mask_to_alpha(ALLEGRO_BITMAP *bmp, ALLEGRO_COLOR mask_color)

Pour la bitmap passée au premier paramètre bmp, la couleur de masque devient la couleur mask_color passée au second paramètre. La couleur de masque est propre à la bitmap et chaque bitmap dans un programme peut avoir une couleur de masque différente.

Dans le programme ci-dessus pour faire disparaître le noir autour du personnage, il suffit de déclarer le noir comme couleur de masque pour toutes les images de l'animation. Par mesure de prudence, j'ai pris le pixel du coin haut gauche sachant que c'est la bonne couleur. Il convient d'être vigilant dans son traitement d'image parce qu'une couleur avec les valeurs (1, 0, 0) est différente d'une couleur avec les valeurs (0, 0, 0), bien que cela soit invisible à l'œ il. Si le fond de l'image n'est pas absolument uni, utiliser un logiciel de retouche comme Photoshop, The Gimp, Paint etc. pour avoir un fond parfaitement uni.

L'appel de la fonction al_convert_mask_to_alpha() est fait au moment du load :

 
Sélectionnez
    // (...)

    // initialisation personnage. Attention les images doivent
    // correspondre aux directions dans l'ordre
    for (i = 0; i<8; i++){
        sprintf(nom, "zelda/z%d.bmp", i);
        anim[i] = al_load_bitmap(nom);
        if (!anim[i])
            erreur("al_load_bitmap()");
            
        al_convert_mask_to_alpha(anim[i], al_get_pixel(anim[i], 0, 0));
    }

    // (...)

Avec cette modification apportée au programme, le personnage se déplace sur le fond rouge sans le carré noir.

La couleur de masque d'une image peut être changée pendant le fonctionnement du programme. Il n'est pas nécessaire qu'elle reste constante. Ainsi différentes zones de l'image peuvent devenir transparentes tour à tour.

X-E. Contrôle de la vitesse de l'animation

Le deuxième point est celui de la vitesse de l'animation. Notre personnage est trop rapide non dans les distances parcourues mais dans la succession des mouvements produits par l'animation. En effet, à chaque affichage, l'animation passe à l'image suivante. Pour ralentir l'animation, il faut que chaque image de l'animation reste plusieurs tours avant de passer à la suivante. Pour ce faire, nous allons ajouter un compteur de tours et l'incrémentation de l'identificateur d'image courante se fera tous les N tours. Dans la boucle principale nous aurons une séquence du type :

 
Sélectionnez
if (compte++ > nbTours){
    compte=0 ;
    image=(image+1)%NBIMAGE ;
}

Avec compte pour compter les tours, nbTours pour le nombre de tours où la même image de l'animation est affichée, image pour identifier l'image courante dans l'animation et NBIMAGE qui donne le nombre total des images de l'animation. On voit bien que image augmente de un uniquement lorsque compte atteint nbTours . compte est alors remis à 0 et ça recommence.

X-E-1. Expérimentation

Le programme pour illustrer cette question est un chat qui traverse en boucle l'écran de la gauche vers la droite. Une fois sorti à droite, il réapparaît sur la gauche. En voici les images :

Image non disponible

La couleur magenta légèrement violette qui entoure le personnage s'explique parce que dans la version 4 d'Allegro, il y avait une seule couleur de masque et c'était cette couleur obtenue avec le mélange 255 de rouge, 0 vert et 255 de bleu. Les sprites utilisés ici sont les mêmes que ceux utilisés dans un livre paru en anglais sur la version 4 d'Allegro (Jonathan Harbour, Game programming All in One, Thomson course technology, 2004).

La récupération des images de l'animation se fait de façon identique aux programmes précédents sur l'animation. Ensuite les variables nécessaires sont initialisées. L'identificateur d'image image , le compteur compte et le nombre de tours nbTours sont mis à 0 :

 
Sélectionnez
image=compte= nbTours= 0;

La position posx, posy du personnage est au départ à mi-hauteur sur la marge gauche :

 
Sélectionnez
posx=0;
posy=(scry - al_get_bitmap_height(anim[0])) / 2;

Du point de vue de l'action, la vitesse du déplacement est toujours la même ; il n'y a que la vitesse de l'animation du mouvement qui change. Elle est contrôlée avec [Flèche en haut] et [Flèche en bas]. La touche [Flèche en haut] augmente le nombre de tours nécessaire pour le changement d'image et ainsi ralentit l'animation. Les mouvements sont de plus en plus lents. La touche [Flèche en bas] a l'effet inverse. Elle diminue le nombre de tours nécessaires au changement d'image avec pour conséquence d'accélérer le mouvement.

Le déplacement entièrement automatique est effectué à chaque événement du minuteur (timer). De même pour l'animation. Le compte du nombre de tours effectués est augmenté de 1 à chaque événement du minuteur. Lorsqu'il atteint le nombre déterminé par la variable nbTours , l'identificateur d'image image est incrémenté de 1 et le compte du nombre de tours repart à 0. L'affichage a lieu juste après. Il prend dans le tableau des images de l'animation l'image correspondant à l'identificateur d'image image .

Voici le programme complet :

Contrôler la vitesse de l'animation
Sélectionnez
// pour Visual C++ uniquement afin de lever l'interdiction 
// d'user de la fonction sprintf jugée "unsafe".
#define _CRT_SECURE_NO_WARNINGS

#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <allegro5/allegro_image.h>
#include <stdio.h>
#include <time.h>

void erreur(const char*txt)
{
    ALLEGRO_DISPLAY*d;
    d = al_is_system_installed() ? al_get_current_display() : NULL;
    al_show_native_message_box(d, "ERREUR", txt, NULL, NULL, 0);
    exit(EXIT_FAILURE);
}
/*****************************************************************
*****************************************************************/
int main()
{
    ALLEGRO_DISPLAY*display;
    ALLEGRO_EVENT_QUEUE*queue;
    ALLEGRO_TIMER*timer;

    int scrx = 800;
    int scry = 600;

    bool fin = 0;

    const int NBIMAGE = 6;
    ALLEGRO_BITMAP*anim[NBIMAGE];
    int compte, nbTours, image, i;
    char nom[256];
    int posx, posy; // position de l'animation

    srand(time(NULL));
    if (!al_init())
        erreur("al_init()");

    if (!al_init_image_addon())
        erreur("al_init_image_addon()");

    if (!al_install_keyboard())
        erreur("al_install_keyboard()");

    display = al_create_display(scrx, scry);
    if (!display)
        erreur("al_create_display()");

    queue = al_create_event_queue();
    if (!queue)
        erreur("al_create_event_queue()");

    timer = al_create_timer(1.0 / 30);
    if (!timer)
        erreur("al_create_timer()");

    // enregistrement événements
    al_register_event_source(queue,
        al_get_display_event_source(display));
    al_register_event_source(queue,
        al_get_keyboard_event_source());
    al_register_event_source(queue,
        al_get_timer_event_source(timer));

    // récupération des images
    for (i = 0; i<NBIMAGE; i++){
        sprintf(nom, "cat/cat%d.bmp", i);
        anim[i] = al_load_bitmap(nom);
        if (!anim[i])
            erreur("al_load_bitmap()");
        al_convert_mask_to_alpha(anim[i], al_get_pixel(anim[i], 0, 0));
    }
    // initialisations
    image = compte = 0;
    nbTours = 0;
    posx = 0;
    posy = (scry - al_get_bitmap_height(anim[0])) / 2;

    // démarrer le timer
    al_start_timer(timer);

    while (!fin){

        ALLEGRO_EVENT event;
        al_wait_for_event(queue, &event);

        if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
            fin = true;
        else if (event.type == ALLEGRO_EVENT_KEY_DOWN){
            switch (event.keyboard.keycode){

                // change le défilement de l'animation
                case ALLEGRO_KEY_UP:
                    nbTours = (nbTours<40) ? nbTours + 1 : 40;
                    break;
                case ALLEGRO_KEY_DOWN:
                    nbTours = (nbTours>0) ? nbTours - 1 : 0;
                    break;

                case ALLEGRO_KEY_ESCAPE:
                    fin = true;
                    break;

            }
            printf("nbTours : %d\n", nbTours);
        }
        else if (event.type == ALLEGRO_EVENT_TIMER){

            // mouvement
            posx += 5;
            if (posx>scrx)
                posx = -al_get_bitmap_height(anim[0]);

            // animation
            if (compte++ > nbTours){
                compte = 0;
                image = (image + 1) % NBIMAGE;
            }

            //affichage
            al_clear_to_color(al_map_rgb(0, 0, 0));
            al_draw_bitmap(anim[image], posx, posy, 0);
            al_flip_display();

        }
    }
    for (i = 0; i<NBIMAGE; i++)
        al_destroy_bitmap(anim[i]);

    al_destroy_display(display);
    al_destroy_timer(timer);
    al_destroy_event_queue(queue);
    return 0;
}

Appuyer sur [Flèche en haut] augmente nbTours et donc ralentit l'animation. Au contraire, appuyer sur [Flèche en bas] diminue nbTours et accélère l'animation. Le déplacement en revanche est constant indépendamment de la vitesse de l'animation. Le nombre de tours nbTours est affiché dans la fenêtre console.

Bien entendu la vitesse attribuée au minuteur (timer) joue elle aussi un rôle. Nous avons pris 1/30 de seconde, ce qui est n'est pas très rapide.

X-F. Récupération d'une série d'images sur un fichier unique

Il peut être pratique d'avoir toutes les images de l'animation sur un seul fichier, voire de regrouper toutes les animations du programme sur un même fichier. Ce fichier sera une grande image bitmap, jpeg ou png contenant toutes les séquences d'images des animations. Chaque animation doit y être soigneusement rangée en lignes et colonnes. Voici par exemple nos trois séquences précédentes regroupées sur un seul fichier image :

Image non disponible

Pour récupérer une image dans une des trois suites d'images ci-dessus, il faut connaître :

  • La position dans la grande bitmap conteneur du coin haut-gauche de la première image de la série.
  • La taille horizontale et verticale des images de l'animation.
  • Le nombre de colonnes du rangement des images.
  • Et le numéro de l'image à récupérer (on considère qu'elles sont numérotées de 0 à NBIMAGE-1 (0 à 9 pour le personnage, 0 à 5 pour le chat, 0 à 7 pour Zelda).
  • Éventuellement, si vos images sont séparées par un espace, cet espace doit être régulier et il devra ensuite être comptabilisé dans l'algorithme : la taille d'une image ne change pas mais le saut d'une image à l'autre en est allongé. Pour la suite nous avons omis ce détail et considéré que les images sont juxtaposées sans espace pour les séparer.

Avec ces informations la fonction recup_sprite() retourne une copie de l'image souhaitée.

 
Sélectionnez
ALLEGRO_BITMAP*recup_sprite(ALLEGRO_BITMAP*source,int tx,int ty, int startx,int starty,int colonne,int i)

Cette fonction retourne une région de la bitmap source de taille tx sur ty à partir de la position startx , starty et selon le nombre de colonnes colonne . Cette région correspond à la ième image d'une des animations.

Exemple d'utilisation, récupération de la suite du personnage. La bitmap all correspond à la grande bitmap conteneur. Elle est sauvée sur le disque avec le nom « all_sprites.bmp ». Il faut commencer par la charger :

 
Sélectionnez
ALLEGRO_BITMAP*all;
all=al_load_bitmap("all_sprites.bmp");

Ensuite la série d'images à récupérer est stockée dans un tableau de bitmap :

 
Sélectionnez
const int NBIMAGE=10;
ALLEGRO_BITMAP*anim[NBIMAGE];

La récupération de chaque image suppose une boucle sur le nombre d'images de la suite. Chaque image a une taille de 32x32 pixels. La première sur notre fichier commence à 0 à l'horizontal et à 100 à la verticale. Il y a 5 colonnes et i prend successivement le numéro de chaque image :

 
Sélectionnez
for(i=0;i<NBIMAGE;i++){
    anim[i]=recup_sprite(all,32,32,0,100,5,i);
    if(!anim[i])
        erreur("recup_sprite()");
    al_convert_mask_to_alpha(anim[i],al_get_pixel(anim[i],0,0));
}

Voici le code de la fonction de récupération :

 
Sélectionnez
ALLEGRO_BITMAP*recup_sprite(
        ALLEGRO_BITMAP*scr, // bitmap conteneur
        int tx,int ty, // taille élément
        int startx,int starty, // coin haut gauche de départ
        int colonne, // nombre de colonnes
        int i) // ième élément
{
    ALLEGRO_BITMAP*sprite=NULL;
    int x,y;
    sprite=al_create_bitmap(tx,ty);
    if(sprite!=NULL){
        // attention colonne doit toujours être > 0
        x=startx+(i%colonne)*tx;
        y=starty+(i/colonne)*ty;
        al_set_target_bitmap(sprite);
        al_draw_bitmap_region(scr,x,y,tx,ty,0,0,0);
    }
    return sprite;
}

À partir du coin haut gauche de départ, de la taille de chaque image, du nombre de colonnes et du numéro d'ordre de l'image, la fonction retourne une copie de la région concernée. Attention, la bitmap cible pour les opérations de dessin ou d'affichage est la bitmap retournée. Il ne faut pas oublier ensuite un appel à :

 
Sélectionnez
al_set_target_backbuffer(display);

pour revenir à des opérations de dessin et d'affichage dans la fenêtre ou écran courant.

X-F-1. Expérimentation : une balle rebondit lorsqu'elle touche un bord

Dans ce programme, toutes les images de l'animation, une balle qui tourne sur elle-même, sont stockées sur un même fichier bitmap nommé all_sprites.bmp.

Voici les images :

Image non disponible

Une structure t_sprite est créée qui regroupe toutes les caractéristiques d'un sprite, position, déplacement et animation. L'animation peut aller en avant (images vers la droite dans tableau) ou en arrière (images vers la gauche dans le tableau). Lorsque la balle heurte un bord, elle rebondit avec des variations pour sa vitesse de déplacement, le sens et la vitesse de l'animation.

Récupérer une série d'images regroupées sur un fichier unique
Sélectionnez
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <allegro5/allegro_image.h>
#include <stdio.h>
#include <time.h>

const int SCRX = 800;
const int SCRY = 600;
const int IMTX = 64;
const int IMTY = 64;
const int NBIMAGE = 32;

typedef struct{
    // déplacement
    float x, y; // position
    float dx, dy; // déplacement

    // image
    int tx, ty; // taille

    // animation
    int imcourante; // image courante
    int nbimage; // nombre d'images
    int tour; // compte tours
    int nbtour; // nombre de tours
    int dir; // direction de l'animation

}t_sprite;



ALLEGRO_BITMAP* recup_sprite(ALLEGRO_BITMAP*scr,
                                int tx, int ty,
                                int startx, int starty,
                                int colonne, int i);
t_sprite* init_sprite(void);
void cntl_anim(t_sprite*s);
void avance(t_sprite*s);

void erreur(const char*txt);
/*****************************************************************
*****************************************************************/
int main()
{
    ALLEGRO_DISPLAY*display;
    ALLEGRO_KEYBOARD_STATE key;
    ALLEGRO_BITMAP*all;
    ALLEGRO_BITMAP*anim[NBIMAGE];
    int i;

    t_sprite*balle;

    srand(time(NULL));
    if (!al_init())
        erreur("al_init()");

    if (!al_init_image_addon())
        erreur("al_init_image_addon()");

    if (!al_install_keyboard())
        erreur("al_install_keyboard()");

    display = al_create_display(SCRX, SCRY);
    if (!display)
        erreur("al_create_display()");

    //Récupération de l'image générale
    all = al_load_bitmap("all_sprites.bmp");
    if (!all)
        erreur("al_load_bitmap()");

    // découpe de l'animation (taille images : 64x64)
    for (i = 0; i<NBIMAGE; i++){
        anim[i] = recup_sprite(all, IMTX, IMTY, 0, 0, 8, i);
        if (!anim[i])
            erreur("recup_sprite()");
        al_convert_mask_to_alpha(anim[i],
            al_get_pixel(anim[i], 0, 0));
    }
    // revenir à l'affichage écran
    al_set_target_backbuffer(display);

    // allocation et initialisation d'un t_sprite
    balle = init_sprite();

    do{
        al_get_keyboard_state(&key);

        // contrôle animation
        cntl_anim(balle);

        // avancer
        avance(balle);

        // affichage
        al_clear_to_color(al_map_rgb(0, 0, 0));
        al_draw_bitmap(anim[balle->imcourante],
            balle->x, balle->y, 0);
        al_flip_display();

        al_rest(1.0 / 60);

    } while (!al_key_down(&key, ALLEGRO_KEY_ESCAPE));

    for (i = 0; i<NBIMAGE; i++)
        al_destroy_bitmap(anim[i]);

    al_destroy_display(display);
    return 0;
}
/*****************************************************************
Initialisation d'un sprite
*****************************************************************/
t_sprite* init_sprite()
{
    t_sprite*s = (t_sprite*)malloc(sizeof(t_sprite));

    s->x = rand() % (SCRX - IMTX);
    s->y = rand() % (SCRY - IMTY);
    s->dx = ((float)rand() / RAND_MAX) * 6 - 3;
    s->dy = ((float)rand() / RAND_MAX) * 6 - 3;
    s->tx = IMTX; // ou al_get_bitmap_width(anim[0]);
    s->ty = IMTY;
    s->imcourante = 0;
    s->nbimage = NBIMAGE;
    s->tour = 0;
    s->nbtour = 1 + rand() % 3;
    s->dir = rand() % 2 * 2 - 1; // -1 ou 1

    return s;
}
/*****************************************************************
Récupérer sur un fichier la séquence d'animation
*****************************************************************/
ALLEGRO_BITMAP*recup_sprite(
    ALLEGRO_BITMAP*scr, // bitmap d'origine
    int tx, int ty, // taille élément
    int startx, int starty, // à partir de
    int colonne, // nombre de colonnes
    int i) // ieme élément
{
    ALLEGRO_BITMAP*sprite = NULL;
    int x, y;
    sprite = al_create_bitmap(tx, ty);
    if (sprite != NULL){
        // attention colonne doit être > 0
        x = startx + (i%colonne)*tx;
        y = starty + (i / colonne)*ty;

        al_set_target_bitmap(sprite);
        al_draw_bitmap_region(scr, x, y, tx, ty, 0, 0, 0);
    }
    return sprite;
}
/*****************************************************************
Contrôler l'animation des images (qui peut fonctionner à l'envers)
*****************************************************************/
void cntl_anim(t_sprite*s)
{
    // attention nbtour doit être > 0
    s->tour = (s->tour + 1) % s->nbtour;
    if (s->tour == 0){
        s->imcourante += s->dir;
        // rester entre 0 et (nbimage-1) compris
        s->imcourante = (s->imcourante + s->nbimage) % s->nbimage;
    }
}
/*****************************************************************
Contrôle du déplacement dans l'écran. Si un bord est touché
la balle part dans l'autre sens avec un pas et une animation
différents.
*****************************************************************/
void avance(t_sprite*s)
{
    bool res = false;

    // avancer
    s->x += s->dx;
    s->y += s->dy;

    if (s->x<0){// cntl bords horizontaux
        s->x = 0;
        s->dx = ((float)rand() / RAND_MAX) * 3;
        res = true;
    }
    else if (s->x + s->tx>SCRX){
        s->x = SCRX - s->tx;
        s->dx = ((float)rand() / RAND_MAX)*-3;
        res = true;
    }
    if (s->y<0){// cntl bords verticaux
        s->y = 0;
        s->dy = ((float)rand() / RAND_MAX) * 3;
        res = true;
    }
    else if (s->y + s->ty>SCRY){
        s->y = SCRY - s->ty;
        s->dy = ((float)rand() / RAND_MAX)*-3;
        res = true;
    }
    if (res == true){// si un bord touché
        s->nbtour = 1 + rand() % 3;
        s->dir = (rand() % 2) * 2 - 1;
    }
}
/*****************************************************************
Gestion des erreurs
*****************************************************************/
void erreur(const char*txt)
{
    ALLEGRO_DISPLAY*d;
    d = al_is_system_installed() ? al_get_current_display() : NULL;
    al_show_native_message_box(d, "ERREUR", txt, NULL, NULL, 0);
    exit(EXIT_FAILURE);
}
/*****************************************************************
*****************************************************************/

X-G. Plusieurs séquences simultanées

X-G-1. Plusieurs balles qui rebondissent

À partir du programme précédent, nous pouvons avoir très facilement de nombreuses balles simultanées à l'écran. Il suffit d'utiliser un tableau de t_sprite . Les fonctions sont exactement les mêmes : init_sprite() , cntl_anim() , avance() , etc. Chacune est appelée pour toutes les balles du tableau de t_sprite dans une boucle for . Voici le programme précédent qui tourne maintenant avec cinquante balles. Le code modifié est en gras :

De nombreuses balles rebondissent sur les bords de l'écran
Sélectionnez
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <allegro5/allegro_image.h>
#include <stdio.h>
#include <time.h>

const int SCRX = 800;
const int SCRY = 600;
const int IMTX = 64;
const int IMTY = 64;
const int NBIMAGE = 32;

const int NBBALLE = 50;

typedef struct{
    // déplacement
    float x, y; // position
    float dx, dy; // déplacement

    // image
    int tx, ty; // taille

    // animation
    int imcourante; // image courante
    int nbimage; // nombre d'images
    int tour; // compte tours
    int nbtour; // nombre de tours
    int dir; // direction de l'animation

}t_sprite;

ALLEGRO_BITMAP* recup_sprite(ALLEGRO_BITMAP*scr,
    int tx, int ty,
    int startx, int starty,
    int colonne, int i);
t_sprite* init_sprite(void);
void cntl_anim(t_sprite*s);
void avance(t_sprite*s);

void erreur(const char*txt);
/*****************************************************************
*****************************************************************/
int main()
{
    ALLEGRO_DISPLAY*display;
    ALLEGRO_KEYBOARD_STATE key;
    ALLEGRO_BITMAP*all;
    ALLEGRO_BITMAP*anim[NBIMAGE];
    int i;

    t_sprite*balle[NBBALLE];

    srand(time(NULL));
    if (!al_init())
        erreur("al_init()");

    if (!al_init_image_addon())
        erreur("al_init_image_addon()");

    if (!al_install_keyboard())
        erreur("al_install_keyboard()");

    display = al_create_display(SCRX, SCRY);
    if (!display)
        erreur("al_create_display()");

    //Récupération de l'image générale
    all = al_load_bitmap("all_sprites.bmp");
    if (!all)
        erreur("al_load_bitmap()");

    // découpe de l'animation (taille images : 64x64)
    for (i = 0; i<NBIMAGE; i++){
        anim[i] = recup_sprite(all, IMTX, IMTY, 0, 0, 8, i);
        if (!anim[i])
            erreur("recup_sprite()");
        al_convert_mask_to_alpha(anim[i],
            al_get_pixel(anim[i], 0, 0));
    }
    // revenir à l'affichage écran
    al_set_target_backbuffer(display);

    // allocation et initialisation des t_sprite
    for (i = 0; i < NBBALLE;i++)
        balle[i] = init_sprite();

    do{
        al_get_keyboard_state(&key);

        for (i = 0; i < NBBALLE; i++){
            // contrôle animation
            cntl_anim(balle[i]);
            // avancer
            avance(balle[i]);
        }

        // affichage
        al_clear_to_color(al_map_rgb(0, 0, 0));

        for (i = 0; i < NBBALLE;i++)
            al_draw_bitmap(anim[balle[i]->imcourante],balle[i]->x, balle[i]->y, 0);
        al_flip_display();

        al_rest(1.0 / 60);

    } while (!al_key_down(&key, ALLEGRO_KEY_ESCAPE));

    for (i = 0; i<NBIMAGE; i++)
        al_destroy_bitmap(anim[i]);

    al_destroy_display(display);
    return 0;
}

// (...)
/*****************************************************************
*****************************************************************/

X-G-2. Plusieurs personnages qui se déplacent

Dans un jeu, il y a en général plusieurs personnages, chacun disposant de sa propre animation. Il arrive également qu'un personnage dispose de plusieurs animations qui sont sélectionnées selon les circonstances du jeu, par exemple marcher, sauter, combattre, mourir, etc. Dans ces conditions il peut être intéressant de découpler personnage et animation afin de donner de la souplesse entre chaque personnage et la ou les animations qui le concernent.

Le programme d'expérimentation ci-dessous met en scène plusieurs personnages. Chacun dispose d'une seule animation qui est sur un fichier d'images, nous avons :

Image non disponible
Image non disponible
Image non disponible
Image non disponible
Image non disponible
Image non disponible
Image non disponible

La structure de données est en deux parties. D'un côté il y a ce qui concerne la récupération des images de l'animation et de l'autre ce qui concerne le déplacement et le mouvement du personnage.

X-G-2-a. Récupération de l'animation

Au départ les images de chaque animation sont regroupées sur un fichier (BMP, PNG, JPG). Pour les récupérer et les utiliser dans le programme, il nous faut :

  • Le nom du fichier conteneur.
  • Le point de départ de l'animation dans le fichier conteneur.
  • La taille de chaque image dans l'animation (elles sont supposées avoir toutes la même taille).
  • S'il y a ou non une image de masque.
  • Un tableau dynamique de pointeurs bitmap pour le stockage et l'utilisation de l'animation dans le programme.

Toutes ces informations sont regroupées sous la forme d'une structure :

 
Sélectionnez
typedef struct{
    char*nom;
    // fichier conteneur avec chemin d'accès
    int startx,starty; // départ dans le fichier conteneur
    int nbcolonne; // nombre de colonnes
    int nbimage // nombre d'images
    int tx,ty; // taille d'une image
    bool masque; // 0 si pas de masque, 1 si oui
    ALLEGRO_BITAMP**image; // les images de l'animation
}t_animation;

Les structures pour l'animation sont déclarées en global parce que chacune est unique. Elles sont toutes partiellement initialisées avec des valeurs connues, en dur à la déclaration. Le seul champ non initialisé à ce moment est le dernier, celui pour stocker les images de l'animation. Sur le disque dur, toutes les images sont au format PNG et elles sont supposées être dans un dossier « anim » qui est dans le répertoire du programme. Il y a un fichier image par animation ce qui donne la séquence de déclarations et d'initialisations :

 
Sélectionnez
t_animation ANIMDECOR={"anim/decor.png",0,0,1,1,640,480,0};
t_animation ANIMDRAGON={"anim/dragon.png",0,0,3,6,128,64,1};
t_animation ANIMPOISSON={"anim/poisson.png",0,0,3,3,64,32,1};
t_animation ANIMCRABE={"anim/crabe.png",0,0,4,4,64,32,1};
t_animation ANIMABEILLE={"anim/abeille.png",0,0,6,6,50,40,1};
t_animation ANIMMOUSTIQUE={"anim/moustique.png",0,0,6,6,50,40,1};
t_animation ANIMSERPENT={"anim/serpent.png",0,0,4,7,100,50,1};

La chaîne « anim/nom.bmp » fonctionne bien sous Windows avec Code::Blocks et Visual Studio C++. Éventuellement, en cas de problème avec cette chaîne utilisez la forme « .\\anim\\nom.bmp ».

La récupération des images de chaque animation est faite avec la fonction :

 
Sélectionnez
void recup_animation(t_animation*a)

Cette fonction charge l'image qui contient l'animation complète, alloue un tableau de pointeurs ALLEGRO_BITMAP* selon le nombre d'images dans l'animation et y stocke chaque image de l'animation. Chaque image est récupérée avec la fonction déjà présentée :

 
Sélectionnez
ALLEGRO_BITMAP*recup_image(ALLEGRO_BITMAP*scr,int tx,int ty, int startx,int starty,int colonne,int i)

X-G-3. Définition d'un personnage

Pour ce qui concerne le personnage et son comportement, nous avons une autre structure qui contient au minimum ce qui concerne le déplacement et le contrôle de l'animation correspondante.

Pour le déplacement :

  • Position
  • Pas d'avancement (vitesse)

Pour l'animation :

  • Indice de l'image courante dans le tableau des images de l'animation.
  • Nombre de tours pendant lequel la même image et affichée.
  • Compteur de tours écoulés avec la même image.
  • Direction de l'animation (l'animation peut aller à l'endroit ou à l'envers).
  • Pointeur sur l'animation courante, c'est-à-dire un pointeur sur une structure t_animation.

C'est la structure suivante :

 
Sélectionnez
typedef struct{
    // déplacement
    float x,y; // position
    float dx,dy; // déplacement
    // gestion animation
    int imcourante; // image courante
    int tour; // compte tours
    int nbtour; // nombre de tours
    int dir; // direction de l'animation
    t_animation*anim; // les images de l'animation
}t_sprite;

Dans le petit monde que nous allons présenter, il y a plusieurs personnages sur un décor de fond : un dragon qui vole, un poisson dans l'eau, un crabe, une abeille, un moustique et un serpent. Tous ces personnages sont des t_sprites regroupés dans un tableau de t_sprite :

 
Sélectionnez
t_sprite*all[NBSPRITE];

Ce tableau n'est pas global mais il est déclaré local dans le main() .

Chaque t_sprite du tableau est identifié avec une constante et l'ensemble de ces constantes constitue un enum :

 
Sélectionnez
enum{DECOR,DRAGON,POISSON,CRABE,ABEILLE,MOUSTIQUE,SERPENT,NBSPRITE};

L'avantage est de pouvoir accéder facilement à chaque personnage avec son nom, mais également de pouvoir les faire défiler tous avec une boucle sur le tableau all de DECOR à NBSPRITE-1 pour les différentes actions (ou éventuellement de DRAGON à NBSPRITE-1).

Le décor est déclaré en premier pour faciliter l'affichage. En effet le décor couvre tout l'écran et l'afficher efface le double buffer. Le décor est considéré comme un t_sprite ce qui permet éventuellement d'avoir un décor animé ou de gérer un scroll.

Chaque t_sprite est initialisé avec la fonction :

 
Sélectionnez
t_sprite*init_sprite(int posx,int posy,int dx, int dy,int nbtour, int dir,t_animation*a)

C'est un constructeur qui alloue un t_sprite et lui passe des valeurs pour sa position, son déplacement, le nombre de tours pour la vitesse de l'animation, la direction de l'animation et une adresse d'animation, à savoir l'adresse d'une des structures t_animation vues précédemment. Ainsi, dans le code, nous avons la séquence suivante pour l'initialisation des personnages :

 
Sélectionnez
all[DECOR]=init_sprite(0,0,0,0,0,0,&ANIMDECOR);
all[DRAGON]=init_sprite(500,0,-5,0,5,1,&ANIMDRAGON);
all[POISSON]=init_sprite(300,400,3,0,8,1,&ANIMPOISSON);
all[CRABE]=init_sprite(300,212,2,0,20,1,&ANIMCRABE);
all[ABEILLE]=init_sprite(100,122,-3,0,8,1,&ANIMABEILLE);
all[MOUSTIQUE]=init_sprite(500,70,4,0,2,1,&ANIMMOUSTIQUE);
all[SERPENT]=init_sprite(350,200,-2,0,4,1,&ANIMSERPENT);

Une boucle n'est pas possible parce que les paramètres sont fixés en dur sans relation les uns aux autres.

L'action dans la boucle d'événements consiste à avancer et animer tout le monde :

 
Sélectionnez
for (i=DECOR;i<NBSPRITE;i++){
    avance_sprite(all[i]);
    anime_sprite(all[i]);
    affiche_sprite(all[i]);
}

Le décor n'avance pas et ne comporte pas d'animation mais après tout ça pourrait. En l'occurrence ça ne gêne pas parce que toutes les valeurs du décor concernées par ces actions sont à 0.

La fonction pour avancer est la suivante :

 
Sélectionnez
void avance_sprite(t_sprite*s)
{
    s->x += s->dx;
    s->y += s->dy;
    //sortie à gauche entrée à droite
    if (s->x + s->anim->tx < 0)
        s->x = SCRX;
    // sortie à droite entrée à gauche
    else if (s->x >SCRX)
        s->x = -s->anim->tx;
    //sortie en haut entrée en bas
    if (s->y + s->anim->ty < 0)
        s->y = SCRY;
    // sortie à droite entrée à gauche
    else if (s->y >SCRY)
        s->y = -s->anim->ty;
}

Elle reçoit en référence une structure t_sprite . Elle modifie sa position avec un contrôle des bords : un personnage qui sort d'un côté entre de l'autre.

La fonction d'animation est la suivante :

 
Sélectionnez
void anime_sprite(t_sprite*s)
{
    if(++s->tour > s->nbtour) {
        s->imcourante =
                (s->imcourante + s->dir + s->anim->nbimage)%s->anim->nbimage;
        s->tour=0;
    }
}

Elle incrémente l'image courante selon la direction choisie (de gauche à droite ou de droite à gauche) tout en veillant à bien rester dans la fourchette du nombre d'images nbimage . L'accès à la structure t_animation se fait avec le pointeur selon une double indirection. D'abord vers le champ anim puis vers les champs de l'animation :

 
Sélectionnez
s->anim-> ...

L'affichage est juste un appel à la fonction al_draw_bitmap() selon les paramètres du t_sprite à afficher :

 
Sélectionnez
void affiche_sprite(t_sprite*s)
{
    al_draw_bitmap(s->anim->image[s->imcourante],s->x,s->y,0);
}

Voici le code complet du programme :

Plusieurs personnages se déplacent sur un décor
Sélectionnez
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <allegro5/allegro_image.h>

// écran
const int SCRX = 640;
const int SCRY = 480;

// animation
typedef struct{
    char*nom; // fichier conteneur
    int startx, starty; // départ dans le fichier conteneur
    int nbcolonne; // nombre de colonne
    int nbimage; // nombre d'images
    int tx, ty; // taille d'une image
    bool masque; // 0 si pas de masque, 1 si oui
    ALLEGRO_BITMAP**image; // les images de l'anim
}t_animation;

enum{ DECOR, DRAGON, POISSON, CRABE, ABEILLE, MOUSTIQUE, SERPENT, NBSPRITE };
t_animation ANIMDECOR = { "anim/decor.png", 0, 0, 1, 1, 640, 480, 0 };
t_animation ANIMDRAGON = { "anim/dragon.png", 0, 0, 3, 6, 128, 64, 1 };
t_animation ANIMPOISSON = { "anim/poisson.png", 0, 0, 3, 3, 64, 32, 1 };
t_animation ANIMCRABE = { "anim/crabe.png", 0, 0, 4, 4, 64, 32, 1 };
t_animation ANIMABEILLE = { "anim/abeille.png", 0, 0, 6, 6, 50, 40, 1 };
t_animation ANIMMOUSTIQUE = { "anim/moustique.png", 0, 0, 6, 6, 50, 40, 1 };
t_animation ANIMSERPENT = { "anim/serpent.png", 0, 0, 4, 7, 100, 50, 1 };

typedef struct{
    // déplacement
    float x, y; // position
    float dx, dy; // déplacement

    // gestion animation
    int imcourante; // image courante
    int tour; // compte tours
    int nbtour; // nombre de tours
    int dir; // direction de l'animation
    t_animation*anim; // les images de l'animations

}t_sprite;

void recup_animation(t_animation*a);
ALLEGRO_BITMAP* recup_image(ALLEGRO_BITMAP*scr, int tx, int ty,
                            int startx, int starty, int colonne,
                            int i);
t_sprite* init_sprite(int posx, int posy, int dx, int dy, 
                            int nbtour, int dir, t_animation*a);
void avance_sprite(t_sprite*s);
void anime_sprite(t_sprite*s);
void affiche_sprite(t_sprite*s);
void destroy_sprite(t_sprite*s[]);
void erreur(const char* txt);
/*****************************************************************
*****************************************************************/
int main()
{
    ALLEGRO_DISPLAY*display;
    ALLEGRO_KEYBOARD_STATE key;
    t_sprite*all[NBSPRITE];
    int i;


    if (!al_init())
        erreur("al_init()");
    if (!al_install_keyboard())
        erreur("al_install_keyboard()");
    if (!al_init_image_addon())
        erreur("al_init_image_addon()");

    display = al_create_display(SCRX, SCRY);
    if (!display)
        erreur("al_create_display()");

    // récupération des animations
    recup_animation(&ANIMDECOR);
    recup_animation(&ANIMDRAGON);
    recup_animation(&ANIMPOISSON);
    recup_animation(&ANIMCRABE);
    recup_animation(&ANIMABEILLE);
    recup_animation(&ANIMMOUSTIQUE);
    recup_animation(&ANIMSERPENT);

    // initialisation des sprites
    all[DECOR] = init_sprite(0, 0, 0, 0, 0, 0, &ANIMDECOR);
    all[DRAGON] = init_sprite(500, 0, -5, 0, 5, 1, &ANIMDRAGON);
    all[POISSON] = init_sprite(300, 400, 3, 0, 8, 1, &ANIMPOISSON);
    all[CRABE] = init_sprite(300, 212, 2, 0, 20, 1, &ANIMCRABE);
    all[ABEILLE] = init_sprite(100, 122, -3, 0, 8, 1, &ANIMABEILLE);
    all[MOUSTIQUE] = init_sprite(500, 70, 4, 0, 2, 1, &ANIMMOUSTIQUE);
    all[SERPENT] = init_sprite(350, 200, -2, 0, 4, 1, &ANIMSERPENT);

    // ne pas oublier!
    al_set_target_backbuffer(display);

    do{
        al_get_keyboard_state(&key);

        for (i = DECOR; i<NBSPRITE; i++){
            avance_sprite(all[i]);
            anime_sprite(all[i]);
            affiche_sprite(all[i]);
        }

        al_flip_display();
        al_rest(1.0 / 30);

    } while (!al_key_down(&key, ALLEGRO_KEY_ESCAPE));

    //destroy_sprite(all);// BUG
    al_destroy_display(display);
    return 0;
}
/*****************************************************************
*****************************************************************/
void recup_animation(t_animation*a)
{
    int i;
    ALLEGRO_BITMAP*conteneur;

    // récupération du fichier conteneur
    conteneur = al_load_bitmap(a->nom);
    if (!conteneur)
        erreur("recup_animation(), conteneur");

    // allocation du tableau dynamique d'images
    a->image = (ALLEGRO_BITMAP**)malloc(
        sizeof(ALLEGRO_BITMAP*)*a->nbimage);
    for (i = 0; i<a->nbimage; i++){
        a->image[i] = recup_image(
            conteneur,// bitmap d'origine
            a->tx, a->ty,// taille élément
            0, 0,        // à partir du point h-g
            a->nbcolonne,// nombre colonnes
            i);// ieme élément de 0 à nbimage-1
        if (!a->image[i])
            erreur("recup_animation()");
        if (a->masque == true)
            al_convert_mask_to_alpha(a->image[i],
            al_get_pixel(a->image[i], 0, 0));
    }
}
/*****************************************************************
*****************************************************************/
ALLEGRO_BITMAP*recup_image(ALLEGRO_BITMAP*scr, int tx, int ty,
    int startx, int starty, int colonne, int i)
{
    ALLEGRO_BITMAP*image = NULL;
    int x, y;
    image = al_create_bitmap(tx, ty);
    if (image != NULL){
        // attention colonne doit être > 0
        x = startx + (i%colonne)*tx;
        y = starty + (i / colonne)*ty;

        al_set_target_bitmap(image);
        al_draw_bitmap_region(scr, x, y, tx, ty, 0, 0, 0);
    }
    return image;
}

/*****************************************************************
*****************************************************************/
t_sprite*init_sprite(int posx, int posy,
    int dx, int dy,
    int nbtour, int dir,
    t_animation*a)
{
    t_sprite*s = (t_sprite*)malloc(sizeof(t_sprite));
    s->x = posx;
    s->y = posy;
    s->dx = dx;
    s->dy = dy;
    s->imcourante = 0;
    s->tour = 0;
    s->nbtour = nbtour;
    s->dir = dir;
    s->anim = a;

    return s;
}
/*****************************************************************
*****************************************************************/
void avance_sprite(t_sprite*s)
{
    s->x += s->dx;
    s->y += s->dy;

    //sortie à gauche entrée à droite
    if (s->x + s->anim->tx < 0)
        s->x = SCRX;
    // sortie à droite entrée à gauche
    else if (s->x >SCRX)
        s->x = -s->anim->tx;

    //sortie en haut entrée en bas
    if (s->y + s->anim->ty < 0)
        s->y = SCRY;
    // sortie à droite entrée à gauche
    else if (s->y >SCRY)
        s->y = -s->anim->ty;
}
/*****************************************************************
*****************************************************************/
void anime_sprite(t_sprite*s)
{
    if (++s->tour > s->nbtour) {
        s->imcourante =
            (s->imcourante + s->dir + s->anim->nbimage) % s->anim->nbimage;
        s->tour = 0;
    }
}
/*****************************************************************
*****************************************************************/
void affiche_sprite(t_sprite*s)
{
    al_draw_bitmap(s->anim->image[s->imcourante], s->x, s->y, 0);
}
/*****************************************************************
*****************************************************************/
void destroy_sprite(t_sprite*s[])
{
    int i;
    for (i = 0; i<NBSPRITE; i++){
        // l'animation peut servir à plusieurs sprite mais
        // il faut la détruire une seule fois
        if (s[i]->anim != NULL){
            if (s[i]->anim->image != NULL){
                for (i = 0; i < s[i]->anim->nbimage; i++)
                    al_destroy_bitmap(s[i]->anim->image[i]);
                free(s[i]->anim->image);
                s[i]->anim->image = NULL;
            }
        }
        free(s[i]);
    }
}
/*****************************************************************
*****************************************************************/
void erreur(const char*txt)
{
    ALLEGRO_DISPLAY*d;
    d = al_is_system_installed() ? al_get_current_display() : NULL;
    al_show_native_message_box(d, "Erreur", txt, NULL, NULL, 0);
    exit(EXIT_FAILURE);
}
/*****************************************************************
*****************************************************************/

Obtenir ce livre

Image non disponible

Ce document est une retranscription autorisée du livre Allegro 5 - Programmation de jeux en C et C++ écrit par Frédéric DROUILLON. Initialement publié aux éditions ENI, vous pouvez commander le livre sur le site de l'éditeur.


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2015 Frédéric DROUILLON. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.