Version en ligne

Tutoriel : [TP] Zozor dans son enclos

Table des matières

[TP] Zozor dans son enclos
Le projet
La solution

[TP] Zozor dans son enclos

Le projet

Amis de la SPA (Société Protectrice des Animaux), ce tuto n'est pas pour vous :p ! En effet, il consiste purement et simplement à confiner notre cher Zozor (la mascotte du Site du Zér0) comme on le fait de nos jours pour de vulgaires volailles (clin d'oeil à la crise de la grippe aviaire :lol: ). Mais cette séquestration ne sera pas sans but, elle vous permettra au moins d'entretenir vos connaissances en C ! Alors, pourquoi pas ? ^^

Le projet

La solution

Mettre Zozor dans son enclos o_O ? Je ne comprends pas trop où tu veux en venir :o !

C'est simple : dans ce TP, nous allons ouvrir une fenêtre dans laquelle le curseur est remplacé par une image de Zozor (à fond transparent). On placera dans cette fenêtre, à 40 pixels de chaque bord, une clôture de 10 px d'épaisseur (représentée par un simple rectangle noir). Les mouvements de Zozor (dirigés par la souris) seront limités à l'intérieur des "clôtures" (autrement dit on ne pourra pas approcher la souris à moins de 50 pixels du bord de la fenêtre). :)

Par où commencer ? :euh:

Je vais vous aider en vous donnant la structure du code ainsi que quelques informations ;) . Ah oui, je vous fournis aussi l'image de Zozor ;) (clic droit + Enregistrer l'image sous... pour la télécharger) :

Image utilisateur

1. Les variables

Je ne m'étends pas là-dessus, vous verrez bien au fur et à mesure de quelles variables & pointeurs vous aurez besoin. Par contre, je précise que vous n'aurez pas besoin de 4 surfaces pour les "clôtures". En effet, une surface pour les verticales (gauche / droite) et une autre pour les horizontales (haut / bas) suffisent. Celles-ci seront blittées 2 fois chacune à deux endroits différents. ;)

2. La fenêtre

Petite précision : la fenêtre fera 640 px de large sur 480 px de haut et sera en 32 bits. N'oubliez pas le double buffering. Je vous laisse choisir le titre et la couleur de fond (bien qu'un vert imitation herbe serait pas mal :p )!

3. Les "clôtures"

Dans cette section, vous allez déterminer la dimension des clôtures, de préférence par rapport à la largeur / hauteur de la fenêtre. Il suffit de faire une soustraction entre la hauteur / largeur de la fenêtre (ecran->w ou ecran->h) et les deux "marges" à laisser entre le bord de celle-ci et la "clôture". N'oubliez pas ce que j'ai dit au début : 40 px de "marge" (que l'on peut considérer comme l'extérieur de l'enclos) + 10 px d'épaisseur de "clôture" pour un total de 50 px de chaque bord dans lesquels Zozor ne pourra pas gambader ;) . Oups! J'en ai déjà trop dit :-° :p ...

Pour vous aider, voici un schéma qui vous permettra de mieux comprendre ce que j'attends de vous.

Image utilisateur

4. Placement initial de Zozor

On charge bien sûr l'image de Zozor dans sa surface et on la rend transparente, mais nous allons aussi nous occuper de deux petites choses qui feront la différence. Premièrement, il faut faire disparaître le curseur pour que l'utilisateur ait l'impression de faire bouger Zozor et non pas un curseur avec notre cher animal derrière car, comme je l'ai dit au début, l'image du mulet suivra le déplacement de la souris. En second lieu, il faut placer initialement Zozor (et donc le curseur) au centre de la fenêtre.

5. La boucle des évènements

Voici venir... la boucle!!! :D
En bref, quand on clique sur la croix en haut à droite, le programme doit se fermer, idem quand on appuie sur n'importe quelle touche du clavier. Jusque-là, c'est simple. Ensuite vient le moment de limiter les déplacements de la souris (de Zozor) grâce au type d'évènement SDL_MOUSEMOTION. À l'aide de 4 conditions if (une pour chaque bord), vous allez vérifier si le curseur a dépassé une certaine abcisse (x) ou une certaine ordonnée (y). Si tel est le cas, il faudra le déplacer à nouveau à l'intérieur de l'enclos. (Non pas au milieu mais contre le bord, ce qui donnera à l'utilisateur l'impression que Zozor se heurte à la clôture et ne peut la franchir :lol: . Je suis sadique, mouahaha :p .) N'oubliez quand même pas qu'après il faut mettre à jour la position de la surface de Zozor selon les coordonnées de l'emplacement du curseur (pour créer l'impression que Zozor suit le déplacement de la souris).
Pour terminer, à chaque passage dans la boucle (et donc à chaque changement de position de Zozor), il faut effacer la fenêtre et blitter à nouveau l'ensemble des surfaces (attention au SDL_Flip() ;) ).

6. On libère la mémoire

Juste pour rappel, il n'y a que la surface principale (ecran) qui ne doit pas être libérée par la fonction SDL_FreeSurface() : il y en a donc quelques-uns à faire ^^ .


La solution

La solution

Le projet

Alors... vous avez réussi ? :) J'espère que oui. Mais si ce n'est pas le cas, pas d'inquiétude ; on n'apprend pas qu'en réussissant ;) mais aussi en se trompant (à condition de se corriger). Voici donc la solution présentée de la même manière que le projet.

(0. Les includes)

Je les mets pour être sûr que vous n'en oublierez aucun ;) .

#include <stdlib.h>
#include <stdio.h>
#include <SDL/SDL.h>

int main(int argc, char *argv[])
{

J'espère que vous n'aviez pas oublié la SDL (sinon honte à vous, mouahaha :p ).

1. Les variables

/*
1. Déclaration des variables
*/
SDL_Surface *ecran = NULL, *zozor = NULL;
SDL_Surface *clotureVerticale = NULL, *clotureHorizontale = NULL;
SDL_Event event;
SDL_Rect positionZozor;
SDL_Rect positionClotureGH, positionClotureD, positionClotureB;
int continuer = 1;

Alors, dans l'ordre on a :

Petite précision à propos des variables de positionnement des clôtures : la lettre en majuscule à la fin du nom de la variable signifie son emplacement (G pour gauche, H pour haut, D pour droite et B pour bas).

2. La fenêtre

/*
2. On s'occupe de la fenêtre
*/
SDL_Init(SDL_INIT_VIDEO);

ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF);
SDL_WM_SetCaption("Pauvre Zozor.", NULL);
//On colore le fond en vert (imitation herbe... :lol: )
SDL_FillRect(ecran, NULL, SDL_MapRGB(ecran->format, 10,255,10));

Pas besoin d'explications, je présume. Il ne fallait pas oublier de mettre le double buffering.

3. Les clôtures

Ici, j'affiche les réponses en plusieurs fois.

/*
3. Les clôtures
*/
// On détermine les dimensions des clôtures verticales et horizontales
clotureVerticale = SDL_CreateRGBSurface(SDL_HWSURFACE, 10, ecran->h - 2*40, 32, 0, 0, 0, 0);
clotureHorizontale = SDL_CreateRGBSurface(SDL_HWSURFACE, ecran->w - 2*40, 10, 32, 0, 0, 0, 0);

Commençons par le début : la dimension des fameuses barrières.
Le calcul des dimensions que j'ai mis dans la fonction SDL_CreateRGBSurface est assez logique.
Parlons de la clôture verticale. Pour la largeur, nous nous étions mis d'accord (ou plutôt je vous ai dit :p ) de mettre 10 px. Pour la hauteur, c'est plus compliqué. On commence par prendre la hauteur de la fenetre (ecran->h) à laquelle on doit soustraire 2 * 40 px (ce sont les marges de part et d'autre de l'extérieur de l'enclos). Et voilà, c'est tout ^^ ! Pour la clôture horizontale, il suffit d'intervertir ces deux données (la largeur devient la hauteur et vice-versa). On continue :

// On détermine la position des 4 clôtures
// Celle de gauche et celle du haut (elles ont la même origine)
positionClotureGH.x = 40;
positionClotureGH.y = 40;
// Celle de droite
positionClotureD.x = ecran->w - 50;
positionClotureD.y = 40;
// Celle du bas
positionClotureB.x = 40;
positionClotureB.y = ecran->h -50;

C'est ici que c'est un peu casse-tête (pour ne pas dire casse-gu*** :-° ). En effet, il faut bien comprendre que le positionnement d'une surface est déterminé par son coin supérieur gauche (c'est son origine). À partir de ce moment-là, on peut s'aider d'un petit schéma :

Image utilisateur

Grâce aux mesures que j'ai affichées en dessous, on peut maintenant facilement déterminer les coordonnées de chaque origine et s'apercevoir qu'il faut, pour obtenir l'ordonnée de la clôture du bas, faire "la hauteur de la fenêtre moins 50 px" (soit ecran->h - 50 pour l'ordonnée) alors que pour obtenir l'abcisse de la clôture de droite, on fait "la largeur de l'écran moins 50 px" (soit ecran->w - 50 pour l'abcisse). Compris ? :p

Fiou, il reste plus qu'à blitter tout ça :D :

// On blitte les 4 clôtures aux bons endroits
//Celle de gauche
SDL_BlitSurface(clotureVerticale, NULL, ecran, &positionClotureGH);
//Celle de droite
SDL_BlitSurface(clotureVerticale, NULL, ecran, &positionClotureD);
//Celle du haut
SDL_BlitSurface(clotureHorizontale, NULL, ecran, &positionClotureGH);
//Celle du bas
SDL_BlitSurface(clotureHorizontale, NULL, ecran, &positionClotureB);

Et voilà, on en a fini avec les clôtures.

4. Placement initial de Zozor

/*
4. Placement initial de Zozor
*/
//On charge l'image de Zozor dans la surface et on rend le fond transparent
zozor = SDL_LoadBMP("zozor.bmp");
SDL_SetColorKey(zozor, SDL_SRCCOLORKEY, SDL_MapRGB(zozor->format, 0, 0, 255));

// On rend le curseur invisible et on le centre (Zozor devient le curseur)
SDL_ShowCursor(SDL_DISABLE);
SDL_WarpMouse((ecran->w / 2 - zozor->w / 2), (ecran->h / 2 - zozor->h / 2));

Hormis le fait qu'on charge l'image de Zozor dans sa surface et qu'on rend le fond transparent (je suppose que ça, c'était OK de votre côté), il faut rendre le curseur invisible grâce à la fonction SDL_ShowCursor(). De plus, on doit déplacer Zozor au milieu de la fenêtre. Mais il ne faut pas perdre de vue que, lorsque la boucle d'évènements fera que Zozor suit les déplacements de la souris, le curseur sera dans le coin supérieur gauche de l'image (testez en n'effaçant pas le curseur et vous verrez). De ce fait, il ne suffit pas de faire :

SDL_WarpMouse(ecran->w / 2, ecran->h / 2);

En effet, il faut faire une soustraction entre la largeur / hauteur de la fenêtre et la largeur / hauteur de Zozor pour obtenir les bonnes coordonnées (comme dans la solution ci-dessus). Vous ne comprenez pas ? Pas de bol, c'est comme ça, les maths :lol: !

5. La boucle principale

/*
5. La boucle principale
*/
while(continuer)
{
    SDL_WaitEvent(&event);

    switch(event.type)
    {
        case SDL_QUIT:
           continuer = 0;
           break;

        // En appuyant sur une touche, on quitte le programme
        case SDL_KEYDOWN:
           continuer = 0;
           break;

        // On limite les déplacements de Zozor à son enclos (mouahaha)
        case SDL_MOUSEMOTION:
        // Empêcher le dépassement vers la droite
           if(event.motion.x > ecran->w - zozor->w - 50)
                SDL_WarpMouse(ecran->w - zozor->w - 50, event.motion.y);

        // Empêcher le dépassement vers la gauche
           if(event.motion.x < 50)
                SDL_WarpMouse(50, event.motion.y);

        // Empêcher le dépassement vers le haut
           if(event.motion.y < 50)
                SDL_WarpMouse(event.motion.x, 50);

        // Empêcher le dépassement vers le bas
           if(event.motion.y > ecran->h - zozor->h - 50)
                SDL_WarpMouse(event.motion.x, ecran->h - zozor->h - 50);

           positionZozor.x = event.motion.x;
           positionZozor.y = event.motion.y;
           break;
        }

    // On efface et on blitte à nouveau toutes les surfaces aux éventuels nouveaux emplacements
    SDL_FillRect(ecran, NULL, SDL_MapRGB(ecran->format, 0, 255, 0));
    // Zozor
    SDL_BlitSurface(zozor, NULL, ecran, &positionZozor);
    // Les clôtures
    SDL_BlitSurface(clotureVerticale, NULL, ecran, &positionClotureGH);
    SDL_BlitSurface(clotureVerticale, NULL, ecran, &positionClotureD);
    SDL_BlitSurface(clotureHorizontale, NULL, ecran, &positionClotureGH);
    SDL_BlitSurface(clotureHorizontale, NULL, ecran, &positionClotureB);

    SDL_Flip(ecran);
}

Parlons un peu du traitement des évènements. En dehors de l'habituelle fermeture en cliquant sur la croix, on retrouve un autre moyen de fermeture en appuyant sur une touche du clavier. J'ai fait cela pour permettre de quitter plus facilement le programme car, comme la souris est coincée dans l'enclos, il n'est pas possible d'atteindre la croix (sauf en faisant un mouvement rapide ;) ). Ensuite, en cas d'évènement MOUSEMOTION (quand la souris bouge), à l'aide des conditions if, on teste si Zozor ne sort pas de ses frontières :p . Si c'est le cas, on déplace la souris.

Lors du même évènement il faut modifier le positionnement de Zozor en remplaçant l'initial par les coordonnées de la souris.

Pour terminer, on efface et on blitte à nouveau toutes les surfaces aux éventuels nouveaux emplacements :D (sans oublier le SDL_Flip() ;) ).

6. On libère la mémoire

/*
6. Pour terminer
*/
SDL_Flip(ecran);
SDL_FreeSurface(zozor);
SDL_FreeSurface(clotureVerticale);
SDL_FreeSurface(clotureHorizontale);
SDL_Quit();

return EXIT_SUCCESS;
}

Aucune explication n'est nécessaire, on libère seulement la mémoire utilisée pour stocker les surfaces ^^ .

Le code en entier

#include <stdlib.h>
#include <stdio.h>
#include <SDL/SDL.h>

int main(int argc, char *argv[])
{
    /*
    1. Déclaration des variables
    */
    SDL_Surface *ecran = NULL, *zozor = NULL;
    SDL_Surface *clotureVerticale = NULL, *clotureHorizontale = NULL;
    SDL_Event event;
    SDL_Rect positionZozor;
    SDL_Rect positionClotureGH, positionClotureD, positionClotureB;
    int continuer = 1;


    /*
    2. On s'occupe de la fenêtre
    */
    SDL_Init(SDL_INIT_VIDEO);

    ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF);
    SDL_WM_SetCaption("Pauvre Zozor.", NULL);
    //On colore le fond en vert (imitation herbe... :lol: )
    SDL_FillRect(ecran, NULL, SDL_MapRGB(ecran->format, 10,255,10));


    /*
    3. Les clôtures
    */
    // On détermine les dimensions des clôtures verticales et horizontales
    clotureVerticale = SDL_CreateRGBSurface(SDL_HWSURFACE, 10, ecran->h - 2*40, 32, 0, 0, 0, 0);
    clotureHorizontale = SDL_CreateRGBSurface(SDL_HWSURFACE, ecran->w - 2*40, 10, 32, 0, 0, 0, 0);

    // On détermine la position des 4 clôtures
    // Celle de gauche et celle du haut (elles ont la même origine)
    positionClotureGH.x = 40;
    positionClotureGH.y = 40;
    // Celle de droite
    positionClotureD.x = ecran->w - 50;
    positionClotureD.y = 40;
    // Celle du bas
    positionClotureB.x = 40;
    positionClotureB.y = ecran->h -50;

    // On blitte les 4 clôtures aux bons endroits
    //Celle de gauche
    SDL_BlitSurface(clotureVerticale, NULL, ecran, &positionClotureGH);
    //Celle de droite
    SDL_BlitSurface(clotureVerticale, NULL, ecran, &positionClotureD);
    //Celle du haut
    SDL_BlitSurface(clotureHorizontale, NULL, ecran, &positionClotureGH);
    //Celle du bas
    SDL_BlitSurface(clotureHorizontale, NULL, ecran, &positionClotureB);


    /*
    4. Placement initial de Zozor
    */
    //On charge l'image de Zozor dans la surface et on rend le fond transparent
    zozor = SDL_LoadBMP("zozor.bmp");
    SDL_SetColorKey(zozor, SDL_SRCCOLORKEY, SDL_MapRGB(zozor->format, 0, 0, 255));

    // On rend le curseur invisible et on le centre (Zozor devient le curseur)
    SDL_ShowCursor(SDL_DISABLE);
    SDL_WarpMouse((ecran->w / 2 - zozor->w / 2), (ecran->h / 2 - zozor->h / 2));


    /*
    5. La boucle principale
    */
    while(continuer)
    {

        SDL_WaitEvent(&event);

        switch(event.type)
        {
            case SDL_QUIT:
                continuer = 0;
                break;

            // En appuyant sur une touche, on quitte le programme
            case SDL_KEYDOWN:
                continuer = 0;
                break;

            // On limite les déplacements de Zozor à son enclos (mouahaha)
            case SDL_MOUSEMOTION:
            // Empêcher le dépassement vers la droite
                if(event.motion.x > ecran->w - zozor->w - 50)
                    SDL_WarpMouse(ecran->w - zozor->w - 50, event.motion.y);

            // Empêcher le dépassement vers la gauche
                if(event.motion.x < 50)
                    SDL_WarpMouse(50, event.motion.y);

            // Empêcher le dépassement vers le haut
                if(event.motion.y < 50)
                    SDL_WarpMouse(event.motion.x, 50);

            // Empêcher le dépassement vers le bas
                if(event.motion.y > ecran->h - zozor->h - 50)
                    SDL_WarpMouse(event.motion.x, ecran->h - zozor->h - 50);

                positionZozor.x = event.motion.x;
                positionZozor.y = event.motion.y;
                break;
        }

        // On efface et on blitte à nouveau toutes les surfaces aux éventuels nouveaux emplacements
        SDL_FillRect(ecran, NULL, SDL_MapRGB(ecran->format, 0, 255, 0));
        // Zozor
        SDL_BlitSurface(zozor, NULL, ecran, &positionZozor);
        // Les clotures
        SDL_BlitSurface(clotureVerticale, NULL, ecran, &positionClotureGH);
        SDL_BlitSurface(clotureVerticale, NULL, ecran, &positionClotureD);
        SDL_BlitSurface(clotureHorizontale, NULL, ecran, &positionClotureGH);
        SDL_BlitSurface(clotureHorizontale, NULL, ecran, &positionClotureB);

        SDL_Flip(ecran);
    }


    /*
    6. Pour terminer
    */
    SDL_Flip(ecran);
    SDL_FreeSurface(zozor);
    SDL_FreeSurface(clotureVerticale);
    SDL_FreeSurface(clotureHorizontale);
    SDL_Quit();

    return EXIT_SUCCESS;
}

J'espère que ce TP vous a plu et surtout vous a permis de bien ancrer une partie de ce que vous avez appris jusqu'ici.

Il y a bien sûr moyen de mettre des images à la place des clôtures noires toutes moches. On peut aussi envisager de mettre une image d'herbe de 640 * 480 à la place du fond vert pour un meilleur effet. Si vous le faites, je serais très intéressé de voir le résultat :) .

Je terminerai en disant qu'aucun animal (en l'occurrence Zozor) n'a souffert pendant le codage de ce programme :lol: !


Le projet