XIII. Collisions entre personnages▲
XIII-A. Introduction▲
Dans le programme précédent, les collisions entre personnages ne sont pas gérées et l'objectif est maintenant d'ajouter cette compétence au programme précédent. Ni les clones ni le joueur ne doivent pouvoir passer les uns à travers les autres comme des fantômes.
XIII-B. Cartographie des personnages▲
Nous partons d'une copie du programme précédent avec un joueur et une tribu de clones qui se déplacent en respectant des contraintes de terrain mais sans interaction entre eux. Nous voulons qu'ils se détectent mutuellement et gèrent les collisions.
XIII-B-1. Intersection de rectangles ? ▲
Avec cinquante personnages, nous pouvons utiliser une intersection de rectangles pour détecter les collisions (technique présentée dans la section Collisions - Intersection de rectangles du chapitre Modèle de jeu simpleModèle de jeu simple). L'algorithme est le suivant : pour chaque personnage, à chaque déplacement, tester l'intersection avec tous les autres. Le nombre d'opérations nécessaires avec un tel algorithme croît de façon exponentielle avec le nombre des personnages. En effet, s'il y a N personnages, pour chaque personnage il faut regarder les N autres ce qui fait N fois N cas, N² cas.
Plus il y a de personnages, plus sont multipliées les opérations nécessaires :
Pour 50 personnages nous avons 50*50 : 2500
Pour 60 personnages nous avons 60*60 : 3600
Pour 70 personnages nous avons 70*70 : 4900
Pour 80 personnages nous avons 80*80 : 6400
Pour 90 personnages nous avons 90*90 : 8100
Pour 100 personnages nous avons 100*100 : 10000
Pour 200 personnages nous avons 200*200 : 40000
Nous constatons qu'en partant de 50 nous avons déjà 2500 opérations. Avec 200 personnages, soit 4 fois plus, nous avons 16 fois plus d'opérations (40000/2500) et plus le nombre de personnages augmente, plus l'écart se creuse.
Puisque nous sommes dans les cartes (map), nous allons tester une autre technique qui repose sur une cartographie des personnages. Tous les personnages vont être suivis sur une carte comme des tuiles de décor dotées du mouvement. Du point de vue algorithmique, lors de chaque déplacement, chaque personnage regarde la carte et sait immédiatement s'il entre en collision ou non avec un autre. Le personnage peut même connaître avec qui il est entré en contact. Il suffit que chaque personnage donne sur la carte, un moyen de l'identifier. L'identificateur peut être un nombre ou même l'adresse mémoire du personnage. Quoi qu'il en soit, c'est intéressant parce qu'il n'y a pas d'augmentation de la complexité :
pour 50 personnages nous n'avons que 50*1 : 50 opérations
…
et pour 200 personnages nous avons seulement 200*1 : 200 opérations.
Il s'ajoute de façon constante une opération par personnage quel que soit le nombre de personnages. C'est une stratégie très bénéfique en temps de calcul. Elle est très utile pour gérer de grands nombres de personnages ou d'éléments animés en interaction. En revanche la mise en place nécessite beaucoup de soin et d'attention. Des erreurs peuvent se glisser qu'il n'est pas toujours évident de comprendre et discerner.
XIII-B-2. Mise en place de la carte▲
Pour commencer la carte des personnages est une seconde matrice d'entiers identique à la carte du terrain. Nous aurions pu tout faire sur la même matrice. C'est un choix.
Les dimensions du terrain sont données par :
#define MAPTX 20
#define MAPTY 15
et ce sont les mêmes pour la carte des personnages. Elle est initialisée à 0 à la déclaration :
int
MAPPERSON[MAPTY][MAPTX] =
{}
;
Une position est considérée comme libre si elle est à 0 et comme occupée si elle est à 1. Ces valeurs sont rendues plus lisibles avec deux macros :
#define LIBRE 0
#define OCCUPE 1
Dans un souci d'harmonisation et de lisibilité des cartes décor et personnages, les positions sur 3 (herbe) dans MAPDECOR deviennent des positions libres et toutes les autres deviennent des positions occupées par le décor. Une fonction opère cette modification de la carte MAPDECOR de la façon suivante :
void
mise_a_libre_ou_occupe
(
)
{
int
y, x;
for
(
y =
0
; y <
MAPTY;y++
)
for
(
x =
0
; x <
MAPTX; x++
)
if
(
MAPDECOR[y][x] ==
3
)
MAPDECOR[y][x] =
LIBRE;
else
MAPDECOR[y][x] =
OCCUPE;
}
Elle est appelée au moment de la création du décor, à la fin du constructeur décor :
ALLEGRO_BITMAP*
construct_decor
(
)
{
(
...)
mise_a_libre_ou_occupe
(
);
return
decor;
}
Au départ, les personnages doivent être inscrits dans la carte selon leur initialisation dans le constructeur de personnages. Cela se fait juste après qu'une position accessible soit trouvée dans le décor :
t_personnage*
construct_personnage
(
)
{
(
...)
// trouver une position LIBRE dans le décor
while
(
MAPDECOR[j->
y][j->
x] !=
LIBRE){
if
(
rand
(
) %
2
)
j->
x =
(++
j->
x) %
MAPTX;
else
j->
y =
(++
j->
y) %
MAPTY;
}
// marquer l'emplacement par le personnage
// dans la carte des personnages
MAPPERSON[j->
y][j->
x] =
OCCUPE;
(
...)
return
j;
}
Dans cette première version du programme, nous acceptons que plusieurs personnages soient à la même place au départ. Ce n'est pas trop gênant parce qu'ils chercheront à se dégager dès qu'ils vont se déplacer.
XIII-B-3. Mouvement des personnages▲
Dans le chapitre Personnages dans un décor, section Nombreux personnages mobilesNombreux personnages mobiles, l'avance des personnages est contrôlée avec la fonction :
int
cntl_avance_personnage
(
int
x, int
y, t_personnage*
j) ;
dans laquelle seules les interactions avec le décor sont prises en compte. Dans la nouvelle fonction rebaptisée bouge_personnages , le contrôle des interactions avec le décor n'a pas changé mais un contrôle sur les personnages est ajouté. Le contrôle total est le suivant :
si le personnage reste dans l'écran et
si sa nouvelle position respecte le terrain et
s'il n'entre en collision avec personne alors
il peut avancer.
Le troisième test sur les personnages si les deux premiers sont passés est nouveau. Il consiste à regarder dans la carte des personnages si un coin du personnage qui avance entre en contact avec un autre. Il est tourné ainsi :
(
...)
// left top right bottom
l =
x /
PIXTILEX;
t =
y /
PIXTILEX;
r =
(
x +
j->
tx -
1
) /
PIXTILEX;
b =
(
y +
j->
ty -
1
) /
PIXTILEY;
if
(
MAPPERSON[t][l] ==
LIBRE &&
MAPPERSON[t][r] ==
LIBRE &&
MAPPERSON[b][l] ==
LIBRE &&
MAPPERSON[b][r] ==
LIBRE){
//avancer
j->
x =
x;
j->
y =
y;
res=
1
;
}
Notons également qu'à la différence de la fonction précédente, le mouvement a lieu dans la fonction, la position du personnage j est modifiée si tous les tests sont vrais.
Par ailleurs, le personnage qui bouge ne doit pas compter les interactions avec lui-même. De plus, s'il bouge il devra quitter sa place pour une nouvelle. Pour ces deux raisons, le personnage est au début de la fonction effacé de la carte des personnages :
mapx =
j->
x /
PIXTILEX;
mapy =
j->
y /
PIXTILEY;
MAPPERSON[mapy][mapx] =
LIBRE;
Ensuite à la fin de l'opération qu'il avance ou non, il est réinscrit à sa position dans la carte des personnages :
mapx =
j->
x /
PIXTILEX;
mapy =
j->
y /
PIXTILEY;
MAPPERSON[mapy][mapx] =
OCCUPE;
La fonction est rebaptisée bouge_personnage pour une question de lisibilité du code au moment de l'appel. Voici la fonction :
int
bouge_personnage
(
int
x, int
y, t_personnage*
j)
{
int
l, r, t, b;
int
mapx, mapy;
int
res =
0
;
// mise à LIBRE position actuelle dans la carte
// des personnages
mapx =
j->
x /
PIXTILEX;
mapy =
j->
y /
PIXTILEY;
MAPPERSON[mapy][mapx] =
LIBRE;
// si le personnage reste dans l'écran
if
(
x >=
0
&&
x +
j->
tx <=
PIXMAPX &&
y >=
0
&&
y +
j->
ty <=
PIXMAPY){
// left top right bottom
l =
x /
PIXTILEX;
t =
y /
PIXTILEX;
r =
(
x +
j->
tx -
1
) /
PIXTILEX;
b =
(
y +
j->
ty -
1
) /
PIXTILEY;
// et si les 4 coins du sprite sont tous sur du HERBE (herbe)
if
(
MAPDECOR[t][l] ==
LIBRE &&
MAPDECOR[t][r] ==
LIBRE &&
MAPDECOR[b][l] ==
LIBRE &&
MAPDECOR[b][r] ==
LIBRE){
if
(
MAPPERSON[t][l] ==
LIBRE &&
MAPPERSON[t][r] ==
LIBRE &&
MAPPERSON[b][l] ==
LIBRE &&
MAPPERSON[b][r] ==
LIBRE){
//avancer
j->
x =
x;
j->
y =
y;
res =
1
;
}
}
}
// mise à OCCUPE
mapx =
j->
x /
PIXTILEX;
mapy =
j->
y /
PIXTILEY;
MAPPERSON[mapy][mapx] =
OCCUPE;
return
res;
}
Cette fonction est appelée deux fois. Une fois pour les clones et une fois pour le joueur. L'appel est simplifié parce que la modification de la position est dans la fonction. Pour les clones, l'appel est le suivant dans la fonction avance_clone :
if
(!
bouge_personnage
(
x, y, j))
direction_clone
(
j);
Si le personnage ne bouge pas, il doit chercher une nouvelle direction. Pour cette recherche nous n'avons rien changé. C'est exactement la même fonction. Le clone prend uniquement en compte le terrain et ignore les autres personnages. L'idée est qu'il devra peut-être passer plusieurs tours à chercher une direction et jusqu'à ce qu'il en trouve libre et accessible autour de lui.
Pour ce qui concerne le joueur, l'appel de la fonction bouge_personnage a lieu dans la fonction avance_joueur .
XIII-B-4. Code complet commenté▲
// pour utilisation de la fonction sprintf
// jugée unsafe par microsoft
#define _CRT_SECURE_NO_WARNINGS
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <allegro5/allegro_primitives.h>
#include <allegro5/allegro_image.h>
#include <stdio.h>
#define SCREENX 640
#define SCREENY 480
#define BLANC al_map_rgb(255,255,255)
#define GRIS al_map_rgb(128,128,128)
// TUILES ET MAPDECOR
// largeur et hauteur des carreaux en pixels
#define PIXTILEX 32
#define PIXTILEY 32
// taille en tuiles de la MAPDECOR
#define MAPTX 20
#define MAPTY 15
// contrôle de collisions
#define LIBRE 0
#define OCCUPE 1
// taille en pixels de la MAPDECOR
#define PIXMAPX (MAPTX*PIXTILEX)
#define PIXMAPY (MAPTY*PIXTILEY)
// la carte des tuiles du terrain
short
MAPDECOR[MAPTY][MAPTX] =
{
{
3
, 3
, 3
, 7
,7
,7
,55
,8
,8
,2
,3
,3
,3
,3
,3
,3
,3
, 3
, 3
,101
}
,
{
3
, 3
, 3
, 3
,3
,3
,55
,1
,1
,3
,3
,3
,3
,3
,3
,3
,3
,101
,101
,101
}
,
{
3
, 3
, 3
, 3
,3
,3
, 3
,2
,1
,3
,3
,3
,2
,3
,3
,3
,3
, 3
,101
, 3
}
,
{
3
, 3
, 1
, 3
,3
,3
, 3
,3
,3
,3
,3
,2
,2
,3
,3
,3
,2
,101
,101
, 3
}
,
{
3
, 1
, 1
, 3
,3
,3
, 3
,3
,3
,3
,2
,2
,3
,3
,3
,3
,2
, 3
, 3
, 3
}
,
{
3
, 1
, 1
, 1
,3
,3
, 3
,3
,3
,8
,2
,2
,2
,3
,3
,1
,7
, 1
, 3
, 3
}
,
{
3
, 3
, 3
, 1
,1
,3
, 3
,3
,3
,3
,1
,3
,3
,3
,3
,3
,3
, 3
, 3
, 3
}
,
{
3
, 3
, 3
, 1
,3
,3
, 3
,3
,1
,3
,3
,3
,3
,3
,3
,3
,3
, 3
, 3
, 3
}
,
{
3
, 3
, 3
, 3
,3
,3
, 3
,2
,1
,3
,3
,3
,3
,3
,3
,3
,3
, 3
, 3
, 3
}
,
{
4
, 4
, 3
, 3
,3
,3
, 3
,2
,1
,3
,3
,6
,6
,3
,3
,3
,3
, 3
, 4
, 4
}
,
{
4
, 4
, 4
, 3
,3
,3
, 2
,2
,3
,3
,3
,4
,4
,3
,3
,3
,3
, 81
, 81
, 2
}
,
{
4
, 4
, 3
, 3
,3
,3
, 2
,1
,3
,3
,3
,4
,1
,3
,3
,3
,4
, 4
, 81
, 81
}
,
{
4
, 4
, 4
, 3
,3
,3
, 3
,1
,3
,3
,3
,3
,2
,3
,3
,4
,4
, 4
, 81
, 81
}
,
{
81
,81
, 4
, 3
,3
,3
, 3
,1
,1
,1
,3
,3
,3
,3
,4
,4
,4
, 2
, 4
, 81
}
,
{
81
,81
,81
,81
,3
,3
, 3
,3
,3
,3
,3
,3
,3
,4
,4
,4
,2
, 2
, 81
, 81
}
}
;
// PERSONNAGE
#define JOUEUR 50
// ensemble des variables qui identifient un personnage
typedef
struct
{
int
x, y; // position
int
pas; // déplacement
int
tx, ty;// taille
//animation
int
tour, nbtour;
int
nbimage;
int
imcourante;
int
dir;
}
t_personnage;
// 4 directions
enum
{
LEFT, RIGHT, UP, DOWN, NBDIR }
;
// pour clavier fluide
int
KEY[NBDIR] =
{}
;
// tableau des animations :
// 4 directions, 4 images par direction
ALLEGRO_BITMAP*
ANIM[NBDIR][4
];
//carte des personnages
int
MAPPERSON[MAPTY][MAPTX] =
{}
;
ALLEGRO_BITMAP*
construct_decor (
void
);
void
mise_a_libre_ou_occupe (
void
);
void
recup_anim_personnage (
void
);
t_personnage*
construct_personnage (
void
);
int
bouge_personnage (
int
x, int
y,
t_personnage*
j);
void
affiche_personnage (
t_personnage*
j);
void
avance_clone (
t_personnage*
j);
void
direction_clone (
t_personnage*
j);
void
controle_joueur (
ALLEGRO_EVENT*
e,
t_personnage*
j);
void
avance_joueur (
t_personnage*
j);
ALLEGRO_BITMAP*
recup_sprite (
ALLEGRO_BITMAP*
scr,
int
tx, int
ty,
int
startx, int
starty,
int
colonne, int
i);
ALLEGRO_DISPLAY*
allegro_init (
ALLEGRO_EVENT_QUEUE**
queue,
ALLEGRO_TIMER**
timer);
void
erreur (
const
char
*
msg);
/**
***************************************************************
****************************************************************
*/
int
main
(
)
{
ALLEGRO_DISPLAY*
display;
ALLEGRO_EVENT_QUEUE*
queue;
ALLEGRO_TIMER*
timer;
ALLEGRO_BITMAP*
decor;
// l'ensemble des personnages avec le joueur
// placé en dernier
t_personnage*
allP[JOUEUR+
1
];
bool fin =
0
;
bool dessine =
true
;
int
i;
display =
allegro_init
(&
queue, &
timer);
decor =
construct_decor
(
);
recup_anim_personnage
(
);
// revenir à l'affichage écran
al_set_target_backbuffer
(
display);
// création des personnages
for
(
i =
0
; i <=
JOUEUR;i++
)
allP[i]=
construct_personnage
(
);
while
(!
fin){
ALLEGRO_EVENT event;
al_wait_for_event
(
queue, &
event);
controle_joueur
(&
event, allP[JOUEUR]);
if
(
event.type ==
ALLEGRO_EVENT_DISPLAY_CLOSE ||
event.keyboard.keycode ==
ALLEGRO_KEY_ESCAPE)
fin =
true
;
else
if
(
event.type ==
ALLEGRO_EVENT_TIMER){
// mouvement
avance_joueur
(
allP[JOUEUR]);
for
(
i =
0
; i <
JOUEUR; i++
)
avance_clone
(
allP[i]);
// redessiner
dessine =
true
;
}
if
(
dessine ==
true
&&
al_is_event_queue_empty
(
queue)){
al_draw_bitmap
(
decor, 0
, 0
, 0
);
// affichage de tous
for
(
i =
0
; i <=
JOUEUR; i++
)
affiche_personnage
(
allP[i]);
al_flip_display
(
);
dessine =
false
;
}
}
for
(
i =
0
; i <=
JOUEUR; i++
)
free
(
allP[i]);
al_destroy_bitmap
(
decor);
al_destroy_display
(
display);
al_destroy_timer
(
timer);
al_destroy_event_queue
(
queue);
return
0
;
}
/**
**************************************************************
DECOR / Création de la bitmap du decor
***************************************************************
*/
ALLEGRO_BITMAP*
construct_decor
(
)
{
ALLEGRO_BITMAP*
decor, *
bmp;
ALLEGRO_BITMAP*
tuiles;
int
mapx, mapy, posx, posy;
// bitmap decor et load tiles
decor =
al_create_bitmap
(
PIXMAPX, PIXMAPY);
tuiles =
al_load_bitmap
(
"
.
\\
images
\\
kit_de_tuiles_2.png
"
);
if
(!
decor ||
!
tuiles)
erreur
(
"
decor, tuiles
"
);
// construction du decor
for
(
mapy =
0
; mapy<
MAPTY; mapy++
)
for
(
mapx =
0
; mapx<
MAPTX; mapx++
){
bmp =
recup_sprite
(
tuiles, // fichier
PIXTILEX, PIXTILEY,// taille tuiles
0
, 0
, // pos départ
20
, // nombre de colonnes
MAPDECOR[mapy][mapx]);// N° de tuile
if
(!
bmp)
erreur
(
"
recup_sprite()
"
);
posx =
mapx*
PIXTILEX;
posy =
mapy*
PIXTILEY;
al_set_target_bitmap
(
decor);
al_draw_bitmap
(
bmp, posx, posy, 0
);
al_draw_rectangle
(
posx, posy,
posx +
PIXTILEX, posy +
PIXTILEY,
GRIS, 1
);
al_destroy_bitmap
(
bmp);
}
// libérer mémoire image inutile
al_destroy_bitmap
(
tuiles);
// les position sont soit libres soit occupées
mise_a_libre_ou_occupe
(
);
// retour adresse du décor
return
decor;
}
/**
**************************************************************
DECOR / met à LIBRE ou OCCUPE les positions dans la map MAPDECOR
selon qu'elles sont accessibles ou non aux personnages du jeu
***************************************************************
*/
void
mise_a_libre_ou_occupe
(
)
{
int
y, x;
for
(
y =
0
; y <
MAPTY;y++
)
for
(
x =
0
; x <
MAPTX; x++
)
if
(
MAPDECOR[y][x] ==
3
)
MAPDECOR[y][x] =
LIBRE;
else
MAPDECOR[y][x] =
OCCUPE;
}
/**
***************************************************************
PERSONNAGES / création
****************************************************************
*/
t_personnage*
construct_personnage
(
)
{
t_personnage*
j =
(
t_personnage*
)malloc
(
sizeof
(
t_personnage));
j->
tx =
PIXTILEX;
j->
ty =
PIXTILEY;
j->
pas =
4
;
// position (en coordonnées MAPDECOR)
j->
x =
rand
(
) %
MAPTX;
j->
y =
rand
(
) %
MAPTY;
// trouver LIBRE
while
(
MAPDECOR[j->
y][j->
x] !=
LIBRE){
if
(
rand
(
) %
2
)
j->
x =
(++
j->
x) %
MAPTX;
else
j->
y =
(++
j->
y) %
MAPTY;
}
// marquer l'emplacement par le personnage
MAPPERSON[j->
y][j->
x] =
OCCUPE;
// convertir la position matrice en position écran
// (en coordonnées pixels)
j->
x *=
PIXTILEX;
j->
y *=
PIXTILEY;
// animation
j->
tour =
0
;
j->
nbtour =
5
;
j->
nbimage =
4
;
j->
imcourante =
0
;
j->
dir =
rand
(
) %
NBDIR;
return
j;
}
/**
***************************************************************
PERSONNAGES / controle du déplacement
****************************************************************
*/
int
bouge_personnage
(
int
x, int
y, t_personnage*
j)
{
int
l, r, t, b;
int
mapx, mapy;
int
res =
0
;
// mise à LIBRE position actuelle dans la carte
// des personnages
mapx =
j->
x /
PIXTILEX;
mapy =
j->
y /
PIXTILEY;
MAPPERSON[mapy][mapx] =
LIBRE;
// si le personnage reste dans l'écran
if
(
x >=
0
&&
x +
j->
tx <=
PIXMAPX &&
y >=
0
&&
y +
j->
ty <=
PIXMAPY){
// left top right bottom
l =
x /
PIXTILEX;
t =
y /
PIXTILEX;
r =
(
x +
j->
tx -
1
) /
PIXTILEX;
b =
(
y +
j->
ty -
1
) /
PIXTILEY;
// et si les 4 coins du sprite sont tous sur
// de l'herbe (LIBRE)
if
(
MAPDECOR[t][l] ==
LIBRE &&
MAPDECOR[t][r] ==
LIBRE &&
MAPDECOR[b][l] ==
LIBRE &&
MAPDECOR[b][r] ==
LIBRE){
// et si aucun autre personnage
if
(
MAPPERSON[t][l] ==
LIBRE &&
MAPPERSON[t][r] ==
LIBRE &&
MAPPERSON[b][l] ==
LIBRE &&
MAPPERSON[b][r] ==
LIBRE){
//avancer
j->
x =
x;
j->
y =
y;
res =
1
;
}
}
}
// mise à OCCUPE
mapx =
j->
x /
PIXTILEX;
mapy =
j->
y /
PIXTILEY;
MAPPERSON[mapy][mapx] =
OCCUPE;
return
res;
}
/**
***************************************************************
PERSONNAGES / Affichage
****************************************************************
*/
void
affiche_personnage
(
t_personnage*
j)
{
// l'animation est permanente
j->
tour++
;
if
(
j->
tour ==
j->
nbtour){
j->
imcourante =
(++
j->
imcourante) %
j->
nbimage;
j->
tour =
0
;
}
// affichage du joueur
al_draw_scaled_bitmap
(
ANIM[j->
dir][j->
imcourante],
0
, 0
, 64
, 64
, // source
j->
x, j->
y, // cible
j->
tx, j->
ty,
0
);
}
/**
***************************************************************
PERSONNAGES / récupération des 4 animations communes à tous
****************************************************************
*/
void
recup_anim_personnage
(
)
{
int
dir, i;
char
nom[256
];
for
(
dir =
0
; dir <
NBDIR; dir++
){
for
(
i =
0
; i <
4
; i++
){
sprintf
(
nom, "
.
\\
images
\\
joueur
\\
hero_%d_%d.bmp
"
, dir, i);
ANIM[dir][i] =
al_load_bitmap
(
nom);
if
(!
ANIM[dir][i])
erreur
(
"
ANIM[dir][i] = al_load_bitmap(nom)
"
);
al_convert_mask_to_alpha
(
ANIM[dir][i],
al_get_pixel
(
ANIM[dir][i], 0
, 0
));
}
}
}
/**
***************************************************************
CLONES / déplacement automatique
****************************************************************
*/
void
avance_clone
(
t_personnage*
j)
{
int
x, y;
// copie position et taille
y =
j->
y;
x =
j->
x;
// avance selon direction
switch
(
j->
dir){
case
LEFT: x -=
j->
pas; break
;
case
RIGHT: x +=
j->
pas; break
;
case
UP: y -=
j->
pas; break
;
case
DOWN: y +=
j->
pas; break
;
}
if
(!
bouge_personnage
(
x, y, j))
direction_clone
(
j);
}
/**
***************************************************************
CLONES / choix direction
Le principe est :
- de tirer une direction aléatoire
- si impossible (cause bords, terrain)
- prendre la suivante
****************************************************************
*/
void
direction_clone
(
t_personnage*
j)
{
int
sens =
(
rand
(
) %
2
) *
2
-
1
; //-1 ou 1
int
dir =
rand
(
) %
NBDIR; // entre 0 et HERBE compris
int
res =
-
1
;
// passer en coordonnées MAPDECOR
int
x =
j->
x /
PIXTILEX;
int
y =
j->
y /
PIXTILEX;
// vérifier la direction, si impossible prendre la
// suivante en tournant selon "sens" (vers gauche ou droite)
do
{
dir =
((
dir +
sens) +
NBDIR) %
NBDIR;
switch
(
dir){
case
LEFT:
if
(
x -
1
>=
0
&&
MAPDECOR[y][x -
1
] ==
LIBRE)
res =
LEFT;
break
;
case
RIGHT:
if
(
x +
1
<
MAPTX &&
MAPDECOR[y][x +
1
] ==
LIBRE)
res =
RIGHT;
break
;
case
UP:
if
(
y -
1
>=
0
&&
MAPDECOR[y -
1
][x] ==
LIBRE)
res =
UP;
break
;
case
DOWN:
if
(
y +
1
<
MAPTY &&
MAPDECOR[y +
1
][x] ==
LIBRE)
res =
DOWN;
break
;
}
}
while
(
res <
0
);
j->
dir =
res;
}
/**
***************************************************************
JOUEUR / contrôle clavier du joueur
****************************************************************
*/
void
controle_joueur
(
ALLEGRO_EVENT*
e, t_personnage*
j)
{
if
(
e->
type ==
ALLEGRO_EVENT_KEY_DOWN){
switch
(
e->
keyboard.keycode){
case
ALLEGRO_KEY_LEFT: //82
case
ALLEGRO_KEY_RIGHT: //83
case
ALLEGRO_KEY_UP: //84
case
ALLEGRO_KEY_DOWN: //85
j->
dir =
e->
keyboard.keycode -
ALLEGRO_KEY_LEFT;
KEY[j->
dir] =
1
;
break
;
}
}
else
if
(
e->
type ==
ALLEGRO_EVENT_KEY_UP){
switch
(
e->
keyboard.keycode){
case
ALLEGRO_KEY_LEFT:
case
ALLEGRO_KEY_RIGHT:
case
ALLEGRO_KEY_UP:
case
ALLEGRO_KEY_DOWN:
j->
dir =
e->
keyboard.keycode -
ALLEGRO_KEY_LEFT;
KEY[j->
dir] =
0
;
break
;
}
}
}
/**
***************************************************************
JOUEUR / déplacement
****************************************************************
*/
void
avance_joueur
(
t_personnage*
j)
{
int
x, y;
// copie position et taille
y =
j->
y;
x =
j->
x;
// avance selon état du clavier
x -=
KEY[LEFT] *
j->
pas;
x +=
KEY[RIGHT] *
j->
pas;
y -=
KEY[UP] *
j->
pas;
y +=
KEY[DOWN] *
j->
pas;
// controle bords et terrain
bouge_personnage
(
x, y, j);
}
/**
***************************************************************
TOOLS / Récupérer les tuiles
****************************************************************
*/
ALLEGRO_BITMAP*
recup_sprite
(
ALLEGRO_BITMAP*
scr, // bitmap d'origine
int
tx, int
ty, // taille élément
int
startx, int
starty, // à partir de
int
colonne, // nombre de colonnes
int
i) // ieme élément
{
ALLEGRO_BITMAP*
sprite =
NULL
;
int
x, y;
sprite =
al_create_bitmap
(
tx, ty);
if
(
sprite !=
NULL
){
// attention colonne doit être > 0
x =
startx +
(
i%
colonne)*
tx;
y =
starty +
(
i /
colonne)*
ty;
al_set_target_bitmap
(
sprite);
al_draw_bitmap_region
(
scr, x, y, tx, ty, 0
, 0
, 0
);
}
return
sprite;
}
/**
***************************************************************
TOOLS / Initialisations allegro
****************************************************************
*/
ALLEGRO_DISPLAY*
allegro_init
(
ALLEGRO_EVENT_QUEUE**
queue,
ALLEGRO_TIMER**
timer)
{
ALLEGRO_DISPLAY*
display;
if
(!
al_init
(
))
erreur
(
"
al_init()
"
);
if
(!
al_init_primitives_addon
(
))
erreur
(
"
al_init_primitives_addon()
"
);
if
(!
al_install_keyboard
(
))
erreur
(
"
al_install_keyboard()
"
);
if
(!
al_init_image_addon
(
))
erreur
(
"
al_init_image_addon()
"
);
display =
al_create_display
(
SCREENX, SCREENY);
if
(!
display)
erreur
(
"
al_create_display()
"
);
*
queue =
al_create_event_queue
(
);
if
(!*
queue)
erreur
(
"
al_create_event_queue()
"
);
*
timer =
al_create_timer
(
1
.0
/
30
);
if
(!*
timer)
erreur
(
"
al_create_timer()
"
);
// enregistrement événements
al_register_event_source
(*
queue,
al_get_display_event_source
(
display));
al_register_event_source
(*
queue,
al_get_keyboard_event_source
(
));
al_register_event_source
(*
queue,
al_get_timer_event_source
(*
timer));
// démarrer le timer
al_start_timer
(*
timer);
return
display;
}
/**
***************************************************************
TOOLS / contrôle d'erreur
****************************************************************
*/
void
erreur
(
const
char
*
txt)
{
ALLEGRO_DISPLAY*
d;
d =
al_is_system_installed
(
) ? al_get_current_display
(
) : NULL
;
al_show_native_message_box
(
d, "
ERREUR
"
, txt, NULL
, NULL
, 0
);
exit
(
EXIT_FAILURE);
}
/**
***************************************************************
****************************************************************
*/
XIII-B-4-a. Capture d'écran : ▲
XIII-B-5. Problèmes posés▲
Certes, lorsque le programme tourne, les clones qui entrent en collision cherchent une nouvelle direction jusqu'à en trouver une et la prennent. Ça marche bien. Mais il y a deux problèmes. Le premier est qu'à chaque changement de direction, le clone change aussi d'animation et de position (vers gauche, droit, haut, bas). Pour autant la position n'est pas forcément libre. Il reste alors sur place et recommence le tour d'après. De sorte que parfois un personnage gigote sur place en passant d'une direction à une autre tous les 1/30e de seconde jusqu'à ce qu'il puisse se dégager. C'est amusant mais ce n'est pas un effet recherché. Il faudrait que le personnage s'arrête tant qu'il cherche une direction et qu'aucune direction ne lui permet d'avancer.
Deuxième problème, les personnages continuent souvent, mais pas toujours, de se superposer et toutes les collisions ne sont pas détectées. Le clone est aveugle sur les clones qui sont juste au-dessus de lui ou juste à sa gauche. Cela provient des coordonnées qui correspondent au coin haut-gauche de l'image. Voici ce qui se passe :
Les clones sont affichés à l'écran en coordonnées pixel et ils se déplacent de 4 pixels en 4 pixels. Ils sont donc très souvent en porte-à-faux sur plusieurs tuiles du terrain, chaque tuile du terrain mesurant 32 pixels. Cependant, tant que le coin haut-gauche du clone est sur la même tuile, il appartient toujours à la même case sur la carte une fois faite la conversion des coordonnées pixel en coordonnées carte. Par exemple sur le schéma, le clone gris est placé en 1 vertical et 1 horizontal, le clone de gauche est en 1 vertical, 0 horizontal et le clone en haut est en 0 vertical, 1 horizontal.
Lorsque le clone en gris avance, il teste les cases correspondant à ses quatre coins et sur le schéma, nous voyons bien qu'il ne peut voir ni le clone au-dessus de lui ni celui à sa gauche, aucun de ses coins ne permet de les détecter. Régler ce problème mérite un peu de réflexion.
Nous allons corriger ces deux problèmes. Premièrement, nous allons implémenter l'immobilité des clones lorsque le déplacement est impossible et comment interdire les superpositions des personnages.
XIII-C. Automatisation du comportement des clones coincés▲
Nous partons encore du programme précédent. Il met en scène le mouvement des clones et du joueur et il est doté de la détection des collisions entre personnages. Notre objectif est de stopper un clone lorsqu'il ne peut pas avancer.
Le joueur reste comme avant toujours en mouvement même s'il n'avance pas. En fait, c'est mieux visuellement et cela permet de le distinguer des autres clones.
XIII-C-1. Modification de la structure de données▲
Le principe est simple : ajouter un indicateur de mobilité dans la structure personnage :
typedef
struct
{
int
x, y;
int
pas;
int
tx, ty;
int
mobile;
// indicateur de mobilité
int
tour, nbtour;
int
nbimage;
int
imcourante;
int
dir;
}
t_personnage;
S'il est sur 1 le personnage avance, s'il est sur 0 le personnage est à l'arrêt. Par défaut, au départ chaque personnage est mobile. L'initialisation se fait dans le constructeur :
t_personnage*
construct_personnage
(
)
{
(
...)
j->
mobile=
1
;
return
j;
}
Par ailleurs il faut une animation spécifique lorsque le personnage est à l'arrêt. Pour ce faire, la matrice des animations a besoin d'une direction supplémentaire, c'est la direction STOP. Ainsi nous avons :
enum
{
LEFT, RIGHT, UP, DOWN, STOP }
;
ALLEGRO_BITMAP*
ANIM[STOP+
1
][4
];
NBDIR est remplacé par STOP et un élément est ajouté à la première dimension du tableau : STOP + 1. Ainsi la constante STOP permettra d'accéder aux images du stop.
Par ailleurs il faut remplacer NBDIR par STOP dans tout le programme.
L'animation est constituée de quatre fois la même image :
Elle est chargée comme les autres dans la fonction :
void
recup_anim_personnage
(
)
{
int
dir, i;
char
nom[256
];
for
(
dir =
0
; dir <=
STOP; dir++
){
// attention <=
for
(
i =
0
; i <
4
; i++
){
sprintf
(
nom, "
.
\\
images
\\
joueur
\\
hero_%d_%d.bmp
"
,dir, i);
ANIM[dir][i] =
al_load_bitmap
(
nom);
if
(!
ANIM[dir][i])
erreur
(
"
ANIM[dir][i] = al_load_bitmap(nom)
"
);
al_convert_mask_to_alpha
(
ANIM[dir][i],
al_get_pixel
(
ANIM[dir][i], 0
, 0
));
}
}
}
Attention à bien prendre en compte la valeur STOP avec inférieur ou égal, c'est l'indice de l'animation stop dans le tableau.
Attention également à ce que les fichiers d'images soient bien dans le dossier joueur lui-même dans le dossier images. Ils doivent se nommer :
hero_4_0.bmp
hero_4_1.bmp
hero_4_2.bmp
hero_4_3.bmp
XIII-C-2. Gestion du mouvement▲
La mobilité est déjà déterminée dans le programme par la fonction
int
bouge_personnage
(
int
x, int
y, t_personnage*
j) ;
qui retourne 1 en cas de mouvement et 0 sinon. Il suffit de récupérer ce retour dans le champ mobile de chaque personnage au moment de l'appel de la fonction, dans la fonction avance_clone :
void
avance_clone
(
t_personnage*
j)
{
(
...)
// si immobile chercher une direction
j->
mobile =
bouge_personnage
(
x, y, j);
if
(!
j->
mobile )
direction_clone
(
j);
}
Ensuite, comme précédemment, s'il n'y a pas de mouvement, le clone cherche une direction.
XIII-C-3. Gestion de l'affichage▲
Au moment de l'affichage, le clone sait s'il est mobile ou non. S'il ne l'est pas, c'est l'animation du stop qui est prise :
void
affiche_personnage
(
t_personnage*
j)
{
int
dir;
// l'animation est permanente
j->
tour++
;
if
(
j->
tour ==
j->
nbtour){
j->
imcourante =
(++
j->
imcourante) %
j->
nbimage;
j->
tour =
0
;
}
// affichage du joueur mobile ou immobile
dir =
(
j->
mobile) ? j->
dir : STOP;
al_draw_scaled_bitmap
(
ANIM[dir][j->
imcourante],
0
, 0
, 64
, 64
,
j->
x, j->
y,
j->
tx, j->
ty,
0
);
}
Et c'est fini. Lorsque les personnages cherchent une direction, ils adoptent la position STOP.
Visuellement il y a un impact. Les clones paraissent mieux organisés. L'action est plus claire, plus calme.
XIII-C-3-a. Capture d'écran▲
Bien entendu, le problème des superpositions entre personnages demeure.
XIII-D. Suppression de superpositions entre personnages▲
XIII-D-1. Principe d'une échelle de la cartographie▲
Pour éradiquer les superpositions, il y a plusieurs solutions. Par exemple centrer les coordonnées des personnages de sorte que la position (x,y) à l'écran du personnage corresponde au centre de l'image du personnage et non plus au coin haut gauche. Cela semble une piste intéressante mais ce n'est pas la solution que nous avons retenue.
Nous avons constaté qu'il y avait comme un rapport d'échelle entre la carte de terrain et le terrain en continu c'est-à-dire en pixels à l'écran. Puisque chaque case de la carte terrain vaut 32 par 32 pixels, le rapport d'échelle est en quelque sorte de 1/32 : une case de la carte correspond à un carreau de 32 par 32 pixels dans le décor. Dans ce programme, le décor a toujours la taille de l'écran et ses dimensions s'alignent sur celui-ci :
#define SCREENX 640
#define SCREENY 480
#define PIXMAPX SCREENX // dimensions en pixels du décor
#define PIXMAPY SCREENY
La taille d'une tuile est 32 par 32 pixels définie de la façon suivante :
#define PIXTILEX 32
#define PIXTILEY 32
Nous en déduisons les dimensions de la carte du décor :
#define MAPTX (PIXMAPX/PIXTILEX) // 20
#define MAPTY (PIXMAPY/PIXTILEY) // 15
et dans cette carte une position correspond à un carré de 32 par 32 pixels dans le décor. Il y a 20 fois 15 positions, soit 300 unités.
Si l'on veut que dans la carte une unité corresponde à un carré plus petit, il suffit de diminuer la taille de tuiles.
Des tuiles de 16 par 16 donnent 40*30 = 1200 positions. Deux fois plus en largeur et deux fois plus en hauteur, soit quatre fois plus au total.
Des tuiles de 8 par 8 donnent 80*60 = 4800 positions, même principe.
Des tuiles de 4 par 4 donnent 160*120 = 19200 positions.
Des tuiles de 2 par 2 donnent 320*240 = 76800 positions.
Des tuiles au pixel près de 1 par 1, c'est-à-dire un monde continu, donnent 640*480 = 307200 positions.
Plus il y a de positions dans la carte plus celle-ci est précise, et l'échelle est donnée par la taille des tuiles.
Dans notre programme, le pas d'avancement des personnages est de 4. Il est initialisé dans la fonction construct_personnage . L'échelle qui semble la plus adaptée est 1/4, avec pour la carte une position égale à une tuile de 4 par 4 pixels. La précision est alors la même que celle des déplacements.
Dans cette perspective notre carte des personnages est maintenant définie de la façon suivante :
#define PTILEX 4
#define PTILEY 4
#define PMAPTX (PIXMAPX/PTILEX) // 160
#define PMAPTY (PIXMAPY/PTILEY) // 120
int
MAPPERSON[PMAPTY][PMAPTX] =
{}
;
Une tuile du décor correspond maintenant à 32/4 par 32/4 soit 8 par 8 positions dans la carte des personnages :
XIII-D-2. Principe de l'interception des collisions▲
Le périmètre de chaque personnage est incrusté dans la carte des personnages. Pour tester les interactions, il suffit de parcourir ce périmètre et de regarder dans la carte s'il croise l'incrustation d'un autre personnage. La détection d'un seul croisement suffit pour savoir qu'il y a collision.
XIII-D-3. Implémentation▲
XIII-D-3-a. Modifier la carte ▲
Le premier point est la modification de la carte des personnages, comme nous l'avons indiqué plus haut :
#define PTILEX 4
#define PTILEY 4
#define PMAPTX (PIXMAPX/PTILEX) // 160
#define PMAPTY (PIXMAPY/PTILEY) // 120
int
MAPPERSON[PMAPTY][PMAPTX] =
{}
;
XIII-D-3-b. Incruster, désincruster un personnage ▲
Ensuite, nous devons pouvoir incruster un personnage dans la carte, mais aussi l'effacer pour le bouger et le réincruster plus loin. À chaque fois, il faut parcourir le tour du personnage et imprimer à sa position dans la carte une valeur pour occupé, c'est l'incrustation, et une autre valeur pour libre, c'est l'effacement. Dans les deux cas, c'est le même traitement, il y a uniquement la valeur d'impression qui change. Pour avoir une seule fonction, cette valeur est donnée en paramètre.
Voici la fonction d'incrustation qui marque ou efface un personnage dans la carte des personnages :
void
marquer_personnage
(
t_personnage*
j, int
val)
{
int
l, r,t,b, i;
l =
j->
x /
PTILEX;
r =
l +
(
j->
tx/
PTILEX)-
1
;
t =
j->
y /
PTILEY;
b =
t +(
j->
ty/
PTILEY)-
1
;
// horizontales
for
(
i =
l; i <=
r; i++
){
MAPPERSON[t][i] =
val;
MAPPERSON[b][i] =
val;
}
// verticale
for
(
i =
t; i <=
b; i++
){
MAPPERSON[i][l] =
val;
MAPPERSON[i][r] =
val;
}
}
Elle reçoit en paramètre le personnage ainsi que la valeur à incruster. Ces valeurs sont définies par les deux macros constantes LIBRE et OCCUPE, OCCUPE pour incruster un personnage et LIBRE pour effacer un personnage. Néanmoins il serait possible de remplacer OCCUPE par un identifiant du personnage, cela permettrait à chaque personnage de savoir qui il rencontre en cas de collision.
Pour le parcours du tour, les deux coins haut gauche et bas droite du rectangle du personnage sont obtenus à partir de la position en pixels du personnage, de sa taille en pixels et de la dimension d'une tuile pour la carte des personnages. Ensuite le tracé consiste à parcourir les deux horizontales et les deux verticales.
Cette fonction est appelée une première fois lors de l'initialisation d'un personnage dans la fonction construct_personnage.
t_personnage*
construct_personnage
(
)
{
t_personnage*
j =
(
t_personnage*
)malloc
(
sizeof
(
t_personnage));
j->
tx =
PIXTILEX;
j->
ty =
PIXTILEY;
j->
pas =
4
;
// position (en coordonnées MAPDECOR)
j->
x =
rand
(
) %
MAPTX;
j->
y =
rand
(
) %
MAPTY;
// seules les positions LIBRE sont acceptables
// sinon chercher la plus proche
while
(
MAPDECOR[j->
y][j->
x] !=
LIBRE){
if
(
rand
(
) %
2
)
j->
x =
(
j->
x+
1
) %
MAPTX;
else
j->
y =
(
j->
y+
1
) %
MAPTY;
}
// marquer l'emplacement par le personnage
MAPDECOR[j->
y][j->
x] =
OCCUPE;
// convertir la position matrice en position écran
// (en coordonnées pixels)
j->
x *=
PIXTILEX;
j->
y *=
PIXTILEY;
marquer_personnage
(
j, OCCUPE);
// animation
j->
tour =
0
;
j->
nbtour =
5
;
j->
nbimage =
4
;
j->
imcourante =
0
;
j->
dir =
rand
(
) %
STOP;
// au départ les personnages sont en mouvement
// le joueur est toujours en mouvement.
j->
mobile =
1
;
return
j;
}
L'appel doit être placé après que la position du personnage soit convertie en pixels, la fonction en effet s'appuie sur la position en pixels du personnage.
Elle est également appelée dans la fonction bouge_personnage , au tout début pour effacer le personnage et à la fin pour le réincruster à son éventuelle nouvelle position.
XIII-D-3-c. Détection des collisions ▲
Mais il y a une modification plus importante dans la fonction de mouvement, précisément c'est la détection de collisions entre personnages. Le test consiste à parcourir tout le tour du personnage en regardant dans la carte s'il croise celui d'un autre personnage. La fonction prend en paramètre les coordonnées des coins haut-gauche et bas-droite du personnage. Elle retourne 1 si croisement et 0 sinon. Dès qu'un croisement est trouvé, il n'est pas utile de chercher plus et la fonction se termine (return 1) :
int
collision_personnage
(
int
l, int
t, int
r,int
b)
{
int
i;
for
(
i =
l; i <=
r ; i++
)
if
(
MAPPERSON[t][i] !=
LIBRE ||
MAPPERSON[b][i] !=
LIBRE)
return
1
;
for
(
i =
t; i <=
b; i++
)
if
(
MAPPERSON[i][l] !=
LIBRE ||
MAPPERSON[i][r] !=
LIBRE
return
1
;
return
0
;
}
Voici comment sont appelées les fonctions marquer_personnage et collision_personnage dans la fonction bouge_personnage :
int
bouge_personnage
(
int
x, int
y, t_personnage*
j)
{
int
l, r, t, b;
int
mobile =
0
; // par défaut immobile
// mise à LIBRE position actuelle dans la carte
// des personnages
marquer_personnage
(
j, LIBRE);
// si le personnage reste dans l'écran
if
(
x >=
0
&&
x +
j->
tx <=
PIXMAPX &&
y >=
0
&&
y +
j->
ty <=
PIXMAPY){
// left top right bottom
l =
x /
PIXTILEX;
t =
y /
PIXTILEX;
r =
(
x +
j->
tx -
1
) /
PIXTILEX;
b =
(
y +
j->
ty -
1
) /
PIXTILEY;
// et si les 4 coins du sprite sont tous sur LIBRE
if
(
MAPDECOR[t][l] ==
LIBRE &&
MAPDECOR[t][r] ==
LIBRE &&
MAPDECOR[b][l] ==
LIBRE &&
MAPDECOR[b][r] ==
LIBRE){
// left top right bottom
l =
x /
PTILEX;
t =
y /
PTILEX;
r =
l +
(
j->
tx/
PTILEX)-
1
;
b =
t +
(
j->
ty/
PTILEY)-
1
;
if
(!
collision_personnage
(
l, t, r, b)){
//avancer
j->
x =
x;
j->
y =
y;
mobile =
1
;
}
}
}
marquer_personnage
(
j, OCCUPE);
return
mobile;
}
XIII-D-3-d. Correction de l'initialisation des personnages ▲
Il reste un détail à traiter mais qui a son importance à propos cette fois de la carte du décor MAPDECOR. En effet, deux personnages peuvent être superposés à l'initialisation et ensuite se paralyser mutuellement. Il est impératif qu'il n'y ait aucune collision à l'initialisation, chaque personnage doit trouver une place libre dans la carte du décor.
Pour ce faire, à l'initialisation d'un personnage, il faut marquer comme OCCUPE dans la carte du décor la position LIBRE qu'il a obtenue, afin qu'aucun des suivants ne puisse l'obtenir.
Dans les programmes précédents, la carte du décor n'était pas modifiée par l'initialisation des personnages. Nous avions dans la fonction construct_decor un appel à la fonction mise_a_libre_ou_occupe qui transformait en LIBRE (0) l'herbe (3) et tout le reste en OCCUPE (-1).
Nous avons modifié cette fonction. Le fait de mettre à OCCUPE n'était pas très utile. Nous avons uniquement conservé la possibilité de mettre à LIBRE. Mais, en plus, nous pouvons préciser quelle valeur mettre à LIBRE. L'objectif est de pouvoir mettre à LIBRE tout ce qui est de l'herbe avec la valeur 3 et une fois que les personnages auront trouvé leur place dans le décor, de pouvoir mettre à LIBRE toutes les places occupées avec la valeur OCCUPE. Voici la fonction mise_a_libre :
void
mise_a_libre_decor
(
int
val)
{
int
y, x;
for
(
y =
0
; y <
MAPTY; y++
)
for
(
x =
0
; x <
MAPTX; x++
)
if
(
MAPDECOR[y][x] ==
val)
MAPDECOR[y][x] =
LIBRE;
}
Cette fonction doit être appelée avant l'initialisation des personnages pour une mise à libre de l'herbe et après, pour une mise à libre des places occupées :
mise_a_libre_decor
(
3
);
for
(
i =
0
; i <=
JOUEUR; i++
)
all[i] =
construct_personnage
(
);
mise_a_libre_decor
(
OCCUPE);
XIII-D-4. Quelques perfectionnements▲
L'implémentation de la carte au 1/4 est terminée mais dans le programme, par souci de lisibilité, nous avons encapsulé dans des fonctions tous les parcours du tableau des personnages qui ont lieu dans le main() . Par exemple au lieu d'avoir :
mise_a_libre_decor
(
3
);
for
(
i =
0
; i <=
JOUEUR; i++
)
all[i] =
construct_personnage
(
);
mise_a_libre_decor
(
OCCUPE);
nous avons :
construct_personnages
(
allP);
la fonction étant définie un peu plus loin :
void
construct_personnages
(
t_personnage*
all[])
{
int
i;
mise_a_libre_decor
(
3
);
for
(
i =
0
; i <=
JOUEUR; i++
)
all[i] =
construct_personnage
(
);
mise_a_libre_decor
(
OCCUPE);
}
Le suffixe _personnages au pluriel dans le nom d'une fonction signifie à chaque fois que la fonction traite l'ensemble des personnages.
XIII-D-5. Code complet commenté▲
// pour utilisation de la fonction sprintf
// jugée unsafe par microsoft
#define _CRT_SECURE_NO_WARNINGS
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <allegro5/allegro_primitives.h>
#include <allegro5/allegro_image.h>
#include <stdio.h>
#include <time.h>
#define SCREENX 640
#define SCREENY 480
#define BLANC al_map_rgb(255,255,255)
#define GRIS al_map_rgb(128,128,128)
#define NOIR al_map_rgb(0,0,0)
// TUILES ET MAPDECOR
// largeur et hauteur des carreaux en pixels
#define PIXTILEX 32
#define PIXTILEY 32
// taille en pixels de la MAPDECOR
#define PIXMAPX SCREENX
#define PIXMAPY SCREENY
// taille en tuiles de la MAPDECOR
#define MAPTX (PIXMAPX /PIXTILEX) // 20
#define MAPTY (PIXMAPY/PIXTILEY) // 15
// identification pour collisions
#define LIBRE 0
#define OCCUPE -1
// la carte des tuiles du terrain
short
MAPDECOR[MAPTY][MAPTX] =
{
{
3
, 3
, 3
, 7
,7
,7
,55
,8
,8
,2
,3
,3
,3
,3
,3
,3
,3
, 3
, 3
,101
}
,
{
3
, 3
, 3
, 3
,3
,3
,55
,1
,1
,3
,3
,3
,3
,3
,3
,3
,3
,101
,101
,101
}
,
{
3
, 3
, 3
, 3
,3
,3
, 3
,2
,1
,3
,3
,3
,2
,3
,3
,3
,3
, 3
,101
, 3
}
,
{
3
, 3
, 1
, 3
,3
,3
, 3
,3
,3
,3
,3
,2
,2
,3
,3
,3
,2
,101
,101
, 3
}
,
{
3
, 1
, 1
, 3
,3
,3
, 3
,3
,3
,3
,2
,2
,3
,3
,3
,3
,2
, 3
, 3
, 3
}
,
{
3
, 1
, 1
, 1
,3
,3
, 3
,3
,3
,8
,2
,2
,2
,3
,3
,1
,7
, 1
, 3
, 3
}
,
{
3
, 3
, 3
, 1
,1
,3
, 3
,3
,3
,3
,1
,3
,3
,3
,3
,3
,3
, 3
, 3
, 3
}
,
{
3
, 3
, 3
, 1
,3
,3
, 3
,3
,1
,3
,3
,3
,3
,3
,3
,3
,3
, 3
, 3
, 3
}
,
{
3
, 3
, 3
, 3
,3
,3
, 3
,2
,1
,3
,3
,3
,3
,3
,3
,3
,3
, 3
, 3
, 3
}
,
{
4
, 4
, 3
, 3
,3
,3
, 3
,2
,1
,3
,3
,6
,6
,3
,3
,3
,3
, 3
, 4
, 4
}
,
{
4
, 4
, 4
, 3
,3
,3
, 2
,2
,3
,3
,3
,4
,4
,3
,3
,3
,3
, 81
, 81
, 2
}
,
{
4
, 4
, 3
, 3
,3
,3
, 2
,1
,3
,3
,3
,4
,1
,3
,3
,3
,4
, 4
, 81
, 81
}
,
{
4
, 4
, 4
, 3
,3
,3
, 3
,1
,3
,3
,3
,3
,2
,3
,3
,4
,4
, 4
, 81
, 81
}
,
{
81
,81
, 4
, 3
,3
,3
, 3
,1
,1
,1
,3
,3
,3
,3
,4
,4
,4
, 2
, 4
, 81
}
,
{
81
,81
,81
,81
,3
,3
, 3
,3
,3
,3
,3
,3
,3
,4
,4
,4
,2
, 2
, 81
, 81
}
}
;
// PERSONNAGE
#define JOUEUR 50
// ensemble des variables qui identifient un personnage
typedef
struct
{
int
x, y; // position
int
pas; // déplacement
int
tx, ty;// taille
//animation
int
mobile;
int
tour, nbtour;
int
nbimage;
int
imcourante;
int
dir;
}
t_personnage;
// 4 directions
enum
{
LEFT, RIGHT, UP, DOWN, STOP }
;
// pour clavier fluide
int
KEY[STOP] =
{}
;
// tableau des animations :
//4 directions + stop, 4 images pour chaque
ALLEGRO_BITMAP*
ANIM[STOP+
1
][4
];
// Carte de contrôle de collisions
#define PTILEX 4
#define PTILEY 4
#define PMAPTX (PIXMAPX/PTILEX) // 160
#define PMAPTY (PIXMAPY/PTILEY) // 120
int
MAPPERSON[PMAPTY][PMAPTX] =
{}
;
ALLEGRO_BITMAP*
construct_decor (
void
);
void
mise_a_libre (
int
val);
void
construct_personnages (
t_personnage*
all[]);
t_personnage*
construct_personnage (
void
);
void
marquer_personnage (
t_personnage*
j, int
val);
void
recup_anim_personnage (
void
);
int
bouge_personnage (
int
x, int
y,
t_personnage*
j);
int
collision_personnage (
int
l, int
t, int
r, int
b);
void
affiche_personnages (
t_personnage*
all[]);
void
affiche_personnage (
t_personnage*
j);
void
affiche_map_personnage (
void
);
void
liberer_personnages
(
t_personnage*
all[]);
void
avance_clones
(
t_personnage*
all[]);
void
avance_clone (
t_personnage*
j);
void
direction_clone (
t_personnage*
j);
void
controle_joueur (
ALLEGRO_EVENT*
e,
t_personnage*
j);
void
avance_joueur (
t_personnage*
j);
ALLEGRO_BITMAP*
recup_sprite (
ALLEGRO_BITMAP*
scr,
int
tx, int
ty,
int
startx, int
starty,
int
colonne, int
i);
ALLEGRO_DISPLAY*
allegro_init (
ALLEGRO_EVENT_QUEUE**
queue,
ALLEGRO_TIMER**
timer);
void
erreur (
const
char
*
msg);
/**
***************************************************************
****************************************************************
*/
int
main
(
)
{
ALLEGRO_DISPLAY*
display;
ALLEGRO_EVENT_QUEUE*
queue;
ALLEGRO_TIMER*
timer;
ALLEGRO_BITMAP*
decor;
// l'ensemble des personnages avec le joueur
// placé en dernier
t_personnage*
allP[JOUEUR+
1
];
bool fin =
0
;
bool dessine =
true
;
int
i;
display =
allegro_init
(&
queue, &
timer);
decor =
construct_decor
(
);
recup_anim_personnage
(
);
// revenir à l'affichage écran
al_set_target_backbuffer
(
display);
// création des personnages
construct_personnages
(
allP);
while
(!
fin){
ALLEGRO_EVENT event;
al_wait_for_event
(
queue, &
event);
controle_joueur
(&
event, allP[JOUEUR]);
if
(
event.type ==
ALLEGRO_EVENT_DISPLAY_CLOSE ||
event.keyboard.keycode ==
ALLEGRO_KEY_ESCAPE)
fin =
true
;
else
if
(
event.type ==
ALLEGRO_EVENT_TIMER){
// mouvement
avance_joueur
(
allP[JOUEUR]);
avance_clones
(
allP);
dessine =
true
;
}
if
(
dessine ==
true
&&
al_is_event_queue_empty
(
queue)){
al_draw_bitmap
(
decor, 0
, 0
, 0
);
affiche_personnages
(
allP);
//affiche_map_personnage();
al_flip_display
(
);
dessine =
false
;
}
}
liberer_personnages
(
allP);
al_destroy_bitmap
(
decor);
al_destroy_display
(
display);
al_destroy_timer
(
timer);
al_destroy_event_queue
(
queue);
return
0
;
}
/**
**************************************************************
DECOR / Création de la bitmap du decor
***************************************************************
*/
ALLEGRO_BITMAP*
construct_decor
(
)
{
ALLEGRO_BITMAP*
decor, *
bmp;
ALLEGRO_BITMAP*
tuiles;
int
mapx, mapy, posx, posy;
// bitmap decor et load tiles
decor =
al_create_bitmap
(
PIXMAPX, PIXMAPY);
tuiles =
al_load_bitmap
(
"
.
\\
images
\\
kit_de_tuiles_2.png
"
);
if
(!
decor ||
!
tuiles)
erreur
(
"
decor, tuiles
"
);
// construction du decor
for
(
mapy =
0
; mapy<
MAPTY; mapy++
)
for
(
mapx =
0
; mapx<
MAPTX; mapx++
){
bmp =
recup_sprite
(
tuiles, // fichier
PIXTILEX, PIXTILEY,// taille tuiles
0
, 0
, // pos départ
20
, // nombre de colonnes
MAPDECOR[mapy][mapx]);// N° de tuile
if
(!
bmp)
erreur
(
"
recup_sprite()
"
);
posx =
mapx*
PIXTILEX;
posy =
mapy*
PIXTILEY;
al_set_target_bitmap
(
decor);
al_draw_bitmap
(
bmp, posx, posy, 0
);
al_draw_rectangle
(
posx, posy,
posx+
PIXTILEX, posy+
PIXTILEY,
GRIS, 1
);
al_destroy_bitmap
(
bmp);
}
// libérer mémoire image inutile
al_destroy_bitmap
(
tuiles);
// retour adresse du décor
return
decor;
}
/**
**************************************************************
DECOR / met à LIBRE dans la map DECOR les positions de valeur val
***************************************************************
*/
void
mise_a_libre_decor
(
int
val)
{
int
y, x;
for
(
y =
0
; y <
MAPTY; y++
)
for
(
x =
0
; x <
MAPTX; x++
)
if
(
MAPDECOR[y][x] ==
val)
MAPDECOR[y][x] =
LIBRE;
}
/**
***************************************************************
PERSONNAGES / création
****************************************************************
*/
void
construct_personnages
(
t_personnage*
all[])
{
int
i;
mise_a_libre_decor
(
3
);
for
(
i =
0
; i <=
JOUEUR; i++
)
all[i] =
construct_personnage
(
);
mise_a_libre_decor
(
OCCUPE);
}
t_personnage*
construct_personnage
(
)
{
t_personnage*
j =
(
t_personnage*
)malloc
(
sizeof
(
t_personnage));
j->
tx =
PIXTILEX;
j->
ty =
PIXTILEY;
j->
pas =
4
;
// position (en coordonnées MAPDECOR)
j->
x =
rand
(
) %
MAPTX;
j->
y =
rand
(
) %
MAPTY;
// seules les positions LIBRE sont acceptables
// si non chercher la plus proche
while
(
MAPDECOR[j->
y][j->
x] !=
LIBRE){
if
(
rand
(
) %
2
)
j->
x =
(
j->
x+
1
) %
MAPTX;
else
j->
y =
(
j->
y+
1
) %
MAPTY;
}
// marquer l'emplacement par le personnage
MAPDECOR[j->
y][j->
x] =
OCCUPE;
// convertir la position matrice en position écran
// (en coordonnées pixels)
j->
x *=
PIXTILEX;
j->
y *=
PIXTILEY;
marquer_personnage
(
j, OCCUPE);
// animation
j->
tour =
0
;
j->
nbtour =
5
;
j->
nbimage =
4
;
j->
imcourante =
0
;
j->
dir =
rand
(
) %
STOP;
// au départ les personnages sont en mouvement
// le joueur est toujours en mouvement.
j->
mobile =
1
;
return
j;
}
/**
***************************************************************
PERSONNAGES / Affichage
****************************************************************
*/
void
marquer_personnage
(
t_personnage*
j, int
val)
{
int
l, r,t,b, i;
l =
j->
x /
PTILEX;
r =
l +
(
j->
tx/
PTILEX)-
1
;
t =
j->
y /
PTILEY;
b =
t +(
j->
ty/
PTILEY)-
1
;
// horizontales
for
(
i =
l; i <=
r; i++
){
MAPPERSON[t][i] =
val;
MAPPERSON[b][i] =
val;
}
// verticale
for
(
i =
t; i <=
b; i++
){
MAPPERSON[i][l] =
val;
MAPPERSON[i][r] =
val;
}
}
/**
***************************************************************
PERSONNAGES / Affichage
****************************************************************
*/
void
affiche_personnages
(
t_personnage*
all[])
{
int
i;
for
(
i =
0
; i <=
JOUEUR;i++
)
affiche_personnage
(
all[i]);
}
void
affiche_personnage
(
t_personnage*
j)
{
int
dir;
// l'animation est permanente
j->
tour++
;
if
(
j->
tour ==
j->
nbtour){
j->
imcourante =
(++
j->
imcourante) %
j->
nbimage;
j->
tour =
0
;
}
// affichage du joueur
dir =
(
j->
mobile) ? j->
dir : STOP;
al_draw_scaled_bitmap
(
ANIM[dir][j->
imcourante],
0
, 0
, 64
, 64
, // source
j->
x, j->
y, // cible
j->
tx, j->
ty,
0
);
}
/**
***************************************************************
PERSONNAGES / Affichage rectangle de contrôle
*****************************************************************
void affiche_map_personnage()
{
int x, y;
ALLEGRO_COLOR c;
for (y = 0; y < PMAPTY;y++)
for (x = 0; x < PMAPTX; x++){
c = (MAPPERSON[y][x] == LIBRE) ? BLANC : NOIR;
al_draw_filled_rectangle(x*PTILEX, y*PTILEY,
x*PTILEX + PTILEX,
y*PTILEY + PTILEY,
c);
}
}
/*****************************************************************
PERSONNAGES / récupération des 4 animations communes à tous
****************************************************************
*/
void
recup_anim_personnage
(
)
{
int
dir, i;
char
nom[256
];
for
(
dir =
0
; dir <=
STOP; dir++
){
// attention <=
for
(
i =
0
; i <
4
; i++
){
sprintf
(
nom, "
.
\\
images
\\
joueur
\\
hero_%d_%d.bmp
"
, dir, i);
ANIM[dir][i] =
al_load_bitmap
(
nom);
if
(!
ANIM[dir][i])
erreur
(
"
ANIM[dir][i] = al_load_bitmap(nom)
"
);
al_convert_mask_to_alpha
(
ANIM[dir][i],
al_get_pixel
(
ANIM[dir][i], 0
, 0
));
}
}
}
/**
***************************************************************
PERSONNAGES / controle du déplacement
****************************************************************
*/
int
bouge_personnage
(
int
x, int
y, t_personnage*
j)
{
int
l, r, t, b;
int
mobile =
0
; // par défaut immobile
// mise à LIBRE position actuelle dans la carte
// des personnages
marquer_personnage
(
j, LIBRE);
// si le personnage reste dans l'écran
if
(
x >=
0
&&
x +
j->
tx <=
PIXMAPX &&
y >=
0
&&
y +
j->
ty <=
PIXMAPY){
// left top right bottom
l =
x /
PIXTILEX;
t =
y /
PIXTILEX;
r =
(
x +
j->
tx -
1
) /
PIXTILEX;
b =
(
y +
j->
ty -
1
) /
PIXTILEY;
// et si les 4 coins du sprite sont tous sur LIBRE
if
(
MAPDECOR[t][l] ==
LIBRE &&
MAPDECOR[t][r] ==
LIBRE &&
MAPDECOR[b][l] ==
LIBRE &&
MAPDECOR[b][r] ==
LIBRE){
// left top right bottom
l =
x /
PTILEX;
t =
y /
PTILEX;
r =
l +
(
j->
tx/
PTILEX)-
1
;
b =
t +
(
j->
ty/
PTILEY)-
1
;
if
(!
collision_personnage
(
l, t, r, b)){
//avancer
j->
x =
x;
j->
y =
y;
mobile =
1
;
}
}
}
marquer_personnage
(
j, OCCUPE);
return
mobile;
}
/**
***************************************************************
PERSONNAGES / collisions
****************************************************************
*/
int
collision_personnage
(
int
l, int
t, int
r,int
b)
{
int
i;
for
(
i =
l; i <=
r ; i++
)
if
(
MAPPERSON[t][i] !=
LIBRE ||
MAPPERSON[b][i] !=
LIBRE)
return
1
;
for
(
i =
t; i <=
b; i++
)
if
(
MAPPERSON[i][l] !=
LIBRE ||
MAPPERSON[i][r] !=
LIBRE)
return
1
;
return
0
;
}
/**
***************************************************************
PERSONNAGES / contrôle d'erreurlibérer
****************************************************************
*/
void
liberer_personnages
(
t_personnage*
all[])
{
int
i;
for
(
i =
0
; i <=
JOUEUR; i++
)
free
(
all[i]);
}
/**
***************************************************************
CLONES / déplacement automatique
****************************************************************
*/
void
avance_clones
(
t_personnage*
all[])
{
int
i;
for
(
i =
0
; i <
JOUEUR; i++
)
avance_clone
(
all[i]);
}
void
avance_clone
(
t_personnage*
j)
{
int
x, y;
// copie position et taille
y =
j->
y;
x =
j->
x;
// avance selon direction
switch
(
j->
dir){
case
LEFT: x -=
j->
pas; break
;
case
RIGHT: x +=
j->
pas; break
;
case
UP: y -=
j->
pas; break
;
case
DOWN: y +=
j->
pas; break
;
}
// si immobile chercher une direction
j->
mobile=
bouge_personnage
(
x, y, j);
if
(!
j->
mobile )
direction_clone
(
j);
}
/**
***************************************************************
CLONES / choix direction
Le principe est :
- de tirer une direction aléatoire
- si impossible (cause bords, terrain)
- prendre la suivante
****************************************************************
*/
void
direction_clone
(
t_personnage*
j)
{
int
sens =
(
rand
(
) %
2
) *
2
-
1
; //-1 ou 1
int
dir =
rand
(
) %
STOP;
int
res =
-
1
;
// passer en coordonnées MAPDECOR
int
x =
j->
x /
PIXTILEX;
int
y =
j->
y /
PIXTILEX;
// vérifier la direction, si impossible prendre la
// suivante en tournant selon "sens" (vers gauche ou droite)
do
{
dir =
((
dir +
sens) +
STOP) %
STOP;
switch
(
dir){
case
LEFT:
if
(
x -
1
>=
0
&&
MAPDECOR[y][x -
1
] ==
LIBRE)
res =
LEFT;
break
;
case
RIGHT:
if
(
x +
1
<
MAPTX &&
MAPDECOR[y][x +
1
] ==
LIBRE)
res =
RIGHT;
break
;
case
UP:
if
(
y -
1
>=
0
&&
MAPDECOR[y -
1
][x] ==
LIBRE)
res =
UP;
break
;
case
DOWN:
if
(
y +
1
<
MAPTY &&
MAPDECOR[y +
1
][x] ==
LIBRE)
res =
DOWN;
break
;
}
}
while
(
res <
0
);
j->
dir =
res;
}
/**
***************************************************************
JOUEUR / contrôle clavier du joueur
****************************************************************
*/
void
controle_joueur
(
ALLEGRO_EVENT*
e, t_personnage*
j)
{
if
(
e->
type ==
ALLEGRO_EVENT_KEY_DOWN){
switch
(
e->
keyboard.keycode){
case
ALLEGRO_KEY_LEFT: //82
case
ALLEGRO_KEY_RIGHT: //83
case
ALLEGRO_KEY_UP: //84
case
ALLEGRO_KEY_DOWN: //85
j->
dir =
e->
keyboard.keycode -
ALLEGRO_KEY_LEFT;
KEY[j->
dir] =
1
;
break
;
}
}
else
if
(
e->
type ==
ALLEGRO_EVENT_KEY_UP){
switch
(
e->
keyboard.keycode){
case
ALLEGRO_KEY_LEFT:
case
ALLEGRO_KEY_RIGHT:
case
ALLEGRO_KEY_UP:
case
ALLEGRO_KEY_DOWN:
j->
dir =
e->
keyboard.keycode -
ALLEGRO_KEY_LEFT;
KEY[j->
dir] =
0
;
break
;
}
}
}
/**
***************************************************************
JOUEUR / déplacement
****************************************************************
*/
void
avance_joueur
(
t_personnage*
j)
{
int
x, y;
// avancer
y =
j->
y;
x =
j->
x;
x -=
KEY[LEFT] *
j->
pas;
x +=
KEY[RIGHT] *
j->
pas;
y -=
KEY[UP] *
j->
pas;
y +=
KEY[DOWN] *
j->
pas;
// le joueur est toujours en mouvement
bouge_personnage
(
x, y, j);
}
/**
***************************************************************
TOOLS / Récupérer les tuiles
****************************************************************
*/
ALLEGRO_BITMAP*
recup_sprite
(
ALLEGRO_BITMAP*
scr, // bitmap d'origine
int
tx, int
ty, // taille élément
int
startx, int
starty, // à partir de
int
colonne, // nombre de colonnes
int
i) // ieme élément
{
ALLEGRO_BITMAP*
sprite =
NULL
;
int
x, y;
sprite =
al_create_bitmap
(
tx, ty);
if
(
sprite !=
NULL
){
// attention colonne doit être > 0
x =
startx +
(
i%
colonne)*
tx;
y =
starty +
(
i /
colonne)*
ty;
al_set_target_bitmap
(
sprite);
al_draw_bitmap_region
(
scr, x, y, tx, ty, 0
, 0
, 0
);
}
return
sprite;
}
/**
***************************************************************
TOOLS / Initialisations allegro
****************************************************************
*/
ALLEGRO_DISPLAY*
allegro_init
(
ALLEGRO_EVENT_QUEUE**
queue,
ALLEGRO_TIMER**
timer)
{
ALLEGRO_DISPLAY*
display;
srand
(
time
(
NULL
));
if
(!
al_init
(
))
erreur
(
"
al_init()
"
);
if
(!
al_init_primitives_addon
(
))
erreur
(
"
al_init_primitives_addon()
"
);
if
(!
al_install_keyboard
(
))
erreur
(
"
al_install_keyboard()
"
);
if
(!
al_init_image_addon
(
))
erreur
(
"
al_init_image_addon()
"
);
//al_set_new_display_flags(ALLEGRO_FULLSCREEN);
display =
al_create_display
(
SCREENX, SCREENY);
if
(!
display)
erreur
(
"
al_create_display()
"
);
*
queue =
al_create_event_queue
(
);
if
(!*
queue)
erreur
(
"
al_create_event_queue()
"
);
*
timer =
al_create_timer
(
1
.0
/
30
);
if
(!*
timer)
erreur
(
"
al_create_timer()
"
);
// enregistrement événements
al_register_event_source
(*
queue,
al_get_display_event_source
(
display));
al_register_event_source
(*
queue,
al_get_keyboard_event_source
(
));
al_register_event_source
(*
queue,
al_get_timer_event_source
(*
timer));
// démarrer le timer
al_start_timer
(*
timer);
return
display;
}
/**
***************************************************************
TOOLS / contrôle d'erreur
****************************************************************
*/
void
erreur
(
const
char
*
txt)
{
ALLEGRO_DISPLAY*
d;
d =
al_is_system_installed
(
) ? al_get_current_display
(
) : NULL
;
al_show_native_message_box
(
d, "
ERREUR
"
, txt, NULL
, NULL
, 0
);
exit
(
EXIT_FAILURE);
}
/**
***************************************************************
****************************************************************
*/
XIII-D-5-a. Capture d'écran▲
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. |