Réalisation d'un jeu de bataille navale en C++


précédentsommairesuivant

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.

 
Sélectionnez
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 contructeur et sont détruits dans le destructeur.

C-I. Ecran 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
 
Sélectionnez
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 :

Image non disponible

C-II. Ecran 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.

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

Image non disponible

C-III. Ecran 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'oeil 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.

Image non disponible

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.

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

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

Image non disponible

C-V. Quelques points à retenir

  • Tous les évènements sont directements gérés dans la boucle d'affichage. C'est la raison pour laquelle on se retrouve avec un gros switch dans chaque écran. Ca ne sera plus le cas dans l'affichage avec Irrlicht.
  • La scène est reconstruite à chaque frame. A 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èmes, en effet, la boucle d'affichage ne tourne que lorsque des évènements sont détectés.

précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2006 Pierre Schwartz. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.