Allegro 5

Programmation de jeux en C ou C++


précédentsommaire

XIII. Collisions entre personnages

XIII-A. Introduction

Dans le programme précédent, les collisions entre personnages ne sont pas gérées et l'objectif est maintenant d'ajouter cette compétence au programme précédent. Ni les clones ni le joueur ne doivent pouvoir passer les uns à travers les autres comme des fantômes.

XIII-B. Cartographie des personnages

Nous partons d'une copie du programme précédent avec un joueur et une tribu de clones qui se déplacent en respectant des contraintes de terrain mais sans interaction entre eux. Nous voulons qu'ils se détectent mutuellement et gèrent les collisions.

XIII-B-1. Intersection de rectangles ?

Avec cinquante personnages, nous pouvons utiliser une intersection de rectangles pour détecter les collisions (technique présentée dans la section Collisions - Intersection de rectangles du chapitre Modèle de jeu simpleModèle de jeu simple). L'algorithme est le suivant : pour chaque personnage, à chaque déplacement, tester l'intersection avec tous les autres. Le nombre d'opérations nécessaires avec un tel algorithme croît de façon exponentielle avec le nombre des personnages. En effet, s'il y a N personnages, pour chaque personnage il faut regarder les N autres ce qui fait N fois N cas, N² cas.

Plus il y a de personnages, plus sont multipliées les opérations nécessaires :

Pour 50 personnages nous avons 50*50 : 2500

Pour 60 personnages nous avons 60*60 : 3600

Pour 70 personnages nous avons 70*70 : 4900

Pour 80 personnages nous avons 80*80 : 6400

Pour 90 personnages nous avons 90*90 : 8100

Pour 100 personnages nous avons 100*100 : 10000

Pour 200 personnages nous avons 200*200 : 40000

Nous constatons qu'en partant de 50 nous avons déjà 2500 opérations. Avec 200 personnages, soit 4 fois plus, nous avons 16 fois plus d'opérations (40000/2500) et plus le nombre de personnages augmente, plus l'écart se creuse.

Puisque nous sommes dans les cartes (map), nous allons tester une autre technique qui repose sur une cartographie des personnages. Tous les personnages vont être suivis sur une carte comme des tuiles de décor dotées du mouvement. Du point de vue algorithmique, lors de chaque déplacement, chaque personnage regarde la carte et sait immédiatement s'il entre en collision ou non avec un autre. Le personnage peut même connaître avec qui il est entré en contact. Il suffit que chaque personnage donne sur la carte, un moyen de l'identifier. L'identificateur peut être un nombre ou même l'adresse mémoire du personnage. Quoi qu'il en soit, c'est intéressant parce qu'il n'y a pas d'augmentation de la complexité :

pour 50 personnages nous n'avons que 50*1 : 50 opérations

et pour 200 personnages nous avons seulement 200*1 : 200 opérations.

Il s'ajoute de façon constante une opération par personnage quel que soit le nombre de personnages. C'est une stratégie très bénéfique en temps de calcul. Elle est très utile pour gérer de grands nombres de personnages ou d'éléments animés en interaction. En revanche la mise en place nécessite beaucoup de soin et d'attention. Des erreurs peuvent se glisser qu'il n'est pas toujours évident de comprendre et discerner.

XIII-B-2. Mise en place de la carte

Pour commencer la carte des personnages est une seconde matrice d'entiers identique à la carte du terrain. Nous aurions pu tout faire sur la même matrice. C'est un choix.

Les dimensions du terrain sont données par :

 
Sélectionnez
#define MAPTX 20
#define MAPTY 15

et ce sont les mêmes pour la carte des personnages. Elle est initialisée à 0 à la déclaration :

 
Sélectionnez
int MAPPERSON[MAPTY][MAPTX] = {};

Une position est considérée comme libre si elle est à 0 et comme occupée si elle est à 1. Ces valeurs sont rendues plus lisibles avec deux macros :

 
Sélectionnez
#define LIBRE 0
#define OCCUPE 1

Dans un souci d'harmonisation et de lisibilité des cartes décor et personnages, les positions sur 3 (herbe) dans MAPDECOR deviennent des positions libres et toutes les autres deviennent des positions occupées par le décor. Une fonction opère cette modification de la carte MAPDECOR de la façon suivante :

 
Sélectionnez
void mise_a_libre_ou_occupe()
{
    int y, x;
    for (y = 0; y < MAPTY;y++)
        for (x = 0; x < MAPTX; x++)
            if (MAPDECOR[y][x] == 3)
                MAPDECOR[y][x] = LIBRE;
            else
                MAPDECOR[y][x] = OCCUPE;
}

Elle est appelée au moment de la création du décor, à la fin du constructeur décor :

 
Sélectionnez
ALLEGRO_BITMAP* construct_decor()
{
    (...)
    mise_a_libre_ou_occupe();
    return decor;
}

Au départ, les personnages doivent être inscrits dans la carte selon leur initialisation dans le constructeur de personnages. Cela se fait juste après qu'une position accessible soit trouvée dans le décor :

 
Sélectionnez
t_personnage* construct_personnage()
{
    (...)
    // trouver une position LIBRE dans le décor
    while (MAPDECOR[j->y][j->x] != LIBRE){
        if (rand() % 2)
            j->x = (++j->x) % MAPTX;
        else
            j->y = (++j->y) % MAPTY;
    }
    // marquer l'emplacement par le personnage
    // dans la carte des personnages
    MAPPERSON[j->y][j->x] = OCCUPE;
    (...)
    return j;
}

Dans cette première version du programme, nous acceptons que plusieurs personnages soient à la même place au départ. Ce n'est pas trop gênant parce qu'ils chercheront à se dégager dès qu'ils vont se déplacer.

XIII-B-3. Mouvement des personnages

Dans le chapitre Personnages dans un décor, section Nombreux personnages mobilesNombreux personnages mobiles, l'avance des personnages est contrôlée avec la fonction :

 
Sélectionnez
int cntl_avance_personnage(int x, int y, t_personnage*j) ;

dans laquelle seules les interactions avec le décor sont prises en compte. Dans la nouvelle fonction rebaptisée bouge_personnages , le contrôle des interactions avec le décor n'a pas changé mais un contrôle sur les personnages est ajouté. Le contrôle total est le suivant :

 
Sélectionnez
si le personnage reste dans l'écran et
si sa nouvelle position respecte le terrain et
s'il n'entre en collision avec personne alors
il peut avancer.

Le troisième test sur les personnages si les deux premiers sont passés est nouveau. Il consiste à regarder dans la carte des personnages si un coin du personnage qui avance entre en contact avec un autre. Il est tourné ainsi :

 
Sélectionnez
(...)
// left top right bottom
l = x / PIXTILEX;
t = y / PIXTILEX;
r = (x + j->tx - 1) / PIXTILEX;
b = (y + j->ty - 1) / PIXTILEY;
if ( MAPPERSON[t][l] == LIBRE &&
MAPPERSON[t][r] == LIBRE &&
MAPPERSON[b][l] == LIBRE &&
MAPPERSON[b][r] == LIBRE){
    //avancer
    j->x = x;
    j->y = y;
    res=1 ;
}

Notons également qu'à la différence de la fonction précédente, le mouvement a lieu dans la fonction, la position du personnage j est modifiée si tous les tests sont vrais.

Par ailleurs, le personnage qui bouge ne doit pas compter les interactions avec lui-même. De plus, s'il bouge il devra quitter sa place pour une nouvelle. Pour ces deux raisons, le personnage est au début de la fonction effacé de la carte des personnages :

 
Sélectionnez
mapx = j->x / PIXTILEX;
mapy = j->y / PIXTILEY;
MAPPERSON[mapy][mapx] = LIBRE;

Ensuite à la fin de l'opération qu'il avance ou non, il est réinscrit à sa position dans la carte des personnages :

 
Sélectionnez
mapx = j->x / PIXTILEX;
mapy = j->y / PIXTILEY;
MAPPERSON[mapy][mapx] = OCCUPE;

La fonction est rebaptisée bouge_personnage pour une question de lisibilité du code au moment de l'appel. Voici la fonction :

 
Sélectionnez
int bouge_personnage(int x, int y, t_personnage*j)
{
    int l, r, t, b;
    int mapx, mapy;
    int res = 0;
    // mise à LIBRE position actuelle dans la carte
    // des personnages
    mapx = j->x / PIXTILEX;
    mapy = j->y / PIXTILEY;
    MAPPERSON[mapy][mapx] = LIBRE;
    // si le personnage reste dans l'écran
    if (x >= 0 && x + j->tx <= PIXMAPX &&
            y >= 0 && y + j->ty <= PIXMAPY){
        // left top right bottom
        l = x / PIXTILEX;
        t = y / PIXTILEX;
        r = (x + j->tx - 1) / PIXTILEX;
        b = (y + j->ty - 1) / PIXTILEY;
        // et si les 4 coins du sprite sont tous sur du HERBE (herbe)
        if (MAPDECOR[t][l] == LIBRE &&
                MAPDECOR[t][r] == LIBRE &&
                MAPDECOR[b][l] == LIBRE &&
                MAPDECOR[b][r] == LIBRE){
            if (MAPPERSON[t][l] == LIBRE &&
                    MAPPERSON[t][r] == LIBRE &&
                    MAPPERSON[b][l] == LIBRE &&
                    MAPPERSON[b][r] == LIBRE){
                //avancer
                j->x = x;
                j->y = y;
                res = 1;
            }
        }
    }
    // mise à OCCUPE
    mapx = j->x / PIXTILEX;
    mapy = j->y / PIXTILEY;
    MAPPERSON[mapy][mapx] = OCCUPE;
    return res;
}

Cette fonction est appelée deux fois. Une fois pour les clones et une fois pour le joueur. L'appel est simplifié parce que la modification de la position est dans la fonction. Pour les clones, l'appel est le suivant dans la fonction avance_clone :

 
Sélectionnez
if (! bouge_personnage(x, y, j))
    direction_clone(j);

Si le personnage ne bouge pas, il doit chercher une nouvelle direction. Pour cette recherche nous n'avons rien changé. C'est exactement la même fonction. Le clone prend uniquement en compte le terrain et ignore les autres personnages. L'idée est qu'il devra peut-être passer plusieurs tours à chercher une direction et jusqu'à ce qu'il en trouve libre et accessible autour de lui.

Pour ce qui concerne le joueur, l'appel de la fonction bouge_personnage a lieu dans la fonction avance_joueur .

XIII-B-4. Code complet commenté

Gestion imparfaite des collisions entre personnages
Sélectionnez
// pour utilisation de la fonction sprintf
// jugée unsafe par microsoft
#define _CRT_SECURE_NO_WARNINGS

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

#define SCREENX    640
#define SCREENY    480
#define BLANC al_map_rgb(255,255,255)
#define GRIS al_map_rgb(128,128,128)

// TUILES ET MAPDECOR
// largeur et hauteur des carreaux en pixels
#define PIXTILEX 32
#define PIXTILEY 32

// taille en tuiles de la MAPDECOR 
#define MAPTX 20 
#define MAPTY 15 

// contrôle de collisions
#define LIBRE        0
#define OCCUPE        1

// taille en pixels de la MAPDECOR
#define PIXMAPX (MAPTX*PIXTILEX)
#define PIXMAPY (MAPTY*PIXTILEY)


// la carte des tuiles du terrain
short MAPDECOR[MAPTY][MAPTX] = {
    { 3, 3, 3, 7,7,7,55,8,8,2,3,3,3,3,3,3,3, 3, 3,101 },
    { 3, 3, 3, 3,3,3,55,1,1,3,3,3,3,3,3,3,3,101,101,101 },
    { 3, 3, 3, 3,3,3, 3,2,1,3,3,3,2,3,3,3,3, 3,101, 3 },
    { 3, 3, 1, 3,3,3, 3,3,3,3,3,2,2,3,3,3,2,101,101, 3 },
    { 3, 1, 1, 3,3,3, 3,3,3,3,2,2,3,3,3,3,2, 3, 3, 3 },
    { 3, 1, 1, 1,3,3, 3,3,3,8,2,2,2,3,3,1,7, 1, 3, 3 },
    { 3, 3, 3, 1,1,3, 3,3,3,3,1,3,3,3,3,3,3, 3, 3, 3 },
    { 3, 3, 3, 1,3,3, 3,3,1,3,3,3,3,3,3,3,3, 3, 3, 3 },
    { 3, 3, 3, 3,3,3, 3,2,1,3,3,3,3,3,3,3,3, 3, 3, 3 },
    { 4, 4, 3, 3,3,3, 3,2,1,3,3,6,6,3,3,3,3, 3, 4, 4 },
    { 4, 4, 4, 3,3,3, 2,2,3,3,3,4,4,3,3,3,3, 81, 81, 2 },
    { 4, 4, 3, 3,3,3, 2,1,3,3,3,4,1,3,3,3,4, 4, 81, 81 },
    { 4, 4, 4, 3,3,3, 3,1,3,3,3,3,2,3,3,4,4, 4, 81, 81 },
    { 81,81, 4, 3,3,3, 3,1,1,1,3,3,3,3,4,4,4, 2, 4, 81 },
    { 81,81,81,81,3,3, 3,3,3,3,3,3,3,4,4,4,2, 2, 81, 81 }
};

// PERSONNAGE
#define JOUEUR    50

// ensemble des variables qui identifient un personnage
typedef struct{
    int x, y; // position
    int pas; // déplacement
    int tx, ty;// taille

    //animation
    int tour, nbtour;
    int nbimage; 
    int imcourante;
    int dir;
}t_personnage;

// 4 directions
enum{ LEFT, RIGHT, UP, DOWN, NBDIR };

// pour clavier fluide
int KEY[NBDIR] = {};

// tableau des animations :
// 4 directions, 4 images par direction
ALLEGRO_BITMAP*ANIM[NBDIR][4];

//carte des personnages
int MAPPERSON[MAPTY][MAPTX] = {};


ALLEGRO_BITMAP*    construct_decor            (void);
void            mise_a_libre_ou_occupe    (void);
void            recup_anim_personnage    (void);
t_personnage*    construct_personnage    (void);
int                bouge_personnage        (int x, int y, 
                                        t_personnage*j);
void            affiche_personnage        (t_personnage*j);
void            avance_clone            (t_personnage*j);
void            direction_clone            (t_personnage*j);


void            controle_joueur        (ALLEGRO_EVENT*e,
                                    t_personnage*j);
void            avance_joueur        (t_personnage*j);
ALLEGRO_BITMAP*    recup_sprite        (ALLEGRO_BITMAP*scr,
                                    int tx, int ty,
                                    int startx, int starty,
                                    int colonne, int i);
ALLEGRO_DISPLAY*allegro_init        (ALLEGRO_EVENT_QUEUE**queue,
                                    ALLEGRO_TIMER**timer);
void            erreur                (const char*msg);
/*****************************************************************
*****************************************************************/
int main()
{
    ALLEGRO_DISPLAY*display;
    ALLEGRO_EVENT_QUEUE*queue;
    ALLEGRO_TIMER*timer;
    ALLEGRO_BITMAP*decor;

    // l'ensemble des personnages avec le joueur
    // placé en dernier
    t_personnage* allP[JOUEUR+1]; 
    
    bool fin = 0;
    bool dessine = true;
    int i;

    display = allegro_init(&queue, &timer);

    decor = construct_decor();
    recup_anim_personnage();
    
    // revenir à l'affichage écran
    al_set_target_backbuffer(display);

    // création des personnages
    for (i = 0; i <= JOUEUR;i++)
        allP[i]= construct_personnage();

    while (!fin){

        ALLEGRO_EVENT event;
        al_wait_for_event(queue, &event);

        controle_joueur(&event, allP[JOUEUR]);

        if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE ||
            event.keyboard.keycode == ALLEGRO_KEY_ESCAPE)
            fin = true;

        else if (event.type == ALLEGRO_EVENT_TIMER){

            // mouvement 
            avance_joueur(allP[JOUEUR]);

            for (i = 0; i < JOUEUR; i++)
                avance_clone(allP[i]);

            // redessiner
            dessine = true;

        }

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

            al_draw_bitmap(decor, 0, 0, 0);

            // affichage de tous
            for (i = 0; i <= JOUEUR; i++)
                affiche_personnage(allP[i]);

            al_flip_display();
            dessine = false;
        }
    }
    for (i = 0; i <= JOUEUR; i++)
        free(allP[i]);
    al_destroy_bitmap(decor);
    al_destroy_display(display);
    al_destroy_timer(timer);
    al_destroy_event_queue(queue);
    return 0;
}
/****************************************************************
DECOR / Création de la bitmap du decor
****************************************************************/
ALLEGRO_BITMAP* construct_decor()
{
ALLEGRO_BITMAP*decor, *bmp;
ALLEGRO_BITMAP*tuiles;
int mapx, mapy, posx, posy;

    // bitmap decor et load tiles
    decor = al_create_bitmap(PIXMAPX, PIXMAPY);
    tuiles = al_load_bitmap(".\\images\\kit_de_tuiles_2.png");
    if (!decor || !tuiles)
        erreur("decor, tuiles");

    // construction du decor
    for (mapy = 0; mapy<MAPTY; mapy++)
        for (mapx = 0; mapx<MAPTX; mapx++){
            bmp = recup_sprite(    tuiles, // fichier
                                PIXTILEX, PIXTILEY,// taille tuiles
                                0, 0, // pos départ
                                20, // nombre de colonnes
                                MAPDECOR[mapy][mapx]);// N° de tuile
            if (!bmp)
                erreur("recup_sprite()");
            posx = mapx*PIXTILEX;
            posy = mapy*PIXTILEY;
            
            al_set_target_bitmap(decor);
            al_draw_bitmap(bmp, posx, posy, 0);
            al_draw_rectangle(    posx, posy, 
                                posx + PIXTILEX, posy + PIXTILEY, 
                                GRIS, 1);

            al_destroy_bitmap(bmp);
    }
    // libérer mémoire image inutile
    al_destroy_bitmap(tuiles);
    
    // les position sont soit libres soit occupées
    mise_a_libre_ou_occupe();

    // retour adresse du décor
    return decor;
}
/****************************************************************
DECOR / met à LIBRE ou OCCUPE les positions dans la map MAPDECOR 
selon qu'elles sont accessibles ou non aux personnages du jeu
****************************************************************/
void mise_a_libre_ou_occupe()
{
    int y, x;
    for (y = 0; y < MAPTY;y++)
        for (x = 0; x < MAPTX; x++)
            if (MAPDECOR[y][x] == 3)
                MAPDECOR[y][x] = LIBRE;
            else
                MAPDECOR[y][x] = OCCUPE;
}
/*****************************************************************
PERSONNAGES / création
*****************************************************************/
t_personnage* construct_personnage()
{
    t_personnage*j = (t_personnage*)malloc(sizeof(t_personnage));

    j->tx = PIXTILEX;
    j->ty = PIXTILEY;
    j->pas = 4;

    // position (en coordonnées MAPDECOR)
    j->x = rand() % MAPTX;
    j->y = rand() % MAPTY;
    
    // trouver LIBRE
    while (MAPDECOR[j->y][j->x] != LIBRE){
        if (rand() % 2)
            j->x = (++j->x) % MAPTX;
        else
            j->y = (++j->y) % MAPTY;
    }
    // marquer l'emplacement par le personnage
    MAPPERSON[j->y][j->x] = OCCUPE;
    
    // convertir la position matrice en position écran 
    // (en coordonnées pixels)
    j->x *= PIXTILEX;
    j->y *= PIXTILEY;

    // animation
    j->tour = 0;
    j->nbtour = 5;
    j->nbimage = 4;
    j->imcourante = 0;
    j->dir = rand() % NBDIR;

    return j;
}
/*****************************************************************
PERSONNAGES / controle du déplacement
*****************************************************************/
int bouge_personnage(int x, int y, t_personnage*j)
{
    int l, r, t, b;
    int mapx, mapy;
    int res = 0;

    // mise à LIBRE position actuelle dans la carte 
    // des personnages
    mapx = j->x / PIXTILEX;
    mapy = j->y / PIXTILEY;
    MAPPERSON[mapy][mapx] = LIBRE;

    // si le personnage reste dans l'écran
    if (x >= 0 && x + j->tx <= PIXMAPX &&
        y >= 0 && y + j->ty <= PIXMAPY){

        // left top right bottom
        l = x / PIXTILEX;
        t = y / PIXTILEX;
        r = (x + j->tx - 1) / PIXTILEX;
        b = (y + j->ty - 1) / PIXTILEY;

        // et si les 4 coins du sprite sont tous sur 
        // de l'herbe (LIBRE)
        if (MAPDECOR[t][l] == LIBRE && 
            MAPDECOR[t][r] == LIBRE &&
            MAPDECOR[b][l] == LIBRE &&
            MAPDECOR[b][r] == LIBRE){

            // et si aucun autre personnage
            if (MAPPERSON[t][l] == LIBRE && 
                MAPPERSON[t][r] == LIBRE &&
                MAPPERSON[b][l] == LIBRE && 
                MAPPERSON[b][r] == LIBRE){

                //avancer
                j->x = x;
                j->y = y;
                res = 1;
            }
            
        }
    }

    // mise à OCCUPE 
    mapx = j->x / PIXTILEX;
    mapy = j->y / PIXTILEY;
    MAPPERSON[mapy][mapx] = OCCUPE;

    return res;
}
/*****************************************************************
PERSONNAGES / Affichage
*****************************************************************/
void affiche_personnage(t_personnage*j)
{
    // l'animation est permanente
    j->tour++;
    if (j->tour == j->nbtour){
        j->imcourante = (++j->imcourante) % j->nbimage;
        j->tour = 0;
    }

    // affichage du joueur
    al_draw_scaled_bitmap(ANIM[j->dir][j->imcourante],
        0, 0, 64, 64,    // source
        j->x, j->y,        // cible
        j->tx, j->ty,
        0);

}
/*****************************************************************
PERSONNAGES / récupération des 4 animations communes à tous
*****************************************************************/
void recup_anim_personnage()
{
    int dir, i;
    char nom[256];

    for (dir = 0; dir < NBDIR; dir++){
        for (i = 0; i < 4; i++){
            sprintf(nom, ".\\images\\joueur\\hero_%d_%d.bmp", dir, i);
            ANIM[dir][i] = al_load_bitmap(nom);
            if (!ANIM[dir][i])
                erreur("ANIM[dir][i] = al_load_bitmap(nom)");
            al_convert_mask_to_alpha(ANIM[dir][i],
                al_get_pixel(ANIM[dir][i], 0, 0));
        }
    }
}
/*****************************************************************
CLONES / déplacement automatique
*****************************************************************/
void avance_clone(t_personnage*j)
{
    int x, y;
    // copie position et taille
    y = j->y;
    x = j->x;
    
    // avance selon direction
    switch (j->dir){
        case LEFT:    x -= j->pas;    break;
        case RIGHT: x += j->pas;    break;
        case UP:    y -= j->pas;    break;
        case DOWN:    y += j->pas;    break;
    }
    if (! bouge_personnage(x, y, j))
        direction_clone(j);    
}

/*****************************************************************
CLONES / choix direction
Le principe est :
- de tirer une direction aléatoire
- si impossible (cause bords, terrain)
- prendre la suivante 
*****************************************************************/
void direction_clone(t_personnage*j)
{
    int sens = (rand() % 2) * 2 - 1; //-1 ou 1
    int dir = rand() % NBDIR;         // entre 0 et HERBE compris
    int res = -1;

    // passer en coordonnées MAPDECOR
    int x = j->x / PIXTILEX;
    int y = j->y / PIXTILEX;

    // vérifier la direction, si impossible prendre la 
    // suivante en tournant selon "sens" (vers gauche ou droite)
    do{
        dir = ((dir + sens) + NBDIR) % NBDIR;
        switch (dir){
            case LEFT:
                if (x - 1 >= 0 && MAPDECOR[y][x - 1] == LIBRE)
                    res = LEFT;
                break;
            case RIGHT:
                if (x + 1<MAPTX && MAPDECOR[y][x + 1] == LIBRE)
                    res = RIGHT;
                break;
            case UP:
                if (y - 1 >= 0 && MAPDECOR[y - 1][x] == LIBRE)
                    res = UP;
                break;
            case DOWN:
                if (y + 1<MAPTY && MAPDECOR[y + 1][x] == LIBRE)
                    res = DOWN;
                break;
        }
        
    } while (res < 0 );
    
    j->dir = res;
}

/*****************************************************************
JOUEUR / contrôle clavier du joueur
*****************************************************************/
void controle_joueur(ALLEGRO_EVENT*e, t_personnage*j)
{
    if (e->type == ALLEGRO_EVENT_KEY_DOWN){
        switch (e->keyboard.keycode){
        case ALLEGRO_KEY_LEFT:    //82
        case ALLEGRO_KEY_RIGHT:    //83
        case ALLEGRO_KEY_UP:    //84
        case ALLEGRO_KEY_DOWN:    //85
            j->dir = e->keyboard.keycode - ALLEGRO_KEY_LEFT;
            KEY[j->dir] = 1;
            break;
        }
    }
    else if (e->type == ALLEGRO_EVENT_KEY_UP){
        switch (e->keyboard.keycode){
        case ALLEGRO_KEY_LEFT:
        case ALLEGRO_KEY_RIGHT:
        case ALLEGRO_KEY_UP:
        case ALLEGRO_KEY_DOWN:
            j->dir = e->keyboard.keycode - ALLEGRO_KEY_LEFT;
            KEY[j->dir] = 0;
            break;
        }
    }
}
/*****************************************************************
JOUEUR / déplacement
*****************************************************************/
void avance_joueur(t_personnage*j)
{
    int x, y;
    // copie position et taille
    y = j->y;
    x = j->x;
    
    // avance selon état du clavier
    x -= KEY[LEFT] * j->pas;
    x += KEY[RIGHT] * j->pas;
    y -= KEY[UP] * j->pas;
    y += KEY[DOWN] * j->pas;

    // controle bords et terrain
    bouge_personnage(x, y, j);
        
}
/*****************************************************************
TOOLS / Récupérer les tuiles
*****************************************************************/
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;
}
/*****************************************************************
TOOLS / Initialisations allegro
*****************************************************************/
ALLEGRO_DISPLAY* allegro_init(    ALLEGRO_EVENT_QUEUE**queue,
                                ALLEGRO_TIMER**timer)
{
ALLEGRO_DISPLAY*display;

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

    if (!al_init_primitives_addon())
        erreur("al_init_primitives_addon()");

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

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

    display = al_create_display(SCREENX, SCREENY);
    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));

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

    return display;
}
/*****************************************************************
TOOLS / contrôle d'erreur
*****************************************************************/
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);
}
/*****************************************************************
*****************************************************************/

XIII-B-4-a. Capture d'écran :

Image non disponible

XIII-B-5. Problèmes posés

Certes, lorsque le programme tourne, les clones qui entrent en collision cherchent une nouvelle direction jusqu'à en trouver une et la prennent. Ça marche bien. Mais il y a deux problèmes. Le premier est qu'à chaque changement de direction, le clone change aussi d'animation et de position (vers gauche, droit, haut, bas). Pour autant la position n'est pas forcément libre. Il reste alors sur place et recommence le tour d'après. De sorte que parfois un personnage gigote sur place en passant d'une direction à une autre tous les 1/30e de seconde jusqu'à ce qu'il puisse se dégager. C'est amusant mais ce n'est pas un effet recherché. Il faudrait que le personnage s'arrête tant qu'il cherche une direction et qu'aucune direction ne lui permet d'avancer.

Deuxième problème, les personnages continuent souvent, mais pas toujours, de se superposer et toutes les collisions ne sont pas détectées. Le clone est aveugle sur les clones qui sont juste au-dessus de lui ou juste à sa gauche. Cela provient des coordonnées qui correspondent au coin haut-gauche de l'image. Voici ce qui se passe :

Image non disponible

Les clones sont affichés à l'écran en coordonnées pixel et ils se déplacent de 4 pixels en 4 pixels. Ils sont donc très souvent en porte-à-faux sur plusieurs tuiles du terrain, chaque tuile du terrain mesurant 32 pixels. Cependant, tant que le coin haut-gauche du clone est sur la même tuile, il appartient toujours à la même case sur la carte une fois faite la conversion des coordonnées pixel en coordonnées carte. Par exemple sur le schéma, le clone gris est placé en 1 vertical et 1 horizontal, le clone de gauche est en 1 vertical, 0 horizontal et le clone en haut est en 0 vertical, 1 horizontal.

Lorsque le clone en gris avance, il teste les cases correspondant à ses quatre coins et sur le schéma, nous voyons bien qu'il ne peut voir ni le clone au-dessus de lui ni celui à sa gauche, aucun de ses coins ne permet de les détecter. Régler ce problème mérite un peu de réflexion.

Nous allons corriger ces deux problèmes. Premièrement, nous allons implémenter l'immobilité des clones lorsque le déplacement est impossible et comment interdire les superpositions des personnages.

XIII-C. Automatisation du comportement des clones coincés

Nous partons encore du programme précédent. Il met en scène le mouvement des clones et du joueur et il est doté de la détection des collisions entre personnages. Notre objectif est de stopper un clone lorsqu'il ne peut pas avancer.

Le joueur reste comme avant toujours en mouvement même s'il n'avance pas. En fait, c'est mieux visuellement et cela permet de le distinguer des autres clones.

XIII-C-1. Modification de la structure de données

Le principe est simple : ajouter un indicateur de mobilité dans la structure personnage :

 
Sélectionnez
typedef struct{
    int x, y;
    int pas;
    int tx, ty;
    int mobile;
    // indicateur de mobilité
    int tour, nbtour;
    int nbimage;
    int imcourante;
    int dir;
}t_personnage;

S'il est sur 1 le personnage avance, s'il est sur 0 le personnage est à l'arrêt. Par défaut, au départ chaque personnage est mobile. L'initialisation se fait dans le constructeur :

 
Sélectionnez
t_personnage* construct_personnage()
{
    (...)
    j->mobile=1;
    return j;
}

Par ailleurs il faut une animation spécifique lorsque le personnage est à l'arrêt. Pour ce faire, la matrice des animations a besoin d'une direction supplémentaire, c'est la direction STOP. Ainsi nous avons :

 
Sélectionnez
enum{ LEFT, RIGHT, UP, DOWN, STOP };
ALLEGRO_BITMAP*ANIM[STOP+1][4];

NBDIR est remplacé par STOP et un élément est ajouté à la première dimension du tableau : STOP + 1. Ainsi la constante STOP permettra d'accéder aux images du stop.

Par ailleurs il faut remplacer NBDIR par STOP dans tout le programme.

L'animation est constituée de quatre fois la même image :

Image non disponible

Elle est chargée comme les autres dans la fonction :

 
Sélectionnez
void recup_anim_personnage()
{
    int dir, i;
    char nom[256];
    for (dir = 0; dir <= STOP; dir++){// attention <=
        for (i = 0; i < 4; i++){
            sprintf(nom, ".\\images\\joueur\\hero_%d_%d.bmp",dir, i);
            ANIM[dir][i] = al_load_bitmap(nom);
            if (!ANIM[dir][i])
                erreur("ANIM[dir][i] = al_load_bitmap(nom)");
            al_convert_mask_to_alpha(ANIM[dir][i],
                                     al_get_pixel(ANIM[dir][i], 0, 0));
        }
    }
}

Attention à bien prendre en compte la valeur STOP avec inférieur ou égal, c'est l'indice de l'animation stop dans le tableau.

Attention également à ce que les fichiers d'images soient bien dans le dossier joueur lui-même dans le dossier images. Ils doivent se nommer :

hero_4_0.bmp
hero_4_1.bmp
hero_4_2.bmp
hero_4_3.bmp

XIII-C-2. Gestion du mouvement

La mobilité est déjà déterminée dans le programme par la fonction

 
Sélectionnez
int bouge_personnage(int x, int y, t_personnage*j) ;

qui retourne 1 en cas de mouvement et 0 sinon. Il suffit de récupérer ce retour dans le champ mobile de chaque personnage au moment de l'appel de la fonction, dans la fonction avance_clone :

 
Sélectionnez
void avance_clone(t_personnage*j)
{
    (...)
    // si immobile chercher une direction
    j->mobile = bouge_personnage(x, y, j);
    if (!j->mobile )
        direction_clone(j);
}

Ensuite, comme précédemment, s'il n'y a pas de mouvement, le clone cherche une direction.

XIII-C-3. Gestion de l'affichage

Au moment de l'affichage, le clone sait s'il est mobile ou non. S'il ne l'est pas, c'est l'animation du stop qui est prise :

 
Sélectionnez
void affiche_personnage(t_personnage*j)
{
    int dir;
    // l'animation est permanente
    j->tour++;
    if (j->tour == j->nbtour){
        j->imcourante = (++j->imcourante) % j->nbimage;
        j->tour = 0;
    }
    // affichage du joueur mobile ou immobile
    dir = (j->mobile) ? j->dir : STOP;
    al_draw_scaled_bitmap( ANIM[dir][j->imcourante],
            0, 0, 64, 64,
            j->x, j->y,
            j->tx, j->ty,
            0);
}

Et c'est fini. Lorsque les personnages cherchent une direction, ils adoptent la position STOP.

Visuellement il y a un impact. Les clones paraissent mieux organisés. L'action est plus claire, plus calme.

XIII-C-3-a. Capture d'écran

Image non disponible

Bien entendu, le problème des superpositions entre personnages demeure.

XIII-D. Suppression de superpositions entre personnages

XIII-D-1. Principe d'une échelle de la cartographie

Pour éradiquer les superpositions, il y a plusieurs solutions. Par exemple centrer les coordonnées des personnages de sorte que la position (x,y) à l'écran du personnage corresponde au centre de l'image du personnage et non plus au coin haut gauche. Cela semble une piste intéressante mais ce n'est pas la solution que nous avons retenue.

Nous avons constaté qu'il y avait comme un rapport d'échelle entre la carte de terrain et le terrain en continu c'est-à-dire en pixels à l'écran. Puisque chaque case de la carte terrain vaut 32 par 32 pixels, le rapport d'échelle est en quelque sorte de 1/32 : une case de la carte correspond à un carreau de 32 par 32 pixels dans le décor. Dans ce programme, le décor a toujours la taille de l'écran et ses dimensions s'alignent sur celui-ci :

 
Sélectionnez
#define SCREENX 640
#define SCREENY 480
#define PIXMAPX SCREENX // dimensions en pixels du décor
#define PIXMAPY SCREENY

La taille d'une tuile est 32 par 32 pixels définie de la façon suivante :

 
Sélectionnez
#define PIXTILEX 32
#define PIXTILEY 32

Nous en déduisons les dimensions de la carte du décor :

 
Sélectionnez
#define MAPTX (PIXMAPX/PIXTILEX) // 20
#define MAPTY (PIXMAPY/PIXTILEY) // 15

et dans cette carte une position correspond à un carré de 32 par 32 pixels dans le décor. Il y a 20 fois 15 positions, soit 300 unités.

Si l'on veut que dans la carte une unité corresponde à un carré plus petit, il suffit de diminuer la taille de tuiles.

Des tuiles de 16 par 16 donnent 40*30 = 1200 positions. Deux fois plus en largeur et deux fois plus en hauteur, soit quatre fois plus au total.

Des tuiles de 8 par 8 donnent 80*60 = 4800 positions, même principe.

Des tuiles de 4 par 4 donnent 160*120 = 19200 positions.

Des tuiles de 2 par 2 donnent 320*240 = 76800 positions.

Des tuiles au pixel près de 1 par 1, c'est-à-dire un monde continu, donnent 640*480 = 307200 positions.

Plus il y a de positions dans la carte plus celle-ci est précise, et l'échelle est donnée par la taille des tuiles.

Dans notre programme, le pas d'avancement des personnages est de 4. Il est initialisé dans la fonction construct_personnage . L'échelle qui semble la plus adaptée est 1/4, avec pour la carte une position égale à une tuile de 4 par 4 pixels. La précision est alors la même que celle des déplacements.

Dans cette perspective notre carte des personnages est maintenant définie de la façon suivante :

 
Sélectionnez
#define PTILEX 4 
#define PTILEY 4 
#define PMAPTX (PIXMAPX/PTILEX) // 160
#define PMAPTY (PIXMAPY/PTILEY) // 120
int MAPPERSON[PMAPTY][PMAPTX] = {};

Une tuile du décor correspond maintenant à 32/4 par 32/4 soit 8 par 8 positions dans la carte des personnages :

Image non disponible

XIII-D-2. Principe de l'interception des collisions

Le périmètre de chaque personnage est incrusté dans la carte des personnages. Pour tester les interactions, il suffit de parcourir ce périmètre et de regarder dans la carte s'il croise l'incrustation d'un autre personnage. La détection d'un seul croisement suffit pour savoir qu'il y a collision.

Image non disponible

XIII-D-3. Implémentation

XIII-D-3-a. Modifier la carte

Le premier point est la modification de la carte des personnages, comme nous l'avons indiqué plus haut :

 
Sélectionnez
#define PTILEX 4 
#define PTILEY 4 
#define PMAPTX (PIXMAPX/PTILEX) // 160
#define PMAPTY (PIXMAPY/PTILEY) // 120
int MAPPERSON[PMAPTY][PMAPTX] = {};

XIII-D-3-b. Incruster, désincruster un personnage

Ensuite, nous devons pouvoir incruster un personnage dans la carte, mais aussi l'effacer pour le bouger et le réincruster plus loin. À chaque fois, il faut parcourir le tour du personnage et imprimer à sa position dans la carte une valeur pour occupé, c'est l'incrustation, et une autre valeur pour libre, c'est l'effacement. Dans les deux cas, c'est le même traitement, il y a uniquement la valeur d'impression qui change. Pour avoir une seule fonction, cette valeur est donnée en paramètre.

Voici la fonction d'incrustation qui marque ou efface un personnage dans la carte des personnages :

 
Sélectionnez
void marquer_personnage(t_personnage*j, int val)
{
    int l, r,t,b, i;
    l = j->x / PTILEX;
    r = l + (j->tx/PTILEX)-1;
    t = j->y / PTILEY;
    b = t +(j->ty/PTILEY)-1;
    // horizontales
    for (i = l; i <= r; i++){
        MAPPERSON[t][i] = val;
        MAPPERSON[b][i] = val;
    }
    // verticale
    for (i = t; i <= b; i++){
        MAPPERSON[i][l] = val;
        MAPPERSON[i][r] = val;
    }
}

Elle reçoit en paramètre le personnage ainsi que la valeur à incruster. Ces valeurs sont définies par les deux macros constantes LIBRE et OCCUPE, OCCUPE pour incruster un personnage et LIBRE pour effacer un personnage. Néanmoins il serait possible de remplacer OCCUPE par un identifiant du personnage, cela permettrait à chaque personnage de savoir qui il rencontre en cas de collision.

Pour le parcours du tour, les deux coins haut gauche et bas droite du rectangle du personnage sont obtenus à partir de la position en pixels du personnage, de sa taille en pixels et de la dimension d'une tuile pour la carte des personnages. Ensuite le tracé consiste à parcourir les deux horizontales et les deux verticales.

Cette fonction est appelée une première fois lors de l'initialisation d'un personnage dans la fonction construct_personnage.

 
Sélectionnez
t_personnage* construct_personnage()
{
    t_personnage*j = (t_personnage*)malloc(sizeof(t_personnage));
    j->tx = PIXTILEX;
    j->ty = PIXTILEY;
    j->pas = 4;
    // position (en coordonnées MAPDECOR)
    j->x = rand() % MAPTX;
    j->y = rand() % MAPTY;
    // seules les positions LIBRE sont acceptables
    // sinon chercher la plus proche
    while (MAPDECOR[j->y][j->x] != LIBRE){
        if (rand() % 2)
            j->x = (j->x+1) % MAPTX;
        else
            j->y = (j->y+1) % MAPTY;
    }
    // marquer l'emplacement par le personnage
    MAPDECOR[j->y][j->x] = OCCUPE;
    // convertir la position matrice en position écran
    // (en coordonnées pixels)
    j->x *= PIXTILEX;
    j->y *= PIXTILEY;
    marquer_personnage(j, OCCUPE);
    // animation
    j->tour = 0;
    j->nbtour = 5;
    j->nbimage = 4;
    j->imcourante = 0;
    j->dir = rand() % STOP;
    // au départ les personnages sont en mouvement
    // le joueur est toujours en mouvement.
    j->mobile = 1;
    return j;
}

L'appel doit être placé après que la position du personnage soit convertie en pixels, la fonction en effet s'appuie sur la position en pixels du personnage.

Elle est également appelée dans la fonction bouge_personnage , au tout début pour effacer le personnage et à la fin pour le réincruster à son éventuelle nouvelle position.

XIII-D-3-c. Détection des collisions

Mais il y a une modification plus importante dans la fonction de mouvement, précisément c'est la détection de collisions entre personnages. Le test consiste à parcourir tout le tour du personnage en regardant dans la carte s'il croise celui d'un autre personnage. La fonction prend en paramètre les coordonnées des coins haut-gauche et bas-droite du personnage. Elle retourne 1 si croisement et 0 sinon. Dès qu'un croisement est trouvé, il n'est pas utile de chercher plus et la fonction se termine (return 1) :

 
Sélectionnez
int collision_personnage(int l, int t, int r,int b)
{
    int i;
    for (i = l; i <= r ; i++)
        if (MAPPERSON[t][i] != LIBRE || MAPPERSON[b][i] != LIBRE)
            return 1;
    for (i = t; i <= b; i++)
        if (MAPPERSON[i][l] != LIBRE || MAPPERSON[i][r] != LIBRE
                return 1;
                return 0;
}

Voici comment sont appelées les fonctions marquer_personnage et collision_personnage dans la fonction bouge_personnage :

 
Sélectionnez
int bouge_personnage(int x, int y, t_personnage*j)
{
    int l, r, t, b;
    int mobile = 0; // par défaut immobile
    // mise à LIBRE position actuelle dans la carte
    // des personnages
    marquer_personnage(j, LIBRE);
    // si le personnage reste dans l'écran
    if (x >= 0 && x + j->tx <= PIXMAPX &&
            y >= 0 && y + j->ty <= PIXMAPY){
        // left top right bottom
        l = x / PIXTILEX;
        t = y / PIXTILEX;
        r = (x + j->tx - 1) / PIXTILEX;
        b = (y + j->ty - 1) / PIXTILEY;
        // et si les 4 coins du sprite sont tous sur LIBRE
        if (MAPDECOR[t][l] == LIBRE &&
                MAPDECOR[t][r] == LIBRE &&
                MAPDECOR[b][l] == LIBRE &&
                MAPDECOR[b][r] == LIBRE){
            // left top right bottom
            l = x / PTILEX;
            t = y / PTILEX;
            r = l + (j->tx/PTILEX)-1;
            b = t + (j->ty/PTILEY)-1;
            if (!collision_personnage(l, t, r, b)){
                //avancer
                j->x = x;
                j->y = y;
                mobile = 1;
            }
        }
    }
    marquer_personnage(j, OCCUPE);
    return mobile;
}

XIII-D-3-d. Correction de l'initialisation des personnages

Il reste un détail à traiter mais qui a son importance à propos cette fois de la carte du décor MAPDECOR. En effet, deux personnages peuvent être superposés à l'initialisation et ensuite se paralyser mutuellement. Il est impératif qu'il n'y ait aucune collision à l'initialisation, chaque personnage doit trouver une place libre dans la carte du décor.

Pour ce faire, à l'initialisation d'un personnage, il faut marquer comme OCCUPE dans la carte du décor la position LIBRE qu'il a obtenue, afin qu'aucun des suivants ne puisse l'obtenir.

Dans les programmes précédents, la carte du décor n'était pas modifiée par l'initialisation des personnages. Nous avions dans la fonction construct_decor un appel à la fonction mise_a_libre_ou_occupe qui transformait en LIBRE (0) l'herbe (3) et tout le reste en OCCUPE (-1).

Nous avons modifié cette fonction. Le fait de mettre à OCCUPE n'était pas très utile. Nous avons uniquement conservé la possibilité de mettre à LIBRE. Mais, en plus, nous pouvons préciser quelle valeur mettre à LIBRE. L'objectif est de pouvoir mettre à LIBRE tout ce qui est de l'herbe avec la valeur 3 et une fois que les personnages auront trouvé leur place dans le décor, de pouvoir mettre à LIBRE toutes les places occupées avec la valeur OCCUPE. Voici la fonction mise_a_libre :

 
Sélectionnez
void mise_a_libre_decor(int val)
{
    int y, x;
    for (y = 0; y < MAPTY; y++)
        for (x = 0; x < MAPTX; x++)
            if (MAPDECOR[y][x] == val)
                MAPDECOR[y][x] = LIBRE;
}

Cette fonction doit être appelée avant l'initialisation des personnages pour une mise à libre de l'herbe et après, pour une mise à libre des places occupées :

 
Sélectionnez
mise_a_libre_decor(3);
for (i = 0; i <= JOUEUR; i++)
    all[i] = construct_personnage();
mise_a_libre_decor(OCCUPE);

XIII-D-4. Quelques perfectionnements

L'implémentation de la carte au 1/4 est terminée mais dans le programme, par souci de lisibilité, nous avons encapsulé dans des fonctions tous les parcours du tableau des personnages qui ont lieu dans le main() . Par exemple au lieu d'avoir :

 
Sélectionnez
mise_a_libre_decor(3);
for (i = 0; i <= JOUEUR; i++)
    all[i] = construct_personnage();
mise_a_libre_decor(OCCUPE);

nous avons :

 
Sélectionnez
construct_personnages(allP);

la fonction étant définie un peu plus loin :

 
Sélectionnez
void construct_personnages(t_personnage*all[])
{
    int i;
    mise_a_libre_decor(3);
    for (i = 0; i <= JOUEUR; i++)
        all[i] = construct_personnage();
    mise_a_libre_decor(OCCUPE);
}

Le suffixe _personnages au pluriel dans le nom d'une fonction signifie à chaque fois que la fonction traite l'ensemble des personnages.

XIII-D-5. Code complet commenté

Gestion corrigée des collisions entre personnages
Sélectionnez
// pour utilisation de la fonction sprintf
// jugée unsafe par microsoft
#define _CRT_SECURE_NO_WARNINGS

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

#define SCREENX    640
#define SCREENY    480
#define BLANC al_map_rgb(255,255,255)
#define GRIS al_map_rgb(128,128,128)
#define NOIR        al_map_rgb(0,0,0)

// TUILES ET MAPDECOR
// largeur et hauteur des carreaux en pixels
#define PIXTILEX 32
#define PIXTILEY 32

// taille en pixels de la MAPDECOR
#define PIXMAPX SCREENX
#define PIXMAPY SCREENY

// taille en tuiles de la MAPDECOR 
#define MAPTX (PIXMAPX /PIXTILEX)    // 20
#define MAPTY (PIXMAPY/PIXTILEY)    // 15

// identification pour collisions
#define LIBRE        0
#define OCCUPE        -1

// la carte des tuiles du terrain
short MAPDECOR[MAPTY][MAPTX] = {
    { 3, 3, 3, 7,7,7,55,8,8,2,3,3,3,3,3,3,3, 3, 3,101 },
    { 3, 3, 3, 3,3,3,55,1,1,3,3,3,3,3,3,3,3,101,101,101 },
    { 3, 3, 3, 3,3,3, 3,2,1,3,3,3,2,3,3,3,3, 3,101, 3 },
    { 3, 3, 1, 3,3,3, 3,3,3,3,3,2,2,3,3,3,2,101,101, 3 },
    { 3, 1, 1, 3,3,3, 3,3,3,3,2,2,3,3,3,3,2, 3, 3, 3 },
    { 3, 1, 1, 1,3,3, 3,3,3,8,2,2,2,3,3,1,7, 1, 3, 3 },
    { 3, 3, 3, 1,1,3, 3,3,3,3,1,3,3,3,3,3,3, 3, 3, 3 },
    { 3, 3, 3, 1,3,3, 3,3,1,3,3,3,3,3,3,3,3, 3, 3, 3 },
    { 3, 3, 3, 3,3,3, 3,2,1,3,3,3,3,3,3,3,3, 3, 3, 3 },
    { 4, 4, 3, 3,3,3, 3,2,1,3,3,6,6,3,3,3,3, 3, 4, 4 },
    { 4, 4, 4, 3,3,3, 2,2,3,3,3,4,4,3,3,3,3, 81, 81, 2 },
    { 4, 4, 3, 3,3,3, 2,1,3,3,3,4,1,3,3,3,4, 4, 81, 81 },
    { 4, 4, 4, 3,3,3, 3,1,3,3,3,3,2,3,3,4,4, 4, 81, 81 },
    { 81,81, 4, 3,3,3, 3,1,1,1,3,3,3,3,4,4,4, 2, 4, 81 },
    { 81,81,81,81,3,3, 3,3,3,3,3,3,3,4,4,4,2, 2, 81, 81 }
};

// PERSONNAGE
#define JOUEUR    50


// ensemble des variables qui identifient un personnage
typedef struct{
    int x, y; // position
    int pas; // déplacement
    int tx, ty;// taille

    //animation
    int mobile;
    int tour, nbtour;
    int nbimage; 
    int imcourante;
    int dir;
}t_personnage;

// 4 directions
enum{ LEFT, RIGHT, UP, DOWN, STOP };

// pour clavier fluide
int KEY[STOP] = {};

// tableau des animations :
//4 directions + stop, 4 images pour chaque
ALLEGRO_BITMAP*ANIM[STOP+1][4];

// Carte de contrôle de collisions
#define PTILEX    4
#define PTILEY    4
#define PMAPTX    (PIXMAPX/PTILEX)    // 160
#define PMAPTY    (PIXMAPY/PTILEY)    // 120
int MAPPERSON[PMAPTY][PMAPTX] = {};


ALLEGRO_BITMAP*    construct_decor            (void);
void            mise_a_libre            (int val);
void            construct_personnages    (t_personnage*all[]);
t_personnage*    construct_personnage    (void);
void            marquer_personnage        (t_personnage*j, int val);
void            recup_anim_personnage    (void);
int                bouge_personnage        (int x, int y, 
                                        t_personnage*j);
int                collision_personnage    (int l, int t, int r, int b);
void            affiche_personnages        (t_personnage*all[]);
void            affiche_personnage        (t_personnage*j);
void            affiche_map_personnage    (void);
void            liberer_personnages(t_personnage*all[]);
void            avance_clones(t_personnage*all[]);
void            avance_clone            (t_personnage*j);
void            direction_clone            (t_personnage*j);


void            controle_joueur        (ALLEGRO_EVENT*e,
                                    t_personnage*j);
void            avance_joueur        (t_personnage*j);
ALLEGRO_BITMAP*    recup_sprite        (ALLEGRO_BITMAP*scr,
                                    int tx, int ty,
                                    int startx, int starty,
                                    int colonne, int i);
ALLEGRO_DISPLAY*allegro_init        (ALLEGRO_EVENT_QUEUE**queue,
                                    ALLEGRO_TIMER**timer);
void            erreur                (const char*msg);
/*****************************************************************
*****************************************************************/
int main()
{
    ALLEGRO_DISPLAY*display;
    ALLEGRO_EVENT_QUEUE*queue;
    ALLEGRO_TIMER*timer;
    ALLEGRO_BITMAP*decor;

    // l'ensemble des personnages avec le joueur
    // placé en dernier
    t_personnage* allP[JOUEUR+1]; 
    
    bool fin = 0;
    bool dessine = true;
    int i;

    display = allegro_init(&queue, &timer);

    decor = construct_decor();
    recup_anim_personnage();
    
    // revenir à l'affichage écran
    al_set_target_backbuffer(display);

    // création des personnages
    construct_personnages(allP);
    
    while (!fin){

        ALLEGRO_EVENT event;
        al_wait_for_event(queue, &event);

        controle_joueur(&event, allP[JOUEUR]);

        if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE ||
            event.keyboard.keycode == ALLEGRO_KEY_ESCAPE)
            fin = true;

        else if (event.type == ALLEGRO_EVENT_TIMER){
            // mouvement 
            avance_joueur(allP[JOUEUR]);
            avance_clones(allP);
            dessine = true;
        }

        if (dessine == true && al_is_event_queue_empty(queue)){
            al_draw_bitmap(decor, 0, 0, 0);
            affiche_personnages(allP);
            //affiche_map_personnage();

            al_flip_display();
            dessine = false;
        }
    }
    liberer_personnages(allP);
    al_destroy_bitmap(decor);
    al_destroy_display(display);
    al_destroy_timer(timer);
    al_destroy_event_queue(queue);
    return 0;
}
/****************************************************************
DECOR / Création de la bitmap du decor
****************************************************************/
ALLEGRO_BITMAP* construct_decor()
{
ALLEGRO_BITMAP*decor, *bmp;
ALLEGRO_BITMAP*tuiles;
int mapx, mapy, posx, posy;

    // bitmap decor et load tiles
    decor = al_create_bitmap(PIXMAPX, PIXMAPY);
    tuiles = al_load_bitmap(".\\images\\kit_de_tuiles_2.png");
    if (!decor || !tuiles)
        erreur("decor, tuiles");

    // construction du decor
    for (mapy = 0; mapy<MAPTY; mapy++)
        for (mapx = 0; mapx<MAPTX; mapx++){
            bmp = recup_sprite(    
                        tuiles, // fichier
                        PIXTILEX, PIXTILEY,// taille tuiles
                        0, 0, // pos départ
                        20, // nombre de colonnes
                        MAPDECOR[mapy][mapx]);// N° de tuile
            if (!bmp)
                erreur("recup_sprite()");
            posx = mapx*PIXTILEX;
            posy = mapy*PIXTILEY;
            
            al_set_target_bitmap(decor);
            al_draw_bitmap(bmp, posx, posy, 0);
            al_draw_rectangle(    posx, posy, 
                                posx+PIXTILEX, posy+PIXTILEY, 
                                GRIS, 1);

            al_destroy_bitmap(bmp);
    }
    // libérer mémoire image inutile
    al_destroy_bitmap(tuiles);

    // retour adresse du décor
    return decor;
}
/****************************************************************
DECOR / met à LIBRE dans la map DECOR les positions de valeur val
****************************************************************/
void mise_a_libre_decor(int val)
{
int y, x;
    for (y = 0; y < MAPTY; y++)
        for (x = 0; x < MAPTX; x++)
            if (MAPDECOR[y][x] == val)
                MAPDECOR[y][x] = LIBRE;
}
/*****************************************************************
PERSONNAGES / création 
*****************************************************************/
void construct_personnages(t_personnage*all[])
{
int i;
    mise_a_libre_decor(3);
    for (i = 0; i <= JOUEUR; i++)
        all[i] = construct_personnage();
    mise_a_libre_decor(OCCUPE);
}
t_personnage* construct_personnage()
{
    t_personnage*j = (t_personnage*)malloc(sizeof(t_personnage));

    j->tx = PIXTILEX;
    j->ty = PIXTILEY;
    j->pas = 4;

    // position (en coordonnées MAPDECOR)
    j->x = rand() % MAPTX;
    j->y = rand() % MAPTY;
    // seules les positions LIBRE sont acceptables
    // si non chercher la plus proche
    while (MAPDECOR[j->y][j->x] != LIBRE){
        if (rand() % 2)
            j->x = (j->x+1) % MAPTX;
        else
            j->y = (j->y+1) % MAPTY;
    }
    // marquer l'emplacement par le personnage
    MAPDECOR[j->y][j->x] = OCCUPE;
    
    // convertir la position matrice en position écran 
    // (en coordonnées pixels)
    j->x *= PIXTILEX;
    j->y *= PIXTILEY;
    marquer_personnage(j, OCCUPE);

    // animation
    j->tour = 0;
    j->nbtour = 5;
    j->nbimage = 4;
    j->imcourante = 0;
    j->dir = rand() % STOP;
    // au départ les personnages sont en mouvement
    // le joueur est toujours en mouvement.
    j->mobile = 1;
    return j;
}
/*****************************************************************
PERSONNAGES / Affichage
*****************************************************************/
void marquer_personnage(t_personnage*j, int val)
{
    int l, r,t,b, i;
    l = j->x / PTILEX;
    r = l + (j->tx/PTILEX)-1;

    t = j->y / PTILEY;
    b = t +(j->ty/PTILEY)-1;

    // horizontales
    for (i = l; i <= r; i++){
        MAPPERSON[t][i] = val;
        MAPPERSON[b][i] = val;
    }
    // verticale
    for (i = t; i <= b; i++){
        MAPPERSON[i][l] = val;
        MAPPERSON[i][r] = val;
    }
}
/*****************************************************************
PERSONNAGES / Affichage
*****************************************************************/
void affiche_personnages(t_personnage*all[])
{
int i;
    for (i = 0; i <= JOUEUR;i++)
        affiche_personnage(all[i]);
}
void affiche_personnage(t_personnage*j)
{
int dir;
    // l'animation est permanente
    j->tour++;
    if (j->tour == j->nbtour){
        j->imcourante = (++j->imcourante) % j->nbimage;
        j->tour = 0;
    }

    // affichage du joueur
    dir = (j->mobile) ? j->dir : STOP;
    al_draw_scaled_bitmap(ANIM[dir][j->imcourante],
        0, 0, 64, 64,    // source
        j->x, j->y,        // cible
        j->tx, j->ty,
        0);

}
/*****************************************************************
PERSONNAGES / Affichage rectangle de contrôle
*****************************************************************
void affiche_map_personnage()
{
    int x, y;
    ALLEGRO_COLOR c;
    for (y = 0; y < PMAPTY;y++)
    for (x = 0; x < PMAPTX; x++){
        c = (MAPPERSON[y][x] == LIBRE) ? BLANC : NOIR;
        al_draw_filled_rectangle(x*PTILEX, y*PTILEY,
            x*PTILEX + PTILEX,
            y*PTILEY + PTILEY,
            c);
    }
}
/*****************************************************************
PERSONNAGES / récupération des 4 animations communes à tous
*****************************************************************/
void recup_anim_personnage()
{
    int dir, i;
    char nom[256];

    for (dir = 0; dir <= STOP; dir++){// attention <=
        for (i = 0; i < 4; i++){
            sprintf(nom, ".\\images\\joueur\\hero_%d_%d.bmp", dir, i);
            ANIM[dir][i] = al_load_bitmap(nom);
            if (!ANIM[dir][i])
                erreur("ANIM[dir][i] = al_load_bitmap(nom)");
            al_convert_mask_to_alpha(ANIM[dir][i],
                al_get_pixel(ANIM[dir][i], 0, 0));
        }
    }
}
/*****************************************************************
PERSONNAGES / controle du déplacement
*****************************************************************/
int bouge_personnage(int x, int y, t_personnage*j)
{
    int l, r, t, b;
    int mobile = 0; // par défaut immobile

    // mise à LIBRE position actuelle dans la carte 
    // des personnages
    marquer_personnage(j, LIBRE);

    // si le personnage reste dans l'écran
    if (x >= 0 && x + j->tx <= PIXMAPX &&
        y >= 0 && y + j->ty <= PIXMAPY){

        // left top right bottom
        l = x / PIXTILEX;
        t = y / PIXTILEX;
        r = (x + j->tx - 1) / PIXTILEX;
        b = (y + j->ty - 1) / PIXTILEY;

        // et si les 4 coins du sprite sont tous sur LIBRE
        if (MAPDECOR[t][l] == LIBRE &&
            MAPDECOR[t][r] == LIBRE &&
            MAPDECOR[b][l] == LIBRE &&
            MAPDECOR[b][r] == LIBRE){

            // left top right bottom
            l = x / PTILEX;
            t = y / PTILEX;
            r = l + (j->tx/PTILEX)-1;
            b = t + (j->ty/PTILEY)-1;

            if (!collision_personnage(l, t, r, b)){
                //avancer
                j->x = x;
                j->y = y;
                mobile = 1;
            }
        }
    }
    marquer_personnage(j, OCCUPE);

    return mobile;
}
/*****************************************************************
PERSONNAGES / collisions
*****************************************************************/
int collision_personnage(int l, int t, int r,int b)
{
    int i;

    for (i = l; i <= r ; i++)
        if (MAPPERSON[t][i] != LIBRE || MAPPERSON[b][i] != LIBRE)
            return 1;
    for (i = t; i <= b; i++)
        if (MAPPERSON[i][l] != LIBRE || MAPPERSON[i][r] != LIBRE)
            return 1;
    return 0;
}
/*****************************************************************
PERSONNAGES / contrôle d'erreurlibérer
*****************************************************************/
void liberer_personnages(t_personnage*all[])
{
    int i;
    for (i = 0; i <= JOUEUR; i++)
        free(all[i]);

}
/*****************************************************************
CLONES / déplacement automatique
*****************************************************************/
void avance_clones(t_personnage*all[])
{
    int i;
    for (i = 0; i < JOUEUR; i++)
        avance_clone(all[i]);
}
void avance_clone(t_personnage*j)
{
    int x, y;
    // copie position et taille
    y = j->y;
    x = j->x;
    
    // avance selon direction
    switch (j->dir){
        case LEFT:    x -= j->pas;    break;
        case RIGHT: x += j->pas;    break;
        case UP:    y -= j->pas;    break;
        case DOWN:    y += j->pas;    break;
    }
    // si immobile chercher une direction
    j->mobile=bouge_personnage(x, y, j);
    if (!j->mobile )
        direction_clone(j);    
}

/*****************************************************************
CLONES / choix direction
Le principe est :
- de tirer une direction aléatoire
- si impossible (cause bords, terrain)
- prendre la suivante 
*****************************************************************/
void direction_clone(t_personnage*j)
{
    int sens = (rand() % 2) * 2 - 1; //-1 ou 1
    int dir = rand() % STOP;    
    int res = -1;

    // passer en coordonnées MAPDECOR
    int x = j->x / PIXTILEX;
    int y = j->y / PIXTILEX;

    // vérifier la direction, si impossible prendre la 
    // suivante en tournant selon "sens" (vers gauche ou droite)
    do{
        dir = ((dir + sens) + STOP) % STOP;
        switch (dir){
            case LEFT:
                if (x - 1 >= 0 && MAPDECOR[y][x - 1] == LIBRE)
                    res = LEFT;
                break;
            case RIGHT:
                if (x + 1<MAPTX && MAPDECOR[y][x + 1] == LIBRE)
                    res = RIGHT;
                break;
            case UP:
                if (y - 1 >= 0 && MAPDECOR[y - 1][x] == LIBRE)
                    res = UP;
                break;
            case DOWN:
                if (y + 1<MAPTY && MAPDECOR[y + 1][x] == LIBRE)
                    res = DOWN;
                break;
        }
        
    } while (res < 0 );
    
    j->dir = res;    
}
/*****************************************************************
JOUEUR / contrôle clavier du joueur
*****************************************************************/
void controle_joueur(ALLEGRO_EVENT*e, t_personnage*j)
{
    if (e->type == ALLEGRO_EVENT_KEY_DOWN){
        switch (e->keyboard.keycode){
        case ALLEGRO_KEY_LEFT:    //82
        case ALLEGRO_KEY_RIGHT:    //83
        case ALLEGRO_KEY_UP:    //84
        case ALLEGRO_KEY_DOWN:    //85
            j->dir = e->keyboard.keycode - ALLEGRO_KEY_LEFT;
            KEY[j->dir] = 1;
            break;
        }
    }
    else if (e->type == ALLEGRO_EVENT_KEY_UP){
        switch (e->keyboard.keycode){
        case ALLEGRO_KEY_LEFT:
        case ALLEGRO_KEY_RIGHT:
        case ALLEGRO_KEY_UP:
        case ALLEGRO_KEY_DOWN:
            j->dir = e->keyboard.keycode - ALLEGRO_KEY_LEFT;
            KEY[j->dir] = 0;
            break;
        }
    }
}
/*****************************************************************
JOUEUR / déplacement
*****************************************************************/
void avance_joueur(t_personnage*j)
{
    int x, y;

    // avancer
    y = j->y;
    x = j->x;

    x -= KEY[LEFT] * j->pas;
    x += KEY[RIGHT] * j->pas;
    y -= KEY[UP] * j->pas;
    y += KEY[DOWN] * j->pas;

    // le joueur est toujours en mouvement
    bouge_personnage(x, y, j);
    
}
/*****************************************************************
TOOLS / Récupérer les tuiles
*****************************************************************/
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;
}
/*****************************************************************
TOOLS / Initialisations allegro
*****************************************************************/
ALLEGRO_DISPLAY* allegro_init(    ALLEGRO_EVENT_QUEUE**queue,
                                ALLEGRO_TIMER**timer)
{
ALLEGRO_DISPLAY*display;

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

    if (!al_init_primitives_addon())
        erreur("al_init_primitives_addon()");

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

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

    //al_set_new_display_flags(ALLEGRO_FULLSCREEN);
    display = al_create_display(SCREENX, SCREENY);
    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));

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

    return display;
}
/*****************************************************************
TOOLS / contrôle d'erreur
*****************************************************************/
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);
}
/*****************************************************************
*****************************************************************/

XIII-D-5-a. Capture d'écran

Image non disponible

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édentsommaire

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.