X. Animations, sprites▲
X-A. Introduction▲
« Sprite » possède plusieurs traductions en français, dont lutins et farfadets. En programmation de jeux, c'est une petite image représentant un personnage, un élément fixe ou mobile dans le jeu. Souvent il désigne une petite séquence d'animation composée d'un ensemble de petites images formant un mini dessin animé, un personnage qui se déplace en marchant, un personnage qui tire au revolver, une figure de combat en arts martiaux, un monstre qui vole, une explosion, etc. Dans la suite de cet ouvrage, nous conserverons le terme de sprite sans traduction en français.
X-B. Un personnage marchant sur place▲
Pour tester les sprites, nous allons reprendre un ancien exemple de la bibliothèque Allegro : un personnage qui marche sur place. En voici les images :
Comme nous avons besoin d'images nous devons inclure le fichier d'en-tête du module spécialisé pour les images :
#include <allegro5/allegro_image.h>
et sans oublier de l'initialiser dans le main() :
if
(!
al_init_image_addon
(
))
erreur
(
"
al_init_image_addon()
"
);
Ensuite le premier point est de récupérer les images de l'animation. Elles sont stockées dans un tableau de pointeurs bitmap assorti d'une constante qui conserve le nombre des bitmaps :
const
int
nbimages=
10
;
ALLEGRO_BITMAP*
anim[nbimages];
Chaque image de l'animation est sur un fichier .bmp à part et l'ensemble est dans un dossier nommé « personnage ». La récupération des images est simplifiée si les images portent toutes le même nom avec juste un numéro pour les différencier. Nous avons nommé nos images pers0.bmp, pers1.bmp, pers2.bmp, etc. jusqu'à pers9.bmp. De la sorte, toutes les images de l'animation peuvent être récupérées dans une boucle. L'indice donne le numéro de l'image et le nom complet est reconstitué avec la fonction standard sprintf() . Rappelons que sprintf() fonctionne comme un printf() mais, à la différence de printf() , elle sort une chaîne de caractères dans un tableau de char et non dans la fenêtre console.
Notons également que sprintf() est dans stdio.h et qu'il faut inclure cette en-tête pour pouvoir l'utiliser.
Ainsi la récupération des images dans le programme s'effectue de la façon suivante :
char
nom[256
] ;
for
(
i=
0
; i<
nbimages; i++
){
sprintf
(
nom,"
personnage/pers%d.bmp
"
,i);
anim[i]=
al_load_bitmap
(
nom);
if
(!
anim[i])
erreur
(
"
al_load_bitmap()
"
);
}
À chaque tour de boucle, le chemin d'accès de l'image dont le numéro correspond à l'indice est stocké dans le tableau de char nom. La chaîne ainsi obtenue est utilisée ensuite pour récupérer l'image avec la fonction al_load_bitmap().
Dans notre programme d'expérimentation, la position de l'image est fixe. Mais dans la plupart des cas, elle sera amenée à varier avec les déplacements du personnage. Dans cette perspective future, la position de l'image est stockée avec deux variables posx et posy.
Le principe de l'animation est celui d'un petit dessin animé. Les images qui décomposent le mouvement doivent se succéder les unes après les autres dans l'ordre où elles sont rangées dans le tableau. C'est le rôle d'une variable nommée cmptimage . Au départ, cmptimage est à 0 et l'image 0 est affichée. Puis à chaque tour, cmptimage est incrémentée de un et l'image correspondante dans le tableau est affichée. Lorsque cmptimage dépasse le nombre d'images contenues dans le tableau, la variable cmptimage est remise à 0.
Nous avons utilisé les valeurs de permutation ALLEGRO_FLIP_HORIZONTAL et ALLEGRO_FLIP_VERTICAL afin que la fonction al_draw_bitmap() exécute des bascules (flip) horizontale, verticale et même les deux à la fois. Cette valeur est stockée dans la variable flag et cette valeur change si l'on tape sur la touche [Entrée]. L'image reste toujours centrée au milieu de l'écran.
Voici les initialisations de départ :
posx=(
scrx-
al_get_bitmap_width
(
anim[0
]))/
2
;
posy=(
scry-
al_get_bitmap_height
(
anim[0
]))/
2
;
// initialisation du compteur d'images à 0
cmptimage=
0
;
flag=
0
;
La succession des images est dirigée par le minuteur (timer). À chaque événement timer, nous affichons l'image courante de l'animation et incrémentons le compteur d'images :
al_draw_bitmap
(
anim[cmptimage],posx,posy,flag);
cmptimage=(
cmptimage+
1
)%
nbimages;
al_flip_display
(
);
X-B-1. Expérimentation▲
Voici le code complet :
// pour Visual C++ uniquement afin de lever l'interdiction
// d'user de la fonction sprintf jugée "unsafe".
#define _CRT_SECURE_NO_WARNINGS
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <allegro5/allegro_image.h>
#include <stdio.h>
void
erreur
(
const
char
*
txt)
{
ALLEGRO_DISPLAY*
d;
d =
al_is_system_installed
(
) ? al_get_current_display
(
) : NULL
;
al_show_native_message_box
(
d, "
ERREUR
"
, txt, NULL
, NULL
, 0
);
exit
(
EXIT_FAILURE);
}
/**
***************************************************************
****************************************************************
*/
int
main
(
)
{
ALLEGRO_DISPLAY*
display;
ALLEGRO_EVENT_QUEUE*
queue;
ALLEGRO_TIMER*
timer;
int
scrx =
800
;
int
scry =
600
;
bool fin =
0
;
// stockage de l'animation
const
int
nbimages =
10
;
ALLEGRO_BITMAP*
anim[nbimages];
// position, compteur
int
posx, posy, cmptimage, flag, i;
char
nom[256
];
if
(!
al_init
(
))
erreur
(
"
al_init()
"
);
if
(!
al_install_keyboard
(
))
erreur
(
"
al_install_keyboard()
"
);
if
(!
al_init_image_addon
(
))
erreur
(
"
al_init_image_addon()
"
);
display =
al_create_display
(
scrx, scry);
if
(!
display)
erreur
(
"
al_create_display()
"
);
queue =
al_create_event_queue
(
);
if
(!
queue)
erreur
(
"
al_create_event_queue()
"
);
timer =
al_create_timer
(
1
.0
/
30
);
if
(!
timer)
erreur
(
"
al_create_timer()
"
);
// enregistrement événements
al_register_event_source
(
queue,
al_get_display_event_source
(
display));
al_register_event_source
(
queue,
al_get_keyboard_event_source
(
));
al_register_event_source
(
queue,
al_get_timer_event_source
(
timer));
// charger l'animation
for
(
i =
0
; i<
nbimages; i++
){
sprintf
(
nom, "
personnage/pers%d.bmp
"
, i);
anim[i] =
al_load_bitmap
(
nom);
if
(!
anim[i])
erreur
(
"
al_load_bitmap()
"
);
}
// initialisation du personnage au centre
posx =
(
scrx -
al_get_bitmap_width
(
anim[0
])) /
2
;
posy =
(
scry -
al_get_bitmap_height
(
anim[0
])) /
2
;
// initialisation du compteur d'images à 0
cmptimage =
0
;
flag =
0
;
al_start_timer
(
timer);
while
(!
fin){
ALLEGRO_EVENT event;
al_wait_for_event
(
queue, &
event);
if
(
event.type ==
ALLEGRO_EVENT_DISPLAY_CLOSE)
fin =
true
;
else
if
(
event.type ==
ALLEGRO_EVENT_KEY_DOWN){
switch
(
event.keyboard.keycode){
// changer le sens de l'animation
case
ALLEGRO_KEY_ENTER:
if
(
flag ==
0
)
flag =
ALLEGRO_FLIP_HORIZONTAL;
else
if
(
flag ==
ALLEGRO_FLIP_HORIZONTAL)
flag =
ALLEGRO_FLIP_VERTICAL;
else
if
(
flag ==
ALLEGRO_FLIP_VERTICAL)
flag |=
ALLEGRO_FLIP_HORIZONTAL;
else
flag =
0
;
break
;
case
ALLEGRO_KEY_ESCAPE:
fin =
true
;
break
;
}
}
else
if
(
event.type ==
ALLEGRO_EVENT_TIMER){
// mouvement
al_draw_bitmap
(
anim[cmptimage], posx, posy, flag);
cmptimage =
(
cmptimage +
1
) %
nbimages;
al_flip_display
(
);
}
}
for
(
i =
0
; i<
nbimages; i++
)
al_destroy_bitmap
(
anim[i]);
al_destroy_display
(
display);
al_destroy_timer
(
timer);
al_destroy_event_queue
(
queue);
return
0
;
}
X-C. Un personnage piloté par le clavier▲
Par rapport au programme précédent, nous souhaitons faire avancer le personnage en le dirigeant avec les touches du clavier. Selon la direction choisie, l'animation n'est pas la même. Il y a quatre directions nord, est, sud et ouest ayant respectivement pour valeur 0, 1, 2, 3. Et pour expérimenter, nous avons pris une séquence d'animation minimaliste de deux images par direction, soit huit images au total.
Elles sont récupérées et stockées dans un tableau rangées dans cet ordre :
nord : indices 0 et 1
est : indices 2 et 3
sud : indices 4 et 5,
ouest : indices 6 et 7.
Voici le tableau de huit pointeurs bitmap :
ALLEGRO_BITMAP*
anim[8
];
Le chargement des images se fait comme dans le programme précédent et il faut être attentif à l'ordre dans lequel les images sont rangées dans le tableau. Comme précédemment, nous avons numéroté soigneusement les fichiers en fonction des animations et par directions. Ils se nomment z0.bmp, z1.bmp, z2.bmp … z7.bmp. Ils sont tous regroupés dans un dossier nommé « zelda » et nous utilisons la fonction sprintf() pour reconstituer le chemin d'accès de chaque fichier. Voici la boucle de récupération des images :
for
(
i=
0
;i<
8
;i++
){
sprintf
(
nom,"
zelda/z%d.bmp
"
,i);
anim[i]=
al_load_bitmap
(
nom);
if
(!
anim[i])
erreur
(
"
al_load_bitmap()
"
);
}
Le personnage a une position, une direction, un compteur d'image et l'indice de l'image courante, ce sont les variables :
int
posx, posy; // position
int
dir, cmptimage, image;// direction, compte image, indice
- dir est une valeur entre 0 et 3 (Nord : 0, Est : 1, Sud : 2, Ouest : 3). La direction est mise à jour selon la touche flèche appuyée dans la boucle d'événements.
- cmptimage compte les images de l'animation dans une direction, il y a deux images par direction alors cmptimage vaut 0 ou 1. La variable cmptimage est mise à jour à chaque événement du minuteur (timer) de la façon suivante :
cmptimage =
1
-
cmptimage ;
// Si cmptimage vaut 1 ça fait 0 et si cmptimage vaut 0 ça fait 1.
- image vaut :
(
dir*
2
) +
cmptimage
dir*2 désigne la série pour une direction. En effet il y a deux images par direction alors :
si dir vaut 0 ce sont les images 0, 1
si dir vaut 1 ce sont les images 2, 3
si dir vaut 2 les images 4 et 5
et si dir vaut 3 les images 6 et 7.
À quoi s'ajoute cmptimage qui vaut 0 pour la première image et 1 pour la seconde. La variable image est utilisée au moment de l'affichage lors de l'appel de la fonction al_draw_bitmap() .
Ces variables direction, compteur et identificateur d'image sont initialisées à 0 au départ et le personnage centré. Soit l'initialisation :
posx=(
scrx-
al_get_bitmap_width
(
anim[0
]))/
2
;
posy=(
scry-
al_get_bitmap_height
(
anim[0
]))/
2
;
dir=
cmptimage=
image=
0
;
Pour ce qui est de la gestion du clavier, nous avons repris le clavier fluide vu dans la section Donner de la fluidité aux mouvements du rectangle du chapitre Les événementsÉvénements. L'état des touches flèche, appuyé ou levé, est stocké dans un tableau de booléens qui répond aux événements KEY_DOWN et KEY_UP . Chaque flèche correspond à un indice du tableau et ces indices sont désignés par des constantes nommées dans un enum :
enum
{
UP,RIGHT,DOWN,LEFT,KEYMAX}
;
bool key[KEYMAX]={
false
}
;
Voici le programme complet :
// pour Visual C++ uniquement afin de lever l'interdiction
// d'user de la fonction sprintf jugée "unsafe".
#define _CRT_SECURE_NO_WARNINGS
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <allegro5/allegro_image.h>
#include <stdio.h>
void
erreur
(
const
char
*
txt)
{
ALLEGRO_DISPLAY*
d;
d =
al_is_system_installed
(
) ? al_get_current_display
(
) : NULL
;
al_show_native_message_box
(
d, "
ERREUR
"
, txt, NULL
, NULL
, 0
);
exit
(
EXIT_FAILURE);
}
/**
***************************************************************
****************************************************************
*/
int
main
(
)
{
ALLEGRO_DISPLAY*
display;
ALLEGRO_EVENT_QUEUE*
queue;
ALLEGRO_TIMER*
timer;
int
scrx =
800
;
int
scry =
600
;
bool fin =
0
;
bool dessine =
true
;
// clavier fluide
enum
{
UP, RIGHT, DOWN, LEFT, KEYMAX }
;
bool key[KEYMAX] =
{
false
}
;
// images et sélection des images. Les images sont rangées
// dans l'ordre : UP,RIGTH,DOWN,LEFT avec deux images par
// direction.
ALLEGRO_BITMAP*
anim[8
];
int
dir, cmptimage, image;
int
posx, posy, i;
char
nom[256
];
if
(!
al_init
(
))
erreur
(
"
al_init()
"
);
if
(!
al_install_keyboard
(
))
erreur
(
"
al_install_keyboard()
"
);
display =
al_create_display
(
scrx, scry);
if
(!
display)
erreur
(
"
al_create_display()
"
);
// fond en rouge
// al_clear_to_color(al_map_rgb(255,0,0)) ;
if
(!
al_init_image_addon
(
))
erreur
(
"
al_init_image_addon()
"
);
queue =
al_create_event_queue
(
);
if
(!
queue)
erreur
(
"
al_create_event_queue()
"
);
timer =
al_create_timer
(
1
.0
/
25
);
if
(!
timer)
erreur
(
"
al_create_timer()
"
);
// enregistrement événements
al_register_event_source
(
queue,
al_get_display_event_source
(
display));
al_register_event_source
(
queue,
al_get_keyboard_event_source
(
));
al_register_event_source
(
queue,
al_get_timer_event_source
(
timer));
// initialisation personnage. Attention les images doivent
// correspondre aux directions dans l'ordre
for
(
i =
0
; i<
8
; i++
){
sprintf
(
nom, "
zelda/z%d.bmp
"
, i);
anim[i] =
al_load_bitmap
(
nom);
if
(!
anim[i])
erreur
(
"
al_load_bitmap()
"
);
}
// centré au départ
posx =
(
scrx -
al_get_bitmap_width
(
anim[0
])) /
2
;
posy =
(
scry -
al_get_bitmap_height
(
anim[0
])) /
2
;
dir =
cmptimage =
image =
0
;
al_start_timer
(
timer);
while
(!
fin){
ALLEGRO_EVENT event;
al_wait_for_event
(
queue, &
event);
if
(
event.type ==
ALLEGRO_EVENT_DISPLAY_CLOSE)
fin =
true
;
else
if
(
event.type ==
ALLEGRO_EVENT_KEY_DOWN){
switch
(
event.keyboard.keycode){
case
ALLEGRO_KEY_UP:
key[UP] =
true
;
dir =
0
;
break
;
case
ALLEGRO_KEY_RIGHT:
key[RIGHT] =
true
;
dir =
1
;
break
;
case
ALLEGRO_KEY_DOWN:
key[DOWN] =
true
;
dir =
2
;
break
;
case
ALLEGRO_KEY_LEFT:
key[LEFT] =
true
;
dir =
3
;
break
;
case
ALLEGRO_KEY_ESCAPE:
fin =
true
;
break
;
}
// sélection de l'image selon direction et sur
// événement key_up. 2 images par direction
// rangées aux indices 01-23-45-67
cmptimage =
1
-
cmptimage; // 0 ou 1
image =
(
dir *
2
) +
cmptimage;
}
else
if
(
event.type ==
ALLEGRO_EVENT_KEY_UP){
switch
(
event.keyboard.keycode){
case
ALLEGRO_KEY_UP: key[UP] =
false
; break
;
case
ALLEGRO_KEY_RIGHT: key[RIGHT] =
false
; break
;
case
ALLEGRO_KEY_DOWN: key[DOWN] =
false
; break
;
case
ALLEGRO_KEY_LEFT: key[LEFT] =
false
; break
;
case
ALLEGRO_KEY_ESCAPE: fin =
true
; break
;
}
}
else
if
(
event.type ==
ALLEGRO_EVENT_TIMER){
// avancer
posy -=
10
*
key[UP];
posx +=
10
*
key[RIGHT];
posy +=
10
*
key[DOWN];
posx -=
10
*
key[LEFT];
// redessiner
dessine =
true
;
}
if
(
dessine ==
true
&&
al_is_event_queue_empty
(
queue)){
al_clear_to_color
(
al_map_rgb
(
0
, 0
, 0
));
// al_clear_to_color(al_map_rgb(255,0,0));
al_draw_bitmap
(
anim[image], posx, posy, 0
);
al_flip_display
(
);
dessine =
false
;
}
}
for
(
i =
0
; i<
8
; i++
)
al_destroy_bitmap
(
anim[i]);
al_destroy_display
(
display);
al_destroy_timer
(
timer);
al_destroy_event_queue
(
queue);
return
0
;
}
Pendant le fonctionnement du programme nous pouvons constater deux problèmes :
- Si vous mettez la couleur de fond en rouge (en commentaire par défaut dans le code) avec al_clear_to_color() , vous constatez que le personnage se déplace dans un carré noir. Il faut faire en sorte que ce noir devienne transparent.
- Par ailleurs, l'animation est presque invisible. Certes, le personnage est petit mais ce n'est pas seulement à cause de sa petite taille, c'est à cause de la rapidité de l'animation. L'animation est trop rapide et la solution n'est pas de ralentir le minuteur, car cela ralentirait aussi tous les autres affichages du jeu.
X-D. Gestion de la couleur du masque▲
Dans une image, il est possible de masquer à l'affichage tous les pixels d'une couleur préalablement sélectionnée. Cette couleur est appelée la couleur de masque. Elle a pour effet d'être transparente (les pixels de cette couleur ne sont pas affichés). La sélection de cette couleur se fait avec un appel à :
void
al_convert_mask_to_alpha
(
ALLEGRO_BITMAP *
bmp, ALLEGRO_COLOR mask_color)
Pour la bitmap passée au premier paramètre bmp, la couleur de masque devient la couleur mask_color passée au second paramètre. La couleur de masque est propre à la bitmap et chaque bitmap dans un programme peut avoir une couleur de masque différente.
Dans le programme ci-dessus pour faire disparaître le noir autour du personnage, il suffit de déclarer le noir comme couleur de masque pour toutes les images de l'animation. Par mesure de prudence, j'ai pris le pixel du coin haut gauche sachant que c'est la bonne couleur. Il convient d'être vigilant dans son traitement d'image parce qu'une couleur avec les valeurs (1, 0, 0) est différente d'une couleur avec les valeurs (0, 0, 0), bien que cela soit invisible à l'œ il. Si le fond de l'image n'est pas absolument uni, utiliser un logiciel de retouche comme Photoshop, The Gimp, Paint etc. pour avoir un fond parfaitement uni.
L'appel de la fonction al_convert_mask_to_alpha() est fait au moment du load :
// (...)
// initialisation personnage. Attention les images doivent
// correspondre aux directions dans l'ordre
for
(
i =
0
; i<
8
; i++
){
sprintf
(
nom, "
zelda/z%d.bmp
"
, i);
anim[i] =
al_load_bitmap
(
nom);
if
(!
anim[i])
erreur
(
"
al_load_bitmap()
"
);
al_convert_mask_to_alpha
(
anim[i], al_get_pixel
(
anim[i], 0
, 0
));
}
// (...)
Avec cette modification apportée au programme, le personnage se déplace sur le fond rouge sans le carré noir.
La couleur de masque d'une image peut être changée pendant le fonctionnement du programme. Il n'est pas nécessaire qu'elle reste constante. Ainsi différentes zones de l'image peuvent devenir transparentes tour à tour.
X-E. Contrôle de la vitesse de l'animation▲
Le deuxième point est celui de la vitesse de l'animation. Notre personnage est trop rapide non dans les distances parcourues mais dans la succession des mouvements produits par l'animation. En effet, à chaque affichage, l'animation passe à l'image suivante. Pour ralentir l'animation, il faut que chaque image de l'animation reste plusieurs tours avant de passer à la suivante. Pour ce faire, nous allons ajouter un compteur de tours et l'incrémentation de l'identificateur d'image courante se fera tous les N tours. Dans la boucle principale nous aurons une séquence du type :
if
(
compte++
>
nbTours){
compte=
0
;
image=(
image+
1
)%
NBIMAGE ;
}
Avec compte pour compter les tours, nbTours pour le nombre de tours où la même image de l'animation est affichée, image pour identifier l'image courante dans l'animation et NBIMAGE qui donne le nombre total des images de l'animation. On voit bien que image augmente de un uniquement lorsque compte atteint nbTours . compte est alors remis à 0 et ça recommence.
X-E-1. Expérimentation ▲
Le programme pour illustrer cette question est un chat qui traverse en boucle l'écran de la gauche vers la droite. Une fois sorti à droite, il réapparaît sur la gauche. En voici les images :
La couleur magenta légèrement violette qui entoure le personnage s'explique parce que dans la version 4 d'Allegro, il y avait une seule couleur de masque et c'était cette couleur obtenue avec le mélange 255 de rouge, 0 vert et 255 de bleu. Les sprites utilisés ici sont les mêmes que ceux utilisés dans un livre paru en anglais sur la version 4 d'Allegro (Jonathan Harbour, Game programming All in One, Thomson course technology, 2004).
La récupération des images de l'animation se fait de façon identique aux programmes précédents sur l'animation. Ensuite les variables nécessaires sont initialisées. L'identificateur d'image image , le compteur compte et le nombre de tours nbTours sont mis à 0 :
image=
compte=
nbTours=
0
;
La position posx, posy du personnage est au départ à mi-hauteur sur la marge gauche :
posx=
0
;
posy=(
scry -
al_get_bitmap_height
(
anim[0
])) /
2
;
Du point de vue de l'action, la vitesse du déplacement est toujours la même ; il n'y a que la vitesse de l'animation du mouvement qui change. Elle est contrôlée avec [Flèche en haut] et [Flèche en bas]. La touche [Flèche en haut] augmente le nombre de tours nécessaire pour le changement d'image et ainsi ralentit l'animation. Les mouvements sont de plus en plus lents. La touche [Flèche en bas] a l'effet inverse. Elle diminue le nombre de tours nécessaires au changement d'image avec pour conséquence d'accélérer le mouvement.
Le déplacement entièrement automatique est effectué à chaque événement du minuteur (timer). De même pour l'animation. Le compte du nombre de tours effectués est augmenté de 1 à chaque événement du minuteur. Lorsqu'il atteint le nombre déterminé par la variable nbTours , l'identificateur d'image image est incrémenté de 1 et le compte du nombre de tours repart à 0. L'affichage a lieu juste après. Il prend dans le tableau des images de l'animation l'image correspondant à l'identificateur d'image image .
Voici le programme complet :
// pour Visual C++ uniquement afin de lever l'interdiction
// d'user de la fonction sprintf jugée "unsafe".
#define _CRT_SECURE_NO_WARNINGS
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <allegro5/allegro_image.h>
#include <stdio.h>
#include <time.h>
void
erreur
(
const
char
*
txt)
{
ALLEGRO_DISPLAY*
d;
d =
al_is_system_installed
(
) ? al_get_current_display
(
) : NULL
;
al_show_native_message_box
(
d, "
ERREUR
"
, txt, NULL
, NULL
, 0
);
exit
(
EXIT_FAILURE);
}
/**
***************************************************************
****************************************************************
*/
int
main
(
)
{
ALLEGRO_DISPLAY*
display;
ALLEGRO_EVENT_QUEUE*
queue;
ALLEGRO_TIMER*
timer;
int
scrx =
800
;
int
scry =
600
;
bool fin =
0
;
const
int
NBIMAGE =
6
;
ALLEGRO_BITMAP*
anim[NBIMAGE];
int
compte, nbTours, image, i;
char
nom[256
];
int
posx, posy; // position de l'animation
srand
(
time
(
NULL
));
if
(!
al_init
(
))
erreur
(
"
al_init()
"
);
if
(!
al_init_image_addon
(
))
erreur
(
"
al_init_image_addon()
"
);
if
(!
al_install_keyboard
(
))
erreur
(
"
al_install_keyboard()
"
);
display =
al_create_display
(
scrx, scry);
if
(!
display)
erreur
(
"
al_create_display()
"
);
queue =
al_create_event_queue
(
);
if
(!
queue)
erreur
(
"
al_create_event_queue()
"
);
timer =
al_create_timer
(
1
.0
/
30
);
if
(!
timer)
erreur
(
"
al_create_timer()
"
);
// enregistrement événements
al_register_event_source
(
queue,
al_get_display_event_source
(
display));
al_register_event_source
(
queue,
al_get_keyboard_event_source
(
));
al_register_event_source
(
queue,
al_get_timer_event_source
(
timer));
// récupération des images
for
(
i =
0
; i<
NBIMAGE; i++
){
sprintf
(
nom, "
cat/cat%d.bmp
"
, i);
anim[i] =
al_load_bitmap
(
nom);
if
(!
anim[i])
erreur
(
"
al_load_bitmap()
"
);
al_convert_mask_to_alpha
(
anim[i], al_get_pixel
(
anim[i], 0
, 0
));
}
// initialisations
image =
compte =
0
;
nbTours =
0
;
posx =
0
;
posy =
(
scry -
al_get_bitmap_height
(
anim[0
])) /
2
;
// démarrer le timer
al_start_timer
(
timer);
while
(!
fin){
ALLEGRO_EVENT event;
al_wait_for_event
(
queue, &
event);
if
(
event.type ==
ALLEGRO_EVENT_DISPLAY_CLOSE)
fin =
true
;
else
if
(
event.type ==
ALLEGRO_EVENT_KEY_DOWN){
switch
(
event.keyboard.keycode){
// change le défilement de l'animation
case
ALLEGRO_KEY_UP:
nbTours =
(
nbTours<
40
) ? nbTours +
1
: 40
;
break
;
case
ALLEGRO_KEY_DOWN:
nbTours =
(
nbTours>
0
) ? nbTours -
1
: 0
;
break
;
case
ALLEGRO_KEY_ESCAPE:
fin =
true
;
break
;
}
printf
(
"
nbTours : %d
\n
"
, nbTours);
}
else
if
(
event.type ==
ALLEGRO_EVENT_TIMER){
// mouvement
posx +=
5
;
if
(
posx>
scrx)
posx =
-
al_get_bitmap_height
(
anim[0
]);
// animation
if
(
compte++
>
nbTours){
compte =
0
;
image =
(
image +
1
) %
NBIMAGE;
}
//affichage
al_clear_to_color
(
al_map_rgb
(
0
, 0
, 0
));
al_draw_bitmap
(
anim[image], posx, posy, 0
);
al_flip_display
(
);
}
}
for
(
i =
0
; i<
NBIMAGE; i++
)
al_destroy_bitmap
(
anim[i]);
al_destroy_display
(
display);
al_destroy_timer
(
timer);
al_destroy_event_queue
(
queue);
return
0
;
}
Appuyer sur [Flèche en haut] augmente nbTours et donc ralentit l'animation. Au contraire, appuyer sur [Flèche en bas] diminue nbTours et accélère l'animation. Le déplacement en revanche est constant indépendamment de la vitesse de l'animation. Le nombre de tours nbTours est affiché dans la fenêtre console.
Bien entendu la vitesse attribuée au minuteur (timer) joue elle aussi un rôle. Nous avons pris 1/30 de seconde, ce qui est n'est pas très rapide.
X-F. Récupération d'une série d'images sur un fichier unique▲
Il peut être pratique d'avoir toutes les images de l'animation sur un seul fichier, voire de regrouper toutes les animations du programme sur un même fichier. Ce fichier sera une grande image bitmap, jpeg ou png contenant toutes les séquences d'images des animations. Chaque animation doit y être soigneusement rangée en lignes et colonnes. Voici par exemple nos trois séquences précédentes regroupées sur un seul fichier image :
Pour récupérer une image dans une des trois suites d'images ci-dessus, il faut connaître :
- La position dans la grande bitmap conteneur du coin haut-gauche de la première image de la série.
- La taille horizontale et verticale des images de l'animation.
- Le nombre de colonnes du rangement des images.
- Et le numéro de l'image à récupérer (on considère qu'elles sont numérotées de 0 à NBIMAGE-1 (0 à 9 pour le personnage, 0 à 5 pour le chat, 0 à 7 pour Zelda).
- Éventuellement, si vos images sont séparées par un espace, cet espace doit être régulier et il devra ensuite être comptabilisé dans l'algorithme : la taille d'une image ne change pas mais le saut d'une image à l'autre en est allongé. Pour la suite nous avons omis ce détail et considéré que les images sont juxtaposées sans espace pour les séparer.
Avec ces informations la fonction recup_sprite() retourne une copie de l'image souhaitée.
ALLEGRO_BITMAP*
recup_sprite
(
ALLEGRO_BITMAP*
source,int
tx,int
ty, int
startx,int
starty,int
colonne,int
i)
Cette fonction retourne une région de la bitmap source de taille tx sur ty à partir de la position startx , starty et selon le nombre de colonnes colonne . Cette région correspond à la ième image d'une des animations.
Exemple d'utilisation, récupération de la suite du personnage. La bitmap all correspond à la grande bitmap conteneur. Elle est sauvée sur le disque avec le nom « all_sprites.bmp ». Il faut commencer par la charger :
ALLEGRO_BITMAP*
all;
all=
al_load_bitmap
(
"
all_sprites.bmp
"
);
Ensuite la série d'images à récupérer est stockée dans un tableau de bitmap :
const
int
NBIMAGE=
10
;
ALLEGRO_BITMAP*
anim[NBIMAGE];
La récupération de chaque image suppose une boucle sur le nombre d'images de la suite. Chaque image a une taille de 32x32 pixels. La première sur notre fichier commence à 0 à l'horizontal et à 100 à la verticale. Il y a 5 colonnes et i prend successivement le numéro de chaque image :
for
(
i=
0
;i<
NBIMAGE;i++
){
anim[i]=
recup_sprite
(
all,32
,32
,0
,100
,5
,i);
if
(!
anim[i])
erreur
(
"
recup_sprite()
"
);
al_convert_mask_to_alpha
(
anim[i],al_get_pixel
(
anim[i],0
,0
));
}
Voici le code de la fonction de récupération :
ALLEGRO_BITMAP*
recup_sprite
(
ALLEGRO_BITMAP*
scr, // bitmap conteneur
int
tx,int
ty, // taille élément
int
startx,int
starty, // coin haut gauche de départ
int
colonne, // nombre de colonnes
int
i) // ième élément
{
ALLEGRO_BITMAP*
sprite=
NULL
;
int
x,y;
sprite=
al_create_bitmap
(
tx,ty);
if
(
sprite!=
NULL
){
// attention colonne doit toujours être > 0
x=
startx+(
i%
colonne)*
tx;
y=
starty+(
i/
colonne)*
ty;
al_set_target_bitmap
(
sprite);
al_draw_bitmap_region
(
scr,x,y,tx,ty,0
,0
,0
);
}
return
sprite;
}
À partir du coin haut gauche de départ, de la taille de chaque image, du nombre de colonnes et du numéro d'ordre de l'image, la fonction retourne une copie de la région concernée. Attention, la bitmap cible pour les opérations de dessin ou d'affichage est la bitmap retournée. Il ne faut pas oublier ensuite un appel à :
al_set_target_backbuffer
(
display);
pour revenir à des opérations de dessin et d'affichage dans la fenêtre ou écran courant.
X-F-1. Expérimentation : une balle rebondit lorsqu'elle touche un bord ▲
Dans ce programme, toutes les images de l'animation, une balle qui tourne sur elle-même, sont stockées sur un même fichier bitmap nommé all_sprites.bmp.
Voici les images :
Une structure t_sprite est créée qui regroupe toutes les caractéristiques d'un sprite, position, déplacement et animation. L'animation peut aller en avant (images vers la droite dans tableau) ou en arrière (images vers la gauche dans le tableau). Lorsque la balle heurte un bord, elle rebondit avec des variations pour sa vitesse de déplacement, le sens et la vitesse de l'animation.
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <allegro5/allegro_image.h>
#include <stdio.h>
#include <time.h>
const
int
SCRX =
800
;
const
int
SCRY =
600
;
const
int
IMTX =
64
;
const
int
IMTY =
64
;
const
int
NBIMAGE =
32
;
typedef
struct
{
// déplacement
float
x, y; // position
float
dx, dy; // déplacement
// image
int
tx, ty; // taille
// animation
int
imcourante; // image courante
int
nbimage; // nombre d'images
int
tour; // compte tours
int
nbtour; // nombre de tours
int
dir; // direction de l'animation
}
t_sprite;
ALLEGRO_BITMAP*
recup_sprite
(
ALLEGRO_BITMAP*
scr,
int
tx, int
ty,
int
startx, int
starty,
int
colonne, int
i);
t_sprite*
init_sprite
(
void
);
void
cntl_anim
(
t_sprite*
s);
void
avance
(
t_sprite*
s);
void
erreur
(
const
char
*
txt);
/**
***************************************************************
****************************************************************
*/
int
main
(
)
{
ALLEGRO_DISPLAY*
display;
ALLEGRO_KEYBOARD_STATE key;
ALLEGRO_BITMAP*
all;
ALLEGRO_BITMAP*
anim[NBIMAGE];
int
i;
t_sprite*
balle;
srand
(
time
(
NULL
));
if
(!
al_init
(
))
erreur
(
"
al_init()
"
);
if
(!
al_init_image_addon
(
))
erreur
(
"
al_init_image_addon()
"
);
if
(!
al_install_keyboard
(
))
erreur
(
"
al_install_keyboard()
"
);
display =
al_create_display
(
SCRX, SCRY);
if
(!
display)
erreur
(
"
al_create_display()
"
);
//Récupération de l'image générale
all =
al_load_bitmap
(
"
all_sprites.bmp
"
);
if
(!
all)
erreur
(
"
al_load_bitmap()
"
);
// découpe de l'animation (taille images : 64x64)
for
(
i =
0
; i<
NBIMAGE; i++
){
anim[i] =
recup_sprite
(
all, IMTX, IMTY, 0
, 0
, 8
, i);
if
(!
anim[i])
erreur
(
"
recup_sprite()
"
);
al_convert_mask_to_alpha
(
anim[i],
al_get_pixel
(
anim[i], 0
, 0
));
}
// revenir à l'affichage écran
al_set_target_backbuffer
(
display);
// allocation et initialisation d'un t_sprite
balle =
init_sprite
(
);
do
{
al_get_keyboard_state
(&
key);
// contrôle animation
cntl_anim
(
balle);
// avancer
avance
(
balle);
// affichage
al_clear_to_color
(
al_map_rgb
(
0
, 0
, 0
));
al_draw_bitmap
(
anim[balle->
imcourante],
balle->
x, balle->
y, 0
);
al_flip_display
(
);
al_rest
(
1
.0
/
60
);
}
while
(!
al_key_down
(&
key, ALLEGRO_KEY_ESCAPE));
for
(
i =
0
; i<
NBIMAGE; i++
)
al_destroy_bitmap
(
anim[i]);
al_destroy_display
(
display);
return
0
;
}
/**
***************************************************************
Initialisation d'un sprite
****************************************************************
*/
t_sprite*
init_sprite
(
)
{
t_sprite*
s =
(
t_sprite*
)malloc
(
sizeof
(
t_sprite));
s->
x =
rand
(
) %
(
SCRX -
IMTX);
s->
y =
rand
(
) %
(
SCRY -
IMTY);
s->
dx =
((
float
)rand
(
) /
RAND_MAX) *
6
-
3
;
s->
dy =
((
float
)rand
(
) /
RAND_MAX) *
6
-
3
;
s->
tx =
IMTX; // ou al_get_bitmap_width(anim[0]);
s->
ty =
IMTY;
s->
imcourante =
0
;
s->
nbimage =
NBIMAGE;
s->
tour =
0
;
s->
nbtour =
1
+
rand
(
) %
3
;
s->
dir =
rand
(
) %
2
*
2
-
1
; // -1 ou 1
return
s;
}
/**
***************************************************************
Récupérer sur un fichier la séquence d'animation
****************************************************************
*/
ALLEGRO_BITMAP*
recup_sprite
(
ALLEGRO_BITMAP*
scr, // bitmap d'origine
int
tx, int
ty, // taille élément
int
startx, int
starty, // à partir de
int
colonne, // nombre de colonnes
int
i) // ieme élément
{
ALLEGRO_BITMAP*
sprite =
NULL
;
int
x, y;
sprite =
al_create_bitmap
(
tx, ty);
if
(
sprite !=
NULL
){
// attention colonne doit être > 0
x =
startx +
(
i%
colonne)*
tx;
y =
starty +
(
i /
colonne)*
ty;
al_set_target_bitmap
(
sprite);
al_draw_bitmap_region
(
scr, x, y, tx, ty, 0
, 0
, 0
);
}
return
sprite;
}
/**
***************************************************************
Contrôler l'animation des images (qui peut fonctionner à l'envers)
****************************************************************
*/
void
cntl_anim
(
t_sprite*
s)
{
// attention nbtour doit être > 0
s->
tour =
(
s->
tour +
1
) %
s->
nbtour;
if
(
s->
tour ==
0
){
s->
imcourante +=
s->
dir;
// rester entre 0 et (nbimage-1) compris
s->
imcourante =
(
s->
imcourante +
s->
nbimage) %
s->
nbimage;
}
}
/**
***************************************************************
Contrôle du déplacement dans l'écran. Si un bord est touché
la balle part dans l'autre sens avec un pas et une animation
différents.
****************************************************************
*/
void
avance
(
t_sprite*
s)
{
bool res =
false
;
// avancer
s->
x +=
s->
dx;
s->
y +=
s->
dy;
if
(
s->
x<
0
){
// cntl bords horizontaux
s->
x =
0
;
s->
dx =
((
float
)rand
(
) /
RAND_MAX) *
3
;
res =
true
;
}
else
if
(
s->
x +
s->
tx>
SCRX){
s->
x =
SCRX -
s->
tx;
s->
dx =
((
float
)rand
(
) /
RAND_MAX)*-
3
;
res =
true
;
}
if
(
s->
y<
0
){
// cntl bords verticaux
s->
y =
0
;
s->
dy =
((
float
)rand
(
) /
RAND_MAX) *
3
;
res =
true
;
}
else
if
(
s->
y +
s->
ty>
SCRY){
s->
y =
SCRY -
s->
ty;
s->
dy =
((
float
)rand
(
) /
RAND_MAX)*-
3
;
res =
true
;
}
if
(
res ==
true
){
// si un bord touché
s->
nbtour =
1
+
rand
(
) %
3
;
s->
dir =
(
rand
(
) %
2
) *
2
-
1
;
}
}
/**
***************************************************************
Gestion des erreurs
****************************************************************
*/
void
erreur
(
const
char
*
txt)
{
ALLEGRO_DISPLAY*
d;
d =
al_is_system_installed
(
) ? al_get_current_display
(
) : NULL
;
al_show_native_message_box
(
d, "
ERREUR
"
, txt, NULL
, NULL
, 0
);
exit
(
EXIT_FAILURE);
}
/**
***************************************************************
****************************************************************
*/
X-G. Plusieurs séquences simultanées▲
X-G-1. Plusieurs balles qui rebondissent▲
À partir du programme précédent, nous pouvons avoir très facilement de nombreuses balles simultanées à l'écran. Il suffit d'utiliser un tableau de t_sprite . Les fonctions sont exactement les mêmes : init_sprite() , cntl_anim() , avance() , etc. Chacune est appelée pour toutes les balles du tableau de t_sprite dans une boucle for . Voici le programme précédent qui tourne maintenant avec cinquante balles. Le code modifié est en gras :
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <allegro5/allegro_image.h>
#include <stdio.h>
#include <time.h>
const
int
SCRX =
800
;
const
int
SCRY =
600
;
const
int
IMTX =
64
;
const
int
IMTY =
64
;
const
int
NBIMAGE =
32
;
const
int
NBBALLE =
50
;
typedef
struct
{
// déplacement
float
x, y; // position
float
dx, dy; // déplacement
// image
int
tx, ty; // taille
// animation
int
imcourante; // image courante
int
nbimage; // nombre d'images
int
tour; // compte tours
int
nbtour; // nombre de tours
int
dir; // direction de l'animation
}
t_sprite;
ALLEGRO_BITMAP*
recup_sprite
(
ALLEGRO_BITMAP*
scr,
int
tx, int
ty,
int
startx, int
starty,
int
colonne, int
i);
t_sprite*
init_sprite
(
void
);
void
cntl_anim
(
t_sprite*
s);
void
avance
(
t_sprite*
s);
void
erreur
(
const
char
*
txt);
/**
***************************************************************
****************************************************************
*/
int
main
(
)
{
ALLEGRO_DISPLAY*
display;
ALLEGRO_KEYBOARD_STATE key;
ALLEGRO_BITMAP*
all;
ALLEGRO_BITMAP*
anim[NBIMAGE];
int
i;
t_sprite*
balle[NBBALLE];
srand
(
time
(
NULL
));
if
(!
al_init
(
))
erreur
(
"
al_init()
"
);
if
(!
al_init_image_addon
(
))
erreur
(
"
al_init_image_addon()
"
);
if
(!
al_install_keyboard
(
))
erreur
(
"
al_install_keyboard()
"
);
display =
al_create_display
(
SCRX, SCRY);
if
(!
display)
erreur
(
"
al_create_display()
"
);
//Récupération de l'image générale
all =
al_load_bitmap
(
"
all_sprites.bmp
"
);
if
(!
all)
erreur
(
"
al_load_bitmap()
"
);
// découpe de l'animation (taille images : 64x64)
for
(
i =
0
; i<
NBIMAGE; i++
){
anim[i] =
recup_sprite
(
all, IMTX, IMTY, 0
, 0
, 8
, i);
if
(!
anim[i])
erreur
(
"
recup_sprite()
"
);
al_convert_mask_to_alpha
(
anim[i],
al_get_pixel
(
anim[i], 0
, 0
));
}
// revenir à l'affichage écran
al_set_target_backbuffer
(
display);
// allocation et initialisation des t_sprite
for
(
i =
0
; i <
NBBALLE;i++
)
balle[i] =
init_sprite
(
);
do
{
al_get_keyboard_state
(&
key);
for
(
i =
0
; i <
NBBALLE; i++
){
// contrôle animation
cntl_anim
(
balle[i]);
// avancer
avance
(
balle[i]);
}
// affichage
al_clear_to_color
(
al_map_rgb
(
0
, 0
, 0
));
for
(
i =
0
; i <
NBBALLE;i++
)
al_draw_bitmap
(
anim[balle[i]->
imcourante],balle[i]->
x, balle[i]->
y, 0
);
al_flip_display
(
);
al_rest
(
1
.0
/
60
);
}
while
(!
al_key_down
(&
key, ALLEGRO_KEY_ESCAPE));
for
(
i =
0
; i<
NBIMAGE; i++
)
al_destroy_bitmap
(
anim[i]);
al_destroy_display
(
display);
return
0
;
}
// (...)
/**
***************************************************************
****************************************************************
*/
X-G-2. Plusieurs personnages qui se déplacent▲
Dans un jeu, il y a en général plusieurs personnages, chacun disposant de sa propre animation. Il arrive également qu'un personnage dispose de plusieurs animations qui sont sélectionnées selon les circonstances du jeu, par exemple marcher, sauter, combattre, mourir, etc. Dans ces conditions il peut être intéressant de découpler personnage et animation afin de donner de la souplesse entre chaque personnage et la ou les animations qui le concernent.
Le programme d'expérimentation ci-dessous met en scène plusieurs personnages. Chacun dispose d'une seule animation qui est sur un fichier d'images, nous avons :
La structure de données est en deux parties. D'un côté il y a ce qui concerne la récupération des images de l'animation et de l'autre ce qui concerne le déplacement et le mouvement du personnage.
X-G-2-a. Récupération de l'animation ▲
Au départ les images de chaque animation sont regroupées sur un fichier (BMP, PNG, JPG). Pour les récupérer et les utiliser dans le programme, il nous faut :
- Le nom du fichier conteneur.
- Le point de départ de l'animation dans le fichier conteneur.
- La taille de chaque image dans l'animation (elles sont supposées avoir toutes la même taille).
- S'il y a ou non une image de masque.
- Un tableau dynamique de pointeurs bitmap pour le stockage et l'utilisation de l'animation dans le programme.
Toutes ces informations sont regroupées sous la forme d'une structure :
typedef
struct
{
char
*
nom;
// fichier conteneur avec chemin d'accès
int
startx,starty; // départ dans le fichier conteneur
int
nbcolonne; // nombre de colonnes
int
nbimage // nombre d'images
int
tx,ty; // taille d'une image
bool masque; // 0 si pas de masque, 1 si oui
ALLEGRO_BITAMP**
image; // les images de l'animation
}
t_animation;
Les structures pour l'animation sont déclarées en global parce que chacune est unique. Elles sont toutes partiellement initialisées avec des valeurs connues, en dur à la déclaration. Le seul champ non initialisé à ce moment est le dernier, celui pour stocker les images de l'animation. Sur le disque dur, toutes les images sont au format PNG et elles sont supposées être dans un dossier « anim » qui est dans le répertoire du programme. Il y a un fichier image par animation ce qui donne la séquence de déclarations et d'initialisations :
t_animation ANIMDECOR={
"
anim/decor.png
"
,0
,0
,1
,1
,640
,480
,0
}
;
t_animation ANIMDRAGON={
"
anim/dragon.png
"
,0
,0
,3
,6
,128
,64
,1
}
;
t_animation ANIMPOISSON={
"
anim/poisson.png
"
,0
,0
,3
,3
,64
,32
,1
}
;
t_animation ANIMCRABE={
"
anim/crabe.png
"
,0
,0
,4
,4
,64
,32
,1
}
;
t_animation ANIMABEILLE={
"
anim/abeille.png
"
,0
,0
,6
,6
,50
,40
,1
}
;
t_animation ANIMMOUSTIQUE={
"
anim/moustique.png
"
,0
,0
,6
,6
,50
,40
,1
}
;
t_animation ANIMSERPENT={
"
anim/serpent.png
"
,0
,0
,4
,7
,100
,50
,1
}
;
La chaîne « anim/nom.bmp » fonctionne bien sous Windows avec Code::Blocks et Visual Studio C++. Éventuellement, en cas de problème avec cette chaîne utilisez la forme « .\\anim\\nom.bmp ».
La récupération des images de chaque animation est faite avec la fonction :
void
recup_animation
(
t_animation*
a)
Cette fonction charge l'image qui contient l'animation complète, alloue un tableau de pointeurs ALLEGRO_BITMAP* selon le nombre d'images dans l'animation et y stocke chaque image de l'animation. Chaque image est récupérée avec la fonction déjà présentée :
ALLEGRO_BITMAP*
recup_image
(
ALLEGRO_BITMAP*
scr,int
tx,int
ty, int
startx,int
starty,int
colonne,int
i)
X-G-3. Définition d'un personnage ▲
Pour ce qui concerne le personnage et son comportement, nous avons une autre structure qui contient au minimum ce qui concerne le déplacement et le contrôle de l'animation correspondante.
Pour le déplacement :
- Position
- Pas d'avancement (vitesse)
Pour l'animation :
- Indice de l'image courante dans le tableau des images de l'animation.
- Nombre de tours pendant lequel la même image et affichée.
- Compteur de tours écoulés avec la même image.
- Direction de l'animation (l'animation peut aller à l'endroit ou à l'envers).
- Pointeur sur l'animation courante, c'est-à-dire un pointeur sur une structure t_animation.
C'est la structure suivante :
typedef
struct
{
// déplacement
float
x,y; // position
float
dx,dy; // déplacement
// gestion animation
int
imcourante; // image courante
int
tour; // compte tours
int
nbtour; // nombre de tours
int
dir; // direction de l'animation
t_animation*
anim; // les images de l'animation
}
t_sprite;
Dans le petit monde que nous allons présenter, il y a plusieurs personnages sur un décor de fond : un dragon qui vole, un poisson dans l'eau, un crabe, une abeille, un moustique et un serpent. Tous ces personnages sont des t_sprites regroupés dans un tableau de t_sprite :
t_sprite*
all[NBSPRITE];
Ce tableau n'est pas global mais il est déclaré local dans le main() .
Chaque t_sprite du tableau est identifié avec une constante et l'ensemble de ces constantes constitue un enum :
enum
{
DECOR,DRAGON,POISSON,CRABE,ABEILLE,MOUSTIQUE,SERPENT,NBSPRITE}
;
L'avantage est de pouvoir accéder facilement à chaque personnage avec son nom, mais également de pouvoir les faire défiler tous avec une boucle sur le tableau all de DECOR à NBSPRITE-1 pour les différentes actions (ou éventuellement de DRAGON à NBSPRITE-1).
Le décor est déclaré en premier pour faciliter l'affichage. En effet le décor couvre tout l'écran et l'afficher efface le double buffer. Le décor est considéré comme un t_sprite ce qui permet éventuellement d'avoir un décor animé ou de gérer un scroll.
Chaque t_sprite est initialisé avec la fonction :
t_sprite*
init_sprite
(
int
posx,int
posy,int
dx, int
dy,int
nbtour, int
dir,t_animation*
a)
C'est un constructeur qui alloue un t_sprite et lui passe des valeurs pour sa position, son déplacement, le nombre de tours pour la vitesse de l'animation, la direction de l'animation et une adresse d'animation, à savoir l'adresse d'une des structures t_animation vues précédemment. Ainsi, dans le code, nous avons la séquence suivante pour l'initialisation des personnages :
all[DECOR]=
init_sprite
(
0
,0
,0
,0
,0
,0
,&
ANIMDECOR);
all[DRAGON]=
init_sprite
(
500
,0
,-
5
,0
,5
,1
,&
ANIMDRAGON);
all[POISSON]=
init_sprite
(
300
,400
,3
,0
,8
,1
,&
ANIMPOISSON);
all[CRABE]=
init_sprite
(
300
,212
,2
,0
,20
,1
,&
ANIMCRABE);
all[ABEILLE]=
init_sprite
(
100
,122
,-
3
,0
,8
,1
,&
ANIMABEILLE);
all[MOUSTIQUE]=
init_sprite
(
500
,70
,4
,0
,2
,1
,&
ANIMMOUSTIQUE);
all[SERPENT]=
init_sprite
(
350
,200
,-
2
,0
,4
,1
,&
ANIMSERPENT);
Une boucle n'est pas possible parce que les paramètres sont fixés en dur sans relation les uns aux autres.
L'action dans la boucle d'événements consiste à avancer et animer tout le monde :
for
(
i=
DECOR;i<
NBSPRITE;i++
){
avance_sprite
(
all[i]);
anime_sprite
(
all[i]);
affiche_sprite
(
all[i]);
}
Le décor n'avance pas et ne comporte pas d'animation mais après tout ça pourrait. En l'occurrence ça ne gêne pas parce que toutes les valeurs du décor concernées par ces actions sont à 0.
La fonction pour avancer est la suivante :
void
avance_sprite
(
t_sprite*
s)
{
s->
x +=
s->
dx;
s->
y +=
s->
dy;
//sortie à gauche entrée à droite
if
(
s->
x +
s->
anim->
tx <
0
)
s->
x =
SCRX;
// sortie à droite entrée à gauche
else
if
(
s->
x >
SCRX)
s->
x =
-
s->
anim->
tx;
//sortie en haut entrée en bas
if
(
s->
y +
s->
anim->
ty <
0
)
s->
y =
SCRY;
// sortie à droite entrée à gauche
else
if
(
s->
y >
SCRY)
s->
y =
-
s->
anim->
ty;
}
Elle reçoit en référence une structure t_sprite . Elle modifie sa position avec un contrôle des bords : un personnage qui sort d'un côté entre de l'autre.
La fonction d'animation est la suivante :
void
anime_sprite
(
t_sprite*
s)
{
if
(++
s->
tour >
s->
nbtour) {
s->
imcourante =
(
s->
imcourante +
s->
dir +
s->
anim->
nbimage)%
s->
anim->
nbimage;
s->
tour=
0
;
}
}
Elle incrémente l'image courante selon la direction choisie (de gauche à droite ou de droite à gauche) tout en veillant à bien rester dans la fourchette du nombre d'images nbimage . L'accès à la structure t_animation se fait avec le pointeur selon une double indirection. D'abord vers le champ anim puis vers les champs de l'animation :
s->
anim->
...
L'affichage est juste un appel à la fonction al_draw_bitmap() selon les paramètres du t_sprite à afficher :
void
affiche_sprite
(
t_sprite*
s)
{
al_draw_bitmap
(
s->
anim->
image[s->
imcourante],s->
x,s->
y,0
);
}
Voici le code complet du programme :
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <allegro5/allegro_image.h>
// écran
const
int
SCRX =
640
;
const
int
SCRY =
480
;
// animation
typedef
struct
{
char
*
nom; // fichier conteneur
int
startx, starty; // départ dans le fichier conteneur
int
nbcolonne; // nombre de colonne
int
nbimage; // nombre d'images
int
tx, ty; // taille d'une image
bool masque; // 0 si pas de masque, 1 si oui
ALLEGRO_BITMAP**
image; // les images de l'anim
}
t_animation;
enum
{
DECOR, DRAGON, POISSON, CRABE, ABEILLE, MOUSTIQUE, SERPENT, NBSPRITE }
;
t_animation ANIMDECOR =
{
"
anim/decor.png
"
, 0
, 0
, 1
, 1
, 640
, 480
, 0
}
;
t_animation ANIMDRAGON =
{
"
anim/dragon.png
"
, 0
, 0
, 3
, 6
, 128
, 64
, 1
}
;
t_animation ANIMPOISSON =
{
"
anim/poisson.png
"
, 0
, 0
, 3
, 3
, 64
, 32
, 1
}
;
t_animation ANIMCRABE =
{
"
anim/crabe.png
"
, 0
, 0
, 4
, 4
, 64
, 32
, 1
}
;
t_animation ANIMABEILLE =
{
"
anim/abeille.png
"
, 0
, 0
, 6
, 6
, 50
, 40
, 1
}
;
t_animation ANIMMOUSTIQUE =
{
"
anim/moustique.png
"
, 0
, 0
, 6
, 6
, 50
, 40
, 1
}
;
t_animation ANIMSERPENT =
{
"
anim/serpent.png
"
, 0
, 0
, 4
, 7
, 100
, 50
, 1
}
;
typedef
struct
{
// déplacement
float
x, y; // position
float
dx, dy; // déplacement
// gestion animation
int
imcourante; // image courante
int
tour; // compte tours
int
nbtour; // nombre de tours
int
dir; // direction de l'animation
t_animation*
anim; // les images de l'animations
}
t_sprite;
void
recup_animation
(
t_animation*
a);
ALLEGRO_BITMAP*
recup_image
(
ALLEGRO_BITMAP*
scr, int
tx, int
ty,
int
startx, int
starty, int
colonne,
int
i);
t_sprite*
init_sprite
(
int
posx, int
posy, int
dx, int
dy,
int
nbtour, int
dir, t_animation*
a);
void
avance_sprite
(
t_sprite*
s);
void
anime_sprite
(
t_sprite*
s);
void
affiche_sprite
(
t_sprite*
s);
void
destroy_sprite
(
t_sprite*
s[]);
void
erreur
(
const
char
*
txt);
/**
***************************************************************
****************************************************************
*/
int
main
(
)
{
ALLEGRO_DISPLAY*
display;
ALLEGRO_KEYBOARD_STATE key;
t_sprite*
all[NBSPRITE];
int
i;
if
(!
al_init
(
))
erreur
(
"
al_init()
"
);
if
(!
al_install_keyboard
(
))
erreur
(
"
al_install_keyboard()
"
);
if
(!
al_init_image_addon
(
))
erreur
(
"
al_init_image_addon()
"
);
display =
al_create_display
(
SCRX, SCRY);
if
(!
display)
erreur
(
"
al_create_display()
"
);
// récupération des animations
recup_animation
(&
ANIMDECOR);
recup_animation
(&
ANIMDRAGON);
recup_animation
(&
ANIMPOISSON);
recup_animation
(&
ANIMCRABE);
recup_animation
(&
ANIMABEILLE);
recup_animation
(&
ANIMMOUSTIQUE);
recup_animation
(&
ANIMSERPENT);
// initialisation des sprites
all[DECOR] =
init_sprite
(
0
, 0
, 0
, 0
, 0
, 0
, &
ANIMDECOR);
all[DRAGON] =
init_sprite
(
500
, 0
, -
5
, 0
, 5
, 1
, &
ANIMDRAGON);
all[POISSON] =
init_sprite
(
300
, 400
, 3
, 0
, 8
, 1
, &
ANIMPOISSON);
all[CRABE] =
init_sprite
(
300
, 212
, 2
, 0
, 20
, 1
, &
ANIMCRABE);
all[ABEILLE] =
init_sprite
(
100
, 122
, -
3
, 0
, 8
, 1
, &
ANIMABEILLE);
all[MOUSTIQUE] =
init_sprite
(
500
, 70
, 4
, 0
, 2
, 1
, &
ANIMMOUSTIQUE);
all[SERPENT] =
init_sprite
(
350
, 200
, -
2
, 0
, 4
, 1
, &
ANIMSERPENT);
// ne pas oublier!
al_set_target_backbuffer
(
display);
do
{
al_get_keyboard_state
(&
key);
for
(
i =
DECOR; i<
NBSPRITE; i++
){
avance_sprite
(
all[i]);
anime_sprite
(
all[i]);
affiche_sprite
(
all[i]);
}
al_flip_display
(
);
al_rest
(
1
.0
/
30
);
}
while
(!
al_key_down
(&
key, ALLEGRO_KEY_ESCAPE));
//destroy_sprite(all);// BUG
al_destroy_display
(
display);
return
0
;
}
/**
***************************************************************
****************************************************************
*/
void
recup_animation
(
t_animation*
a)
{
int
i;
ALLEGRO_BITMAP*
conteneur;
// récupération du fichier conteneur
conteneur =
al_load_bitmap
(
a->
nom);
if
(!
conteneur)
erreur
(
"
recup_animation(), conteneur
"
);
// allocation du tableau dynamique d'images
a->
image =
(
ALLEGRO_BITMAP**
)malloc
(
sizeof
(
ALLEGRO_BITMAP*
)*
a->
nbimage);
for
(
i =
0
; i<
a->
nbimage; i++
){
a->
image[i] =
recup_image
(
conteneur,// bitmap d'origine
a->
tx, a->
ty,// taille élément
0
, 0
, // à partir du point h-g
a->
nbcolonne,// nombre colonnes
i);// ieme élément de 0 à nbimage-1
if
(!
a->
image[i])
erreur
(
"
recup_animation()
"
);
if
(
a->
masque ==
true
)
al_convert_mask_to_alpha
(
a->
image[i],
al_get_pixel
(
a->
image[i], 0
, 0
));
}
}
/**
***************************************************************
****************************************************************
*/
ALLEGRO_BITMAP*
recup_image
(
ALLEGRO_BITMAP*
scr, int
tx, int
ty,
int
startx, int
starty, int
colonne, int
i)
{
ALLEGRO_BITMAP*
image =
NULL
;
int
x, y;
image =
al_create_bitmap
(
tx, ty);
if
(
image !=
NULL
){
// attention colonne doit être > 0
x =
startx +
(
i%
colonne)*
tx;
y =
starty +
(
i /
colonne)*
ty;
al_set_target_bitmap
(
image);
al_draw_bitmap_region
(
scr, x, y, tx, ty, 0
, 0
, 0
);
}
return
image;
}
/**
***************************************************************
****************************************************************
*/
t_sprite*
init_sprite
(
int
posx, int
posy,
int
dx, int
dy,
int
nbtour, int
dir,
t_animation*
a)
{
t_sprite*
s =
(
t_sprite*
)malloc
(
sizeof
(
t_sprite));
s->
x =
posx;
s->
y =
posy;
s->
dx =
dx;
s->
dy =
dy;
s->
imcourante =
0
;
s->
tour =
0
;
s->
nbtour =
nbtour;
s->
dir =
dir;
s->
anim =
a;
return
s;
}
/**
***************************************************************
****************************************************************
*/
void
avance_sprite
(
t_sprite*
s)
{
s->
x +=
s->
dx;
s->
y +=
s->
dy;
//sortie à gauche entrée à droite
if
(
s->
x +
s->
anim->
tx <
0
)
s->
x =
SCRX;
// sortie à droite entrée à gauche
else
if
(
s->
x >
SCRX)
s->
x =
-
s->
anim->
tx;
//sortie en haut entrée en bas
if
(
s->
y +
s->
anim->
ty <
0
)
s->
y =
SCRY;
// sortie à droite entrée à gauche
else
if
(
s->
y >
SCRY)
s->
y =
-
s->
anim->
ty;
}
/**
***************************************************************
****************************************************************
*/
void
anime_sprite
(
t_sprite*
s)
{
if
(++
s->
tour >
s->
nbtour) {
s->
imcourante =
(
s->
imcourante +
s->
dir +
s->
anim->
nbimage) %
s->
anim->
nbimage;
s->
tour =
0
;
}
}
/**
***************************************************************
****************************************************************
*/
void
affiche_sprite
(
t_sprite*
s)
{
al_draw_bitmap
(
s->
anim->
image[s->
imcourante], s->
x, s->
y, 0
);
}
/**
***************************************************************
****************************************************************
*/
void
destroy_sprite
(
t_sprite*
s[])
{
int
i;
for
(
i =
0
; i<
NBSPRITE; i++
){
// l'animation peut servir à plusieurs sprite mais
// il faut la détruire une seule fois
if
(
s[i]->
anim !=
NULL
){
if
(
s[i]->
anim->
image !=
NULL
){
for
(
i =
0
; i <
s[i]->
anim->
nbimage; i++
)
al_destroy_bitmap
(
s[i]->
anim->
image[i]);
free
(
s[i]->
anim->
image);
s[i]->
anim->
image =
NULL
;
}
}
free
(
s[i]);
}
}
/**
***************************************************************
****************************************************************
*/
void
erreur
(
const
char
*
txt)
{
ALLEGRO_DISPLAY*
d;
d =
al_is_system_installed
(
) ? al_get_current_display
(
) : NULL
;
al_show_native_message_box
(
d, "
Erreur
"
, txt, NULL
, NULL
, 0
);
exit
(
EXIT_FAILURE);
}
/**
***************************************************************
****************************************************************
*/
Obtenir ce livre▲
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. |