C. Couche haute : l'affichage SDL▲
Vous pouvez déjà trouver pas mal de documentation sur SDL sur Internet : sur le site officiel libsdl.orghttp://www.libsdl.org ou bien ici même sur developpez.comhttp://www.developpez.com.
Pour réaliser une couche haute pour l'affichage, il nous faut implémenter les fonctions virtuelles de la classe interface Gfx.
class
SDLinterface : public
Gfx{
public
:
SDLinterface();
int
choose_client_server();
int
positionner_bateaux(Flotte&
);
std::
pair<
int
,int
>
choisir_attaque(Essais, Flotte);
void
update(Essais, Flotte);
void
affiche_gagne();
void
affiche_perdu();
virtual
~
SDLinterface();
private
:
void
dessiner_2_grilles(Essais, Flotte);
// tous les sprites
SDL_Surface *
screen;
SDL_Surface *
creer, *
rejoindre, *
quitter, *
fond1, *
placez, *
instructions, *
fond_grille, *
bateau, *
interface_sdl,
*
fond_jeu, *
petit_bateau, *
degats, *
plouf, *
a_vous, *
en_attente, *
gagne, *
perdu;
}
;
On remarque qu'il y a toutes les fonctions de Gfx, mais aussi des fonctions propres à l'interface SDL. On remarque aussi tous les attributs de type SDL_Surface * pour stocker tous les sprites que nous allons utiliser dans le jeu. Les sprites sont chargés dans le constructeur et sont détruits dans le destructeur.
C-I. Écran de choix Client/Serveur▲
Je me suis appuyé sur le tutoriel de fearyourself sur la réalisation de menus en SDL pour réaliser le premier écran du jeu : le choix entre le mode client et le mode serveur.
Pour résumer :
- on charge les sprites relatifs aux différents items du menu en spécifiant un indice de transparence ;
- on positionne les sprites ;
- on crée des rectangles aux dimensions et positions des sprites ;
- lors d'un clic on vérifie si on se situe dans l'un de ces rectangles pour déclencher une procédure.
int
SDLinterface::
choose_client_server(){
// on affiche un menu
SDL_BlitSurface(fond1, NULL
, screen, NULL
);
// on positionne les boutons
SDL_Rect r;
r.x =
160
; r.y =
100
; SDL_BlitSurface(creer, NULL
, screen, &
r);
SDL_Rect r0; r0.w =
creer->
w; r0.h=
creer->
h; r0.x=
r.x; r0.y=
r.y;
r.x =
97
; r.y =
200
; SDL_BlitSurface(rejoindre, NULL
, screen, &
r);
SDL_Rect r1; r1.w =
rejoindre->
w; r1.h=
rejoindre->
h; r1.x=
r.x; r1.y=
r.y;
r.x =
281
; r.y =
400
; SDL_BlitSurface(quitter, NULL
, screen, &
r);
SDL_Rect r2; r2.w =
quitter->
w; r2.h=
quitter->
h; r2.x=
r.x; r2.y=
r.y;
SDL_Flip(screen);
// on lance une boucle pour attraper un clic sur un bouton
bool
clic_correct =
false
;
int
numero_clique=-
1
;
SDL_Event event;
while
(!
clic_correct){
SDL_WaitEvent(&
event);
switch
(event.type){
case
SDL_QUIT:
clic_correct =
true
;
numero_clique =
2
;
break
;
case
SDL_MOUSEBUTTONUP:
if
(event.button.x >
r0.x &&
event.button.x <
r0.x+
r0.w &&
event.button.y >
r0.y &&
event.button.y <
r0.y+
r0.h){
numero_clique =
0
;
clic_correct =
true
;
}
else
if
(event.button.x >
r1.x &&
event.button.x <
r1.x+
r1.w &&
event.button.y >
r1.y &&
event.button.y <
r1.y+
r1.h){
numero_clique =
1
;
clic_correct =
true
;
}
else
if
(event.button.x >
r2.x &&
event.button.x <
r2.x+
r2.w &&
event.button.y >
r2.y &&
event.button.y <
r2.y+
r2.h){
numero_clique =
2
;
clic_correct =
true
;
}
break
;
}
SDL_Flip(screen);
}
// 1 = client
// 0 = serveur
// 2 = quitter
return
numero_clique;
}
Ce qui nous affiche :
C-II. Écran de placement des bateaux▲
L'étape suivante est d'implémenter la fonction permettant de placer les bateaux sur la grille. J'ai choisi une grille 11x11 (allez savoir pourquoi). Une pression sur la touche A réarrange les bateaux sur la grille et une pression sur Entrée valide le choix et passe à l'écran suivant. Je choisis d'afficher un sprite carré sur chaque position occupée par un bateau.
int
SDLinterface::
positionner_bateaux(Flotte&
f){
// on affiche la grille et le texte
SDL_BlitSurface(fond_grille, NULL
, screen, NULL
);
SDL_Rect r;
r.x =
150
; r.y =
1
; SDL_BlitSurface(placez, NULL
, screen, &
r);
r.x =
544
; r.y =
75
; SDL_BlitSurface(instructions, NULL
, screen, &
r);
SDL_Event event;
bool
clic_correct =
false
;
while
(!
clic_correct){
SDL_WaitEvent(&
event);
switch
(event.type){
case
SDL_QUIT:
clic_correct =
true
;
break
;
case
SDL_KEYDOWN:
switch
(event.key.keysym.sym){
case
SDLK_a:{
// on place aléatoirement les bateaux sur la grille
f.placer_aleatoirement();
// afficher le résultat du placement aléatoire
SDL_BlitSurface(fond_grille, NULL
, screen, NULL
);
r.x =
150
; r.y =
1
; SDL_BlitSurface(placez, NULL
, screen, &
r);
r.x =
544
; r.y =
75
; SDL_BlitSurface(instructions, NULL
, screen, &
r);
// et boucler pour afficher tous les petits carrés des bateaux
for
(int
i=
0
; i<
int
(f._lb.size()); i++
)
for
(int
j=
0
; j<
f._lb[i].get_taille(); j++
){
r.x =
(f._lb[i].is_horizontal()==
true
) ? f._lb[i].get_pos().first +
j : f._lb[i].get_pos().first;
r.y =
(f._lb[i].is_horizontal()==
false
) ? f._lb[i].get_pos().second +
j : f._lb[i].get_pos().second;
r.x*=
48
;
r.y*=
48
;
// la grille commence au point 7,63
r.x+=
7
;
r.y+=
63
;
SDL_BlitSurface(bateau, NULL
, screen, &
r);
}
break
;
}
case
SDLK_RETURN:
clic_correct =
true
;
break
;
default
:
break
;
}
break
;
}
SDL_Flip(screen);
}
return
0
;
}
On remarquera les opérations directement sur le placement des sprites sur l'image pour les câler au pixel près sur la grille. Chaque sprite fait 48 pixels de côté. Et en effet, les coordonnées stockées dans la couche de manipulation de données sont comprises entre 0 et 10, je dois les remettre à l'échelle pour les faire coller au sprite.
Ce qui nous donne :
C-III. Écran du jeu proprement dit▲
Cet écran permet au joueur de cliquer sur la case qu'il souhaite attaquer. Il permet aussi de visualiser d'un seul coup d'œil les endroits déjà attaqués, les coups au but, et les coups que l'adversaire a déjà faits.
On place à gauche les bateaux du joueur et à droite les bateaux de l'adversaire.
On se contente d'attendre un clic sur la grille de l'adversaire, de vérifier que la case cliquée n'a pas déjà été cliquée, puis on renvoie les coordonnées cliquées.
On remarquera que les grilles de jeu n'ont pas la même dimension que dans l'écran précédent, les opérations pour caler les sprites sur les pixels ne sont donc plus tout à fait les mêmes.
pair<
int
,int
>
SDLinterface::
choisir_attaque(Essais e, Flotte f){
// on attend un clic sur une case, pas encore attaquée
pair<
int
,int
>
endroit;
dessiner_2_grilles(e,f);
SDL_Rect r;
r.x =
280
;
r.y =
140
;
SDL_BlitSurface(a_vous, NULL
, screen, &
r);
SDL_Flip(screen);
SDL_Event event;
bool
clic_correct =
false
;
while
(!
clic_correct){
SDL_WaitEvent(&
event);
switch
(event.type){
case
SDL_QUIT:
clic_correct =
true
;
endroit.first =
endroit.second =
-
1
;
break
;
case
SDL_MOUSEBUTTONUP:
if
((event.button.x >
410
) &&
(event.button.y >
200
)){
endroit.first =
event.button.x-
410
;
endroit.second =
event.button.y-
200
;
endroit.first/=
35
;
endroit.second/=
35
;
if
(endroit.first >=
TAILLE_GRILLE) endroit.first =
TAILLE_GRILLE-
1
;
if
(endroit.second >=
TAILLE_GRILLE) endroit.second=
TAILLE_GRILLE-
1
;
bool
deja_tire =
false
;
// on vérifie qu'on n'a pas déjà tiré ici
deja_tire =
e.is_in("dans l'eau"
, endroit) ||
e.is_in("touches"
, endroit);
if
(deja_tire)
clic_correct =
false
;
else
clic_correct =
true
;
}
break
;
}
SDL_Flip(screen);
}
// redessiner l'écran avec le "en attente"
update(e,f);
return
endroit;
}
On remarque l'appel à la fonction update qui met à jour l'écran avec le résultat de l'attaque (un coup dans l'eau ou un bateau touché). Cette fonction se contente d'afficher tous les sprites.
C-IV. L'écran final▲
Le dernier écran affiche simplement si le joueur a perdu ou gagné. Puis on attend qu'il appuie sur une touche pour quitter.
void
SDLinterface::
affiche_gagne(){
SDL_BlitSurface(fond1, NULL
, screen, NULL
);
SDL_Rect r;
r.x =
127
;
r.y =
100
;
SDL_BlitSurface(gagne, NULL
, screen, &
r);
SDL_Flip(screen);
SDL_Event event;
bool
clic_correct =
false
;
while
(!
clic_correct){
SDL_WaitEvent(&
event);
switch
(event.type){
case
SDL_QUIT:
clic_correct =
true
;
break
;
case
SDL_KEYDOWN:
clic_correct =
true
;
break
;
}
}
}
L'affichage quand le joueur a perdu est sensiblement le même, on change juste le sprite à afficher.
C-V. Quelques points à retenir▲
- Tous les évènements sont directement gérés dans la boucle d'affichage. C'est la raison pour laquelle on se retrouve avec un gros switch dans chaque écran. Ça ne sera plus le cas dans l'affichage avec Irrlicht.
- La scène est reconstruite à chaque frame. À chaque frame on rajoute tous les sprites correspondant aux coups dans l'eau, aux bateaux, aux dégâts … ça ne sera plus le cas dans l'affichage avec Irrlicht.
- L'interface graphique avec SDL consomme très peu de ressources système, en effet, la boucle d'affichage ne tourne que lorsque des évènements sont détectés.