Allegro 5

Programmation de jeux en C ou C++


précédentsommairesuivant

XII. Personnages dans un décor

XII-A. Introduction

Maintenant que nous savons comment obtenir un décor, nous allons nous pencher sur les interactions entre personnages et décor.

XII-B. Un personnage mobile dans un décor fixe

Première expérimentation : implémenter un seul personnage déplacé avec les flèches sur un décor fixe composé de tuiles.

XII-B-1. Structures de données et initialisations

XII-B-1-a. Initialisation Allegro

Toutes les initialisations d'Allegro sont regroupées dans la fonction :

 
Sélectionnez
ALLEGRO_DISPLAY* allegro_init(ALLEGRO_EVENT_QUEUE**queue, ALLEGRO_TIMER**timer);

La fonction retourne une fenêtre d'affichage display et également par référence une file d'événements ainsi qu'un minuteur. Elle est appelée en tout premier, au commencement du programme.

XII-B-1-b. Construction du décor

Le décor est construit à partir d'une matrice d'entiers fournie en dur dans le programme. Le principe est identique à celui du programme vu dans le chapitre Décor, monde à la section Utiliser une matrice de nombresUtiliser une matrice de nombres. Mais afin de dégager le main() , nous avons placé cette opération dans la fonction :

 
Sélectionnez
ALLEGRO_BITMAP* construct_decor() ;

qui retourne la bitmap du décor. Le jeu de tuiles est sur un fichier nommé kit_de_tuiles_2.png. Il est dans un dossier nommé images qui se trouve dans le répertoire du programme.

Cette fonction est appelée après les initialisations d'Allegro, avant la boucle d'événements.

XII-B-1-c. Récupération de l'animation (sprites joueur)

Toujours dans la constitution des ressources nécessaires au fonctionnement du programme, l'animation pour le personnage est récupérée avec la fonction :

 
Sélectionnez
void recup_animation(void) ;

L'animation consiste en un personnage qui marche dans quatre directions (gauche, droite, haut, bas) avec quatre images par direction. Elle est stockée en global dans une matrice de 4x4 bitmaps :

 
Sélectionnez
ALLEGRO_BITMAP*ANIM[NBDIR][4];

Sur le disque dur chacune des 16 images de l'animation est un fichier .bmp stocké dans un dossier nommé joueur, lui-même dans le dossier images. Les fichiers sont numérotés avec en premier le numéro de direction et en second le numéro d'image dans l'animation. Les numéros de direction sont calés sur l'ordre des macros constantes d'Allegro pour les flèches :

 
Sélectionnez
ALLEGRO_KEY_LEFT // 82
ALLEGRO_KEY_RIGHT // 83
ALLEGRO_KEY_UP // 84
ALLEGRO_KEY_DOWN // 85

En soustrayant la valeur de ALLEGRO_KEY_LEFT nous obtenons la valeur de la direction :

 
Sélectionnez
LEFT : 0
RIGHT : 1
UP : 2
DOWN : 3

Ces quatre identifiants de direction sont définis dans un enum :

 
Sélectionnez
enum{LEFT,RIGHT,UP,DOWN,NBDIR} ;

Ils sont utilisés pour le défilement des images des animations selon les directions, par exemple :

ANIM[LEFT][0] correspond à l'image 1 de l'animation vers la gauche.

ANIM[DOWN][1] correspond à l'image 2 de l'animation vers le bas.

etc.

XII-B-1-d. La carte du décor (map)

La carte est une matrice de short définie en global dans le programme :

 
Sélectionnez
#define MAPTX 20
#define MAPTY 15
const short MAP[MAPTY][MAPTX] = { /*toutes les valeurs*/... } ;

La taille en pixels d'une tuile est donnée avec deux macros constantes :

 
Sélectionnez
#define PIXTILEX 32
#define PIXTILEY 32

À partir de la taille de la matrice et de la taille des tuiles, nous obtenons la taille en pixels du décor en bitmap :

 
Sélectionnez
#define PIXMAPX (MAPTX*PIXTILEX)
#define PIXMAPY (MAPTY*PIXTILEY)

Mettre des parenthèses autour du calcul pour le cas où la macro serait intégrée dans une expression. Par exemple, sans parenthèse l'expression :

 
Sélectionnez
val / PIXMAPX

vaut :

 
Sélectionnez
val / MAPTX * PIXTILEX

c'est-à-dire :

 
Sélectionnez
(val / MAPTX) * PIXTILEX // associativité gauche

Avec parenthèses l'expression vaut :

 
Sélectionnez
val / (MAPTX*PIXTILEX)

Ce qui n'est pas la même chose.

XII-B-1-e. Personnage

Un personnage est à une position, il avance avec un pas, il a une taille. Une animation lui correspond qui comprend un nombre total d'images, une image courante selon une direction. Afin de contrôler la vitesse de défilement de l'animation, nous ajoutons un nombre de tours d'affichage de la même image et un compte tour (technique présentée à la section Contrôle de la vitesse de l'animation du chapitre Animation, spritesAnimations, sprites).

Toutes les variables correspondantes, nécessaires au fonctionnement d'un personnage sont rassemblées dans une structure :

 
Sélectionnez
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;

Cette structure est accompagnée d'une fonction constructeur qui permet d'obtenir un joueur. La fonction alloue et initialise un personnage. La position au départ est choisie au hasard. La seule contrainte est que le personnage doit obligatoirement se trouver sur de l'herbe, c'est-à-dire sur des tuiles n°3 de la matrice terrain.

Notre personnage joueur possède la taille d'une tuile, le plus simple est de le caler sur une seule tuile. Ainsi nous devons trouver une seule tuile de type 3 et non plusieurs s'il devait se trouver à cheval sur plusieurs.

L'idée est de tirer une position aléatoire dans la matrice terrain et tant qu'elle n'est pas de type 3 en prendre une à côté quasi systématiquement jusqu'à tomber sur une bonne.

Une fois obtenue une position correcte dans la matrice, cette position est convertie en coordonnée-écran. Pour l'obtenir, il suffit de multiplier la position matrice par la taille de tuile.

Voici la fonction :

 
Sélectionnez
t_personnage* constructeur_joueur()
{
    t_personnage*j = (t_personnage*)malloc(sizeof(t_personnage));
    j->tx = PIXTILEX;
    j->ty = PIXTILEY;
    j->pas = 8;
    // position (en coordonnées map)
    j->x = rand() % MAPTX;
    j->y = rand() % MAPTY;
    // seules les positions sur 3 sont acceptables,
    // si la position obtenue n'est pas 3,
    // en chercher une à partir de celle où on est
    while (MAP[j->y][j->x] != 3){
        if (rand() % 2)
            j->x = (++j->x) % MAPTX;
        else
            j->y = (++j->y) % MAPTY;
    }
    // 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;
}

Le joueur est déplacé au clavier et afin d'assurer une bonne fluidité, nous avons repris la technique présentée une première fois à la section Donner de la fluidité aux mouvements du rectangle du chapitre Les événementsÉvénements. Nous avons ainsi un tableau d'entiers KEY visible en global :

 
Sélectionnez
int KEY[NBDIR] ; // 4 entiers

Il est utilisé avec les quatre identifiants de direction LEFT, RIGHT, UP, DOWN.

XII-B-2. Contrôle du joueur

Le contrôle complet du joueur est réparti sur trois fonctions :

  • La première récupère les entrées clavier.
  • La seconde fait avancer le joueur. Elle contrôle les sorties sur les bords ainsi que les collisions avec le terrain.
  • La troisième affiche le joueur.

XII-B-2-a. Réponse au clavier

Nous avons clarifié la boucle d'événements avec des fonctions qui regroupent les instructions utiles. Ainsi pour le contrôle au clavier des déplacements du joueur, nous avons la fonction :

 
Sélectionnez
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;
        }
    }
}

L'utilisation du switch est originale : quelle que soit l'entrée sur les touches qui nous intéressent, l'action est la même. S'il s'agit d'une flèche appuyée, le joueur prend sa direction et il faut mettre à 1 la position correspondante dans le tableau KEY :

 
Sélectionnez
j->dir = e->keyboard.keycode - ALLEGRO_KEY_LEFT;
KEY[j->dir] = 1;

La direction c'est la valeur de la touche flèche moins la valeur ALLEGRO_KEY_LEFT ce qui donne les indices correspondants dans le tableau KEY :

 
Sélectionnez
82-82 = 0
83-82 = 1
84-82 = 2
85-82 = 3

Cette fonction est appelée en premier, dès qu'un événement est capturé par al_wait_for_event() .

XII-B-2-b. Mouvement du joueur

Si le joueur avance, il avance dans la direction indiquée par la flèche appuyée mais il faut vérifier qu'il reste dans l'écran et qu'il reste sur du sol autorisé. Dans notre décor, il n'a droit qu'à l'herbe identifiée par le nombre 3 dans la matrice du terrain.

Les coordonnées du joueur sont en pixels pour l'affichage à l'écran alors pour vérifier dans la matrice la qualité du terrain sur lequel il avance, nous devons passer en coordonnées matrice. C'est obtenu en divisant les coordonnées pixels par la taille en pixels d'une tuile :

 
Sélectionnez
coordonnées matrice = (x / PIXTILEX, y / PIXTILEY)

Mais un personnage peut se trouver à cheval sur plusieurs tuiles :

Image non disponible

Sur le schéma, le personnage est posé sur quatre tuiles et nous voyons que les coins du personnage permettent de détecter une collision avec une tuile du décor.

Ainsi le principe est simple pour le déplacement du personnage :

  • Simuler l'avancement dans une copie de ses coordonnées.
  • À partir de cette copie, convertir les quatre coins du personnage en coordonnées matrice et vérifier dans la matrice que le terrain est acceptable. C'est-à-dire que chacun des coins repose sur de l'herbe (3 dans la matrice).
  • Si oui, concrétiser le déplacement en affectant les valeurs de la copie aux coordonnées originales du personnage.
  • Sinon le personnage ne peut pas avancer dans cette direction, il ne bouge pas et il suffit de ne rien faire.

Sur ce principe, les mouvements du joueur sont effectués avec la fonction :

 
Sélectionnez
void avance_joueur(t_personnage*j)
{
    int x, y, tx, ty, cx1, cx2, cy1, cy2;
    // copie position et taille
    y = j->y;
    x = j->x;
    tx = j->tx;
    ty = j->ty;
    // 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;
    // si le joueur reste dans l'écran
    if (
            x >= 0 && x + tx <= PIXMAPX &&
            y >= 0 && y + ty <= PIXMAPY
            ){
        // les quatre coins du joueur en coordonnées map
        // (valeurs entières uniquement)
        cx1 = x / PIXTILEX;
        cy1 = y / PIXTILEX;
        cx2 = (x + tx-1) / PIXTILEX;
        cy2 = (y + ty-1) / PIXTILEY;
        // S'ils sont tous sur du 3 (herbe)
        if (MAP[cy1][cx1] == 3 && MAP[cy1][cx2] == 3 &&
                MAP[cy2][cx1] == 3 && MAP[cy2][cx2] == 3){
            //avancer
            j->x = x;
            j->y = y;
        }
    }
}

Quand et où la fonction est-elle appelée ? La fonction est appelée à chaque événement du minuteur. C'est lui qui impulse tous les mécanismes à l'œuvre dans le jeu.

XII-B-2-c. Affichage du joueur

L'animation est permanente. Même s'il ne peut pas avancer, le joueur marche sur place. Le contrôle de l'animation, le compte des tours et le choix de l'image courante, ont lieu dans la fonction d'affichage. L'affichage proprement dit est un appel à la fonction d'Allegro al_draw_scaled_bitmap() de façon à ce que le joueur, dont les images font 64 pixels par 64 pixels, adopte la taille d'une tuile qui fait 32 pixels par 32 pixels.

En effet si les images du joueur sont plus grandes que les tuiles, il faut modifier le principe de la collision avec le terrain. Notamment parce que le joueur peut alors recouvrir et englober un espace interdit du fait de sa taille.

La prise en compte des seuls coins ne suffit plus, l'intérieur du personnage doit aussi être pris en compte, ce qui complique un peu la tâche.

Voici la fonction d'affichage :

 
Sélectionnez
void affiche_joueur(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,
            j->x, j->y,
            // source
            // cible
            j->tx, j->ty,
            0);
}

L'affichage a lieu dans le if spécifique si le dessin est demandé et si la file d'événements est vide.

XII-B-2-c-i. Code complet commenté
Un personnage déplacé au clavier dans un décor
Sélectionnez
#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

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

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

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

#define BLANC al_map_rgb(255,255,255)
#define GRIS al_map_rgb(128,128,128)

// la carte des tuiles du terrain
const short MAP[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
// ensemble des variables qui identifient le joueur
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];

ALLEGRO_BITMAP*    constructeur_decor    (void);
t_personnage*    constructeur_joueur    (void);
void            recup_animation        (void);
ALLEGRO_BITMAP*    recup_sprite        (ALLEGRO_BITMAP*scr,
                                    int tx, int ty,
                                    int startx, int starty,
                                    int colonne, int i);
void            controle_joueur        (ALLEGRO_EVENT*e,
                                    t_personnage*j);
void            avance_joueur        (t_personnage*j);
void            affiche_joueur        (t_personnage*j);
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;
    t_personnage* joueur;
    

    bool fin = 0;
    bool dessine = true;

    display = allegro_init(&queue, &timer);

    decor = constructeur_decor();
    joueur = constructeur_joueur();
    recup_animation();
    
    // revenir à l'affichage écran
    al_set_target_backbuffer(display);

    while (!fin){

        ALLEGRO_EVENT event;
        al_wait_for_event(queue, &event);

        controle_joueur(&event, 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(joueur);

            // redessiner
            dessine = true;
        }
        // affichage
        if (dessine == true && al_is_event_queue_empty(queue)){

            al_draw_bitmap(decor, 0, 0, 0);
            affiche_joueur(joueur);
            al_flip_display();

            dessine = false;
        }
    }
    
    free(joueur);
    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* constructeur_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
                                MAP[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;
}
/*****************************************************************
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, tx, ty, cx1, cx2, cy1, cy2;
    // copie position et taille
    y = j->y;
    x = j->x;
    tx = j->tx;
    ty = j->ty;

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

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

        // les quatre coins du joueur en coordonnées map
        // (valeurs entières uniquement)
        cx1 = x / PIXTILEX;
        cy1 = y / PIXTILEX;
        cx2 = (x + tx-1) / PIXTILEX;
        cy2 = (y + ty-1) / PIXTILEY;

        // S'ils sont tous sur du 3 (herbe) 
        if (MAP[cy1][cx1] == 3 && MAP[cy1][cx2] == 3 &&
            MAP[cy2][cx1] == 3 && MAP[cy2][cx2] == 3){

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

        }    
    }
}
/*****************************************************************
JOUEUR / Affichage
******************************************************************/
void affiche_joueur(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);

}
/*****************************************************************
JOUEUR / création d'un joueur
******************************************************************/
t_personnage* constructeur_joueur()
{
    t_personnage*j = (t_personnage*)malloc(sizeof(t_personnage));

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

    // position (en coordonnées map)
    j->x = rand() % MAPTX;
    j->y = rand() % MAPTY;
    // seules les position sur 3 sont acceptables, si la position 
    // obtenue n'est pas 3 en chercher une à partir de celle où
    // on est
    while (MAP[j->y][j->x] != 3){
        if (rand() % 2)
            j->x = (++j->x) % MAPTX;
        else
            j->y = (++j->y) % MAPTY;
    }
    // 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;
}
/*****************************************************************
JOUEUR / récupération des 4 animations du joueur
******************************************************************/
void recup_animation()
{
    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));
        }
    }
}
/*****************************************************************
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);
}
/*****************************************************************
*****************************************************************/
XII-B-2-c-ii. Capture d'écran
Image non disponible

XII-C. Nombreux personnages mobiles

Nous souhaitons maintenant disposer d'un personnage dirigé par les flèches avec simultanément une multitude de clones autodirigés qui vont et viennent dans ce monde de façon autonome. Les collisions entre personnages ne sont pas gérées par le programme.

Première opération : copier-coller le programme précédent dans un nouveau projet.

Tout ce qui concerne le décor et l'animation reste rigoureusement identique, ce qui diffère ce sont les clones dirigés automatiquement qu'il faut ajouter. Le traitement du mouvement du joueur est également légèrement modifié.

XII-C-1. Un ensemble de personnages

XII-C-1-a. Structure de données

Tous les clones partagent la même animation, identique à celle du programme précédent. Il y a toujours quatre directions définies dans un enum et quatre images par direction accessibles en global :

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

Les touches flèches sont toujours doublées par un tableau en global répondant aux quatre directions de l'enum correspondant pour une plus grande fluidité de ses déplacements :

 
Sélectionnez
int KEY[NBDIR]={} ;

La multitude des clones ainsi que le joueur tiennent dans un tableau de personnages localisé dans le main() :

 
Sélectionnez
#define JOUEUR 50
t_personnage* allP[JOUEUR+1];

Le joueur est le personnage du dernier indice identifié avec la macro JOUEUR qui donne aussi le nombre total des personnages (JOUEUR+1).

Il est intéressant de le ranger avec les autres clones, pour bénéficier des fonctions communes à tous les personnages. Ce sont les fonctions constructeur, de contrôle du déplacement et d'affichage. Une unique boucle permet de gérer tout le monde.

Le joueur nécessite en plus deux fonctions spécifiques à lui seul, une pour le contrôle des flèches du clavier et l'autre pour ses déplacements qui en dépendent.

Les clones recourent également à deux fonctions propres, une pour avancer automatiquement qui en requiert une autre pour le choix d'une direction acceptable en cas de collision avec un obstacle sur le terrain.

Pour mieux les différencier et rendre plus lisible le programme, les fonctions communes à tous les personnages (joueur et clones) comportent le suffixe _personnage . Les fonctions spécifiques aux clones prennent le suffixe _clone et les fonctions propres au joueur le suffixe _joueur .

XII-C-1-b. Initialisations générales

Nous retrouvons comme dans le programme précédent :

L'initialisation générale d'Allegro avec la fonction :

 
Sélectionnez
ALLEGRO_DISPLAY*allegro_init(ALLEGRO_EVENT_QUEUE**queue, ALLEGRO_TIMER**timer);

La construction du décor avec la fonction :

 
Sélectionnez
ALLEGRO_BITMAP* construct_decor(void);

Nous retrouvons également les fonctions outils :

 
Sélectionnez
ALLEGRO_BITMAP*recup_sprite(ALLEGRO_BITMAP*scr,
                            int tx, int ty,
                            int startx, int starty,
                            int colonne, int i);
void erreur(const char*msg);

XII-C-1-c. Initialisation des personnages

La fonction du programme précédent :

 
Sélectionnez
t_personnage* constructeur_joueur()

est rebaptisée en :

 
Sélectionnez
t_personnage* construct_personnage(void);

mais c'est la même. Seule différence, le pas d'avancement est passé à 4 ce qui est plus harmonieux pour les déplacements des clones.

L'appel est le suivant avant la boucle d'événements :

 
Sélectionnez
for (i = 0; i <= JOUEUR;i++)
    allP[i]= construct_personnage();

Remarquez bien le <= qui inclut le traitement du joueur dans la boucle.

XII-C-1-d. Affichage des personnages

La fonction de récupération des animations ne change pas. Mais elle est rebaptisée :

 
Sélectionnez
void recup_animation (void);

devient :

 
Sélectionnez
void recup_anim_personnage(void) ;

L'affichage reste identique, c'est la fonction :

 
Sélectionnez
void affiche_personnage(t_personnage*j)

L'appel est le suivant au même endroit dans la partie dessin de la boucle d'événements :

 
Sélectionnez
for (i = 0; i <= JOUEUR; i++) // joueur compris
    affiche_personnage(allP[i]);

XII-C-1-e. Contrôle des déplacements des personnages

Le joueur et les clones sont soumis aux mêmes contraintes pour se déplacer : obligation de rester sur le terrain et sur l'herbe (tuiles n°3).

Dans le programme précédent, le déplacement du joueur et les tests nécessaires sont regroupés au sein de la même fonction. Au début se trouve l'avancement, ensuite viennent les tests :

 
Sélectionnez
void avance_joueur(t_personnage*j)
{
    int x, y, tx, ty, cx1, cx2, cy1, cy2;
    // copie position et taille
    y = j->y;
    x = j->x;
    tx = j->tx;
    ty = j->ty;
    // 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;
    // si le joueur reste dans l'écran
    if (x >= 0 && y >= 0 && x + tx <= PIXMAPX && y + ty <= PIXMAPY){
        // les quatre coins du joueur en coordonnées map
        // (valeurs entières uniquement)
        cx1 = x / PIXTILEX;
        cy1 = y / PIXTILEX;
        cx2 = (x + tx-1) / PIXTILEX;
        cy2 = (y + ty-1) / PIXTILEY;
        // S'ils sont tous sur du 3 (herbe)
        if (MAP[cy1][cx1] == 3 && MAP[cy1][cx2] == 3 &&
                MAP[cy2][cx1] == 3 && MAP[cy2][cx2] == 3){
            //avancer
            j->x = x;
            j->y = y;
        }
    }
}

Les tests sont communs à tous les personnages alors l'idée est d'extraire cette partie commune et d'en faire une fonction à part qui retourne le résultat du test. Ce test s'effectue sur une copie de la position d'un personnage à laquelle est additionné son déplacement. Ainsi la fonction prend en entrée cette copie qui simule l'avancement avec le personnage concerné et elle exécute le test pour finalement retourner son résultat. C'est la fonction :

 
Sélectionnez
int cntl_avance_personnage(int x, int y, t_personnage*j)
{
    int cx1, cx2, cy1, cy2;
    int res = 0;
    // si le personnage reste dans l'écran
    if ( x >= 0 && x + j->tx <= PIXMAPX &&
         y >= 0 && y + j->ty <= PIXMAPY){
        // les 2 coins (top,left) et (right,botom)
        cx1 = x / PIXTILEX;
        cy1 = y / PIXTILEX;
        cx2 = (x + j->tx - 1) / PIXTILEX;
        cy2 = (y + j->ty - 1) / PIXTILEY;
        // et si les 4 coins du sprite sont tous sur du 3 (herbe)
        if (MAP[cy1][cx1] == 3 && MAP[cy1][cx2] == 3 &&
                MAP[cy2][cx1] == 3 && MAP[cy2][cx2] == 3){
            //avancer
            res = 1;
        }
    }
    return res;
}

XII-C-2. Le mouvement des clones

Les clones vont dans une direction indiquée par le champ dir de la structure t_personnage jusqu'à ce qu'ils rencontrent un obstacle. Stoppés par un obstacle, ils prennent au hasard une nouvelle direction parmi les directions possibles.

Comme pour le joueur, le mouvement est d'abord simulé sur une copie de la position du clone. Selon la direction prise, la copie de position est modifiée et l'avance est simulée. Ensuite si la fonction de contrôle de mouvement retourne 1, c'est-à-dire si la direction est autorisée, la position réelle est modifiée et le clone avance. En revanche si le contrôle retourne 0, le clone est devant un obstacle et il doit trouver une nouvelle direction. La recherche d'une direction est confiée à la fonction direction_clone() . Voici tout d'abord la fonction pour faire avancer un clone :

 
Sélectionnez
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: break;
        y -= j->pas;
    case DOWN:
        y += j->pas;
        break;
    }
    if (cntl_avance_personnage(x, y, j)){
        j->x = x;
        j->y = y;
    }
    else
        direction_clone(j);
}

Cette fonction pour l'avance des clones est appelée à chaque événement du minuteur de la façon suivante :

 
Sélectionnez
for (i = 0; i < JOUEUR; i++) // joueur non compris
    avance_clone(allP[i]);

Pour le choix d'une nouvelle direction, le clone tire d'abord une direction au hasard, ensuite il vérifie s'il peut prendre ou non cette direction. S'il le peut, il la garde et la fonction se termine. S'il ne peut pas, il cherche systématiquement les directions voisines jusqu'à en trouver une bonne. Il trouvera nécessairement. La recherche de direction se fait soit dans le sens des aiguilles d'une montre soit dans le sens inverse. C'est décidé au hasard au début de la fonction. Voici la fonction complète :

 
Sélectionnez
void direction_clone(t_personnage*j)
{
    int sens = (rand() % 2) * 2 - 1; // -1 ou 1
    int dir = rand() % NBDIR; // entre 0 et 3 compris
    int res = -1;
    // passer en coordonnées map
    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 && MAP[y][x - 1] == 3)
                res = LEFT;
            break;
        case RIGHT:
            if (x + 1<MAPTX && MAP[y][x + 1] == 3)
                res = RIGHT;
            break;
        case UP:
            if (y - 1 >= 0 && MAP[y - 1][x] == 3)
                res = UP;
            break;
        case DOWN:
            if (y + 1<MAPTY && MAP[y + 1][x] == 3)
                res = DOWN;
            break;
        }
    } while (res<0 );
    j->dir = res;
}

Le terrain garantit qu'il n'y a pas de case d'herbe isolée au milieu d'autres cases interdites. Un clone ne peut pas se trouver dans l'impossibilité d'obtenir une direction et il n'y a pas de raison pour que la boucle puisse être infinie.

XII-C-3. Le mouvement du joueur

Pour le joueur il n'y a aucun changement de principe. La fonction de contrôle du joueur par le clavier ne change pas. C'est la fonction :

 
Sélectionnez
void controle_joueur(ALLEGRO_EVENT*e, t_personnage*j)

Cette fonction est appelée comme dans le programme précédent juste après la capture d'événements.

Il y a une légère variation dans l'écriture de la fonction qui fait avancer le joueur. Le test de viabilité de la direction prise est remplacé par un appel à la fonction présentée plus haut :

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

Ce qui donne la fonction d'avance du joueur suivante :

 
Sélectionnez
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
    if (cntl_avance_personnage(x, y, j)){
        //avancer
        j->x = x;
        j->y = y;
    }
}

Cette fonction est appelée à chaque événement du minuteur comme dans le programme précédent.

XII-C-3-a. Code complet commenté

De nombreux personnages autonomes dans un décor
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

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

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

// taille en pixels de la map
#define PIXMAPX MAPTX*PIXTILEX
#define PIXMAPY MAPTY*PIXTILEY

#define BLANC al_map_rgb(255,255,255)
#define GRIS al_map_rgb(128,128,128)

// la carte des tuiles du terrain
const short MAP[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];

ALLEGRO_BITMAP*    construct_decor            (void);
void            recup_anim_personnage    (void);
t_personnage*    construct_personnage    (void);
int                cntl_avance_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
                                MAP[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;
}
/*****************************************************************
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 map)
    j->x = rand() % MAPTX;
    j->y = rand() % MAPTY;
    // seules les position sur 3 sont acceptables, si la position 
    // obtenue n'est pas 3 en chercher une à partir de celle où
    // on est
    while (MAP[j->y][j->x] != 3){
        if (rand() % 2)
            j->x = (++j->x) % MAPTX;
        else
            j->y = (++j->y) % MAPTY;
    }
    // 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 cntl_avance_personnage(int x, int y, t_personnage*j)
{
    int cx1, cx2, cy1, cy2;
    int res = 0;

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

        // les 2 coins (top,left) et (right,botom)
        cx1 = x / PIXTILEX;
        cy1 = y / PIXTILEX;
        cx2 = (x + j->tx - 1) / PIXTILEX;
        cy2 = (y + j->ty - 1) / PIXTILEY;

        // et si les 4 coins du sprite sont tous sur du 3 (herbe) 
        if (MAP[cy1][cx1] == 3 && MAP[cy1][cx2] == 3 &&
            MAP[cy2][cx1] == 3 && MAP[cy2][cx2] == 3){

            //avancer
            res = 1;
        }
    }
    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 (cntl_avance_personnage(x, y, j)){
        j->x = x;
        j->y = y;
    }
    else
        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 3 compris
    int res = -1;

    // passer en coordonnées map
    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 && MAP[y][x - 1] == 3)
                    res = LEFT;
                break;
            case RIGHT:
                if (x + 1<MAPTX && MAP[y][x + 1] == 3)
                    res = RIGHT;
                break;
            case UP:
                if (y - 1 >= 0 && MAP[y - 1][x] == 3)
                    res = UP;
                break;
            case DOWN:
                if (y + 1<MAPTY && MAP[y + 1][x] == 3)
                    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
    if (cntl_avance_personnage(x, y, j)){
        //avancer
        j->x = x;
        j->y = y;
    }
}
/*****************************************************************
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);
}
/*****************************************************************
*****************************************************************/
XII-C-3-a-i. 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é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.