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 :
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 :
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 :
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 :
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 :
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 :
LEFT : 0
RIGHT : 1
UP : 2
DOWN : 3
Ces quatre identifiants de direction sont définis dans un enum :
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 :
#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 :
#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 :
#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 :
val /
PIXMAPX
vaut :
val /
MAPTX *
PIXTILEX
c'est-à-dire :
(
val /
MAPTX) *
PIXTILEX // associativité gauche
Avec parenthèses l'expression vaut :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
coordonnées matrice =
(
x /
PIXTILEX, y /
PIXTILEY)
Mais un personnage peut se trouver à cheval sur plusieurs tuiles :
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 :
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 :
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é▲
#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 ▲
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 :
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 :
int
KEY[NBDIR]={}
;
La multitude des clones ainsi que le joueur tiennent dans un tableau de personnages localisé dans le main() :
#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 :
ALLEGRO_DISPLAY*
allegro_init
(
ALLEGRO_EVENT_QUEUE**
queue, ALLEGRO_TIMER**
timer);
La construction du décor avec la fonction :
ALLEGRO_BITMAP*
construct_decor
(
void
);
Nous retrouvons également les fonctions outils :
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 :
t_personnage*
constructeur_joueur
(
)
est rebaptisée en :
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 :
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 :
void
recup_animation (
void
);
devient :
void
recup_anim_personnage
(
void
) ;
L'affichage reste identique, c'est la fonction :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
int
cntl_avance_personnage
(
int
x, int
y, t_personnage*
j)
Ce qui donne la fonction d'avance du joueur suivante :
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é ▲
// 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▲
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. |