Chinese (People's Republic of China)  English  Français


Supinfo-Projects.com
Tous les projets des élèves ingénieurs de Supinfo



Projets
  Dernier Projet
  Les plus populaires
  Tous les Projets

157 Visiteurs
3168 Projets


My Supinfo-Projects

   Connectez-vous
   Créez un Compte


Synopsis

   44 Visites
   Note INTERNET : 18.6
    (3 Votants)
   0 Commentaires

   Lire l'article

Evaluez cet article

20
18
16
14
12
10
8
6
4
2
0


Commentez cet article

Auteur :

Email :

Votre commentaire :



 
2005 - Pérennisation
Creation d'un moteur de jeu base sur les tiles
[40 mn de lecture - paru le 5/17/2005 5:39:48 PM - Public : Confirmé]

Auteur

cuella_sSamuel CUELLA
Elève-Ingénieur Supinfo Paris
Promotion SUPINFO 2009

   Lui écrire
   Tous les projets de cet auteur
   Le mini-CV de cet auteur

2 Une ébauche de moteur

2.1 Les Tiles

Une fois la SDL correctement initialisée, nous allons pouvoir commencer l'écriture de notre moteur.
Ce dernier est basé sur des tiles, il nous faut donc une structure de données capable de contenir l'image représentant le tile ainsi que toutes les informations que nous jugerons utile d'y placer.

typedef struct __tile_t{
    SDL_Surface *img;
    int blocked;
}tile_t;

Pour le moment, cette structure ne contient que deux champs :
une SDL_Surface destinée à contenir l'image représentant le tile ainsi qu'un
flag indiquant si ce tile est bloqué ou non, c'est à dire si le joueur peut le traverser : Une porte fermée, par exemple, n'est pas traversable le flag sera donc à l'état vrai, c'est à dire 1. Pour ouvrir la porte, il suffit de changer l'image puis de mettre le flag à 0.
Bien entendu nous ne stockons pas réellement l'image dans la structure !
Une carte comprend en général un grand nombre de tiles identiques, de la pelouse par exemple : il serait dommage de charger 100 fois la meme image en memoire alors qu'un pointeur nous permet de n'en conserver qu'un unique exemplaire !
Remarques également que la structure est nommée( __tile_t )alors que nous avons recours à un typedef : cela n'est pas utile pour le moment, mais la structure est appelleé à evoluer, et soyez en sur, ceci deviendra indispensable.

Nous ne définirons pas de fonction d'allocation particulière, allouer un tile n'ayant en soit que peu d'intérêt : Nous manipulerons un tableau de tiles.

2.2 La Carte( Tilemap )

2.2.1 Création

Une fois notre type de base défini, nous pouvons envisager d'afficher un peu plus q'un unique tile sur notre écran : Un niveau entier, ou une carte, si vous preferez, sera definie de la maniere suivante :

typedef struct{
    tile_t **map;
    int height;
    int width;
    int x_offset;
    int y_offset;
}level_t;

Notre premier champ est bien entendu notre carte à proprement parler, définie sous la forme d'un tableau à deux dimensions : Chaque tile sera repéré par ses coordonnées x et y. Les deux champs suivants contiendrons respectivement la hauteur et la largeur de la carte. Quant aux deux derniers, ils indiquent le déplacement par rapport au premier tile(0,0) en haut à gauche : c'est ainsi que nous deplacerons la carte lorsque le joueur s'approchera danguereusement des bords de l'ecran.

Comme vous vous en doutez sûrement, cette structure ne sera pas déclarée de manière statique, mais nous laisserons le travail d'allocation mémoire de la structure comme du tableau de tiles à la fonction suivante, qui prendra en paramètre deux entiers indiquant respectivement la largeur et la hauteur en tiles de la carte.

level_t *create_tile_map(int width, int height )
{
    level_t *rv;
    int i, j;

    rv = (level_t *)malloc(sizeof(level_t));
    if( !rv )
        return(NULL);
    memset(rv,0,sizeof(level_t));

    rv->map = (tile_t **)malloc(sizeof(tile_t *) * width);
    if( !rv->map )
        return(NULL);

    for( i = 0; i < width; i++ ){
        rv->map[i] = ( tile_t * )malloc( sizeof(tile_t) * height );   
        if( !rv->map[i] )
            return(NULL);
        memset( rv->map[i], 0, sizeof(tile_t) * height) ;

    }

    for( i = 0; i < width; i++ ){
        for( j = 0; j < height; j++ ){
            rv->map[i][j].blocked = 0;
            rv->map[i][j].img = NULL;
        }
    }

    rv->width = width;
    rv->height = height;
   
    return(rv);
}

Cette fonction ne contient aucun point particulier à décrire, il s'agit principalement de l'allocation d'un tableau a deux dimensions.
Remarquez tout de même l'appel à memset() qui permet ici de mettre à 0 toute une
Portion de mémoire, en l'occurrence la mémoire occupée par la structure, ce qui a pour effet de régler tous ses champs à 0.
On notera que tous les tiles voient leur pointeur sur surface réglé à NULL ainsi que leur champ blocked à 0 par le même procédé.
ATTENTION : Cette fonction ne crée pas vraiment de carte affichable, elle ne fait que renvoyer une structure initialisée comportant un tableau de tiles aux dimensions voulues. Tenter de l'afficher tel quel provoquerait à coup sur une erreur !
Il faut d'abord faire pointer la surface de chaque tile vers une SDL_Surface précédemment allouée.

2.2.2 Chargement d'images

Voici une fonction permettant de charger une image, pour le moment au format BMP uniquement :

SDL_Surface *load_img_from_file( char *filename )
{
    SDL_Surface *rv;
   
    if( !filename )
        return(NULL);


    rv = SDL_LoadBMP(filename);

    return(rv);
}

Certes, on pourrai largement se passer de cette fonction en appelant directement SDL_LoadBMP, mais cela nous obligerait à modifier beaucoup de code si nous décidions de supporter d'autres formats, ce qui sera fait prochainement.
Enfin, il est vrai que la variable rv n'est pas obligatoire, il est tout à fait possible de retourner directement le résultat de la fonction, mais cela pourrai compromettre la lisibilité du code. Encore une fois, rien ne vous empêche d'apporter les modifications que vous jugerez utiles.

Maintenant que nous savons charger une image en mémoire, nous pouvons sans difficulté créer une carte de 40x50 tiles représentant une vaste et verdoyante prairie.

SDL_Surface *pict = load_img_from_file("grass.bmp");

level_t *level = create_tile_map( 40, 50 );

for( i = 0; i < level->width; i++ ){
    for( j = 0; j < level->height; j++ ){
        level->map[i][j].img = pict;
    }
}

insérez ce code dans le main, ou créez une fonction d'initialisation si cela vous semble plus convenable.
Comme vous pouvez le constater, il n'existe aucune contrainte sur la taille des tiles, cependant le moteur est destiné à gérer des tiles carrés. Une taille de 64x64 pixels semble être tout à fait correcte en regard de la résolution( 800x600 ) choisie.

2.2.3 Affichage

Une fois le pré chargé en mémoire, l'étape suivante est de le dessiner !

int draw_level( level_t *level )
{
    int i,j;
    SDL_Rect dest_rect;

    if( !level )
        return(-1);

    SDL_FillRect( screen, NULL, SDL_MapRGB(screen->format,0,0,0) );

    for( i = 0; i < level->width; i++ ){
        for( j = 0; j < level->height; j++ ){
            dest_rect.x = i * level->map[i][j].img->w - level->x_offset;
            dest_rect.y = j * level->map[i][j].img->h - level->y_offset;
            SDL_BlitSurface(level->map[i][j].img,NULL,screen, &dest_rect);
        }
    }
    return(0);
}

Avant de dessiner quoi que ce soit, il faut effacer l'écran : Grâce à SDL_FillRect, nous remplissons l'écran de couleur noire. Cette fonction prend 3 paramètres :

  • La surface de destination
  • L'aire dans cette surface que nous voulons remplir ou NULL dans notre cas, afin de remplir la totalité de la surface.
  • Le dernier paramètre correspond à la couleur.
Or, il n'est pas possible de spécifier de couleur directement.
 En effet, chaque surface peutAvoir un format de pixel différent :
Il faut faire appel a une fonction, SDL_MapRGB qui va traduire une couleur spécifiée
au moyen des coefficients Rouge, Vert et Bleu dans le format de pixel de la surface
que l'on veut remplir. Pour ce faire, il suffit de passer à la fonction SDL_MapRGB
le format de pixel dans lequel il faut effectuer la translation ainsi que les valeurs RGB.
Chaque surface contient son format de pixel dans le champ... format !
C'est ainsi que pour remplir tout l'écran de couleur noire, nous utilisons la fonction suivante :

SDL_FillRect( screen, NULL, SDL_MapRGB(screen->format,0,0,0) );

Ensuite nous commençons le dessin de la carte proprement dit :
Grâce aux deux boucles for imbriqués, nous iterons sur tous les éléments (x, y)
de la carte.
Avant l'affichage, nous devons transformer les coordonnées en tiles vers des coordonnées en pixel :

Pour obtenir les coordonnées (x',y') en pixels du coin haut gauche de la tile, il suffit
,sachant que toutes les tiles ont la même taille, de multiplier ses coordonnées (x,y) ou
bien encore dans la fonction (i,j) par la largeur ou
la hauteur d'une tile selon si nous désirons la coordonnée x ou y puis de soustraire
l'offset correspondant : Inutile de vérifier si la tile est actuellement visible ou non !
SDL s'en charge pour nous : une fois l'offset soustrait, si la tile a deux coordonnee
négatives, elle ne sera pas dessinée.

dest_rect.x = i * level->map[i][j].img->w - level->x_offset;
dest_rect.y = j * level->map[i][j].img->h - level->y_offset;

Comme il apparaît dans le code, les coordonnées ne sont pas « directement » spécifiées, il faut remplir une structure de type SDL_Rect comprenant 4 champs :
  • l'abscisse (x)   
  • l'ordonnée (y)
  • la hauteur (h)
  • la largeur (w)

Ici, seule l'ordonnée et l'abscisse sont nécessaires.

La fonction qui permet de copier la tile vers l'écran,
SDL_BlitSurface, prends 4 paramètres :
  • L'adresse de la surface source       
  • L'adresse d'un SDL_Rect représentant la portion de surface source à copier       
  • L'adresse de la Surface de destination       
  • L'adresse d'un SDL_Rect dont seul les champs x et y sont utilisés pour indiquer les coordonnées de la surface de destination vers laquelle doit être réalisée la   copie

Passer NULL en deuxième paramètre permet de copier la totalité de la surface.

A présent, nous sommes capables de créer une carte, et de l'afficher à l'écran.
Il ne nous manque plus qu'un joueur et bien entendu de pouvoir déplacer la carte si notre héros venait à s'aventurer trop près des bords de l'écran.

2.3 Le Joueur

2.3.1 Structure

Il sera représenté par la structure suivante :

typedef struct{
    SDL_Surface *img;
    int x,y;
    int vx,vy;
}player_t;

Une image, ses coordonnées et enfin sa  « vitesse » en x et en y.

2.3.2 Creation

Pour créer le joueur, utilisons la fonction suivante :

player_t *make_player( SDL_Surface *def,  int x, int y )
{
    player_t *rv;
   
    if( !def )
        return(NULL);

    rv = (player_t *)malloc( sizeof(player_t) );
    if( !rv )
        return(NULL);
    memset( rv, 0, sizeof(player_t) );

    rv->img = def;
    rv->x = x;
    rv->y = y;

    return(rv);
}

Cette fonction se contente de prendre en paramètre un pointeur vers une surface contenant l'image représentant le joueur, sa position (x,y) d'origine sur la carte
et renvoie un pointeur sur le joueur nouvellement alloué.
ATTENTION ! L'image représentant le joueur doit impérativement être de la même taille que les tiles, en effet, la position (x,y) du joueur est la position d'un tile, le tile représentant le joueur.

2.3.3 Affichage

2.3.3.1 Calcul de coordonnées

Avant de pouvoir afficher le joueur, nous devons être capables de traduire sa position dans le repère des tiles vers celui de l'écran, en pixel : il s'agit d'un simple changement de repere comme celui effectué pour le dessin des tiles.

Ici, une fonction va nous être utile :

SDL_Rect player_coords_to_screen_area( level_t *level, player_t *player )
{
    SDL_Rect rv;

    if( !(level && player) )
        return;
    rv.x = player->x * player->img->w - level->x_offset;
    rv.y = player->y * player->img->h - level->y_offset;
    rv.h = player->img->h;
    rv.w = player->img->w;

    return(rv);
}

Avant toute chose, vous ne manquerez pas de faire la remarque suivante :
Pourquoi utiliser une fonction séparée, alors que le calcul de la position des tiles
se fait dans le corps de la fonction d'affichage ?
Tout simplement a cause du coup de l'appel de fonction :
Lorsque l'on dessine la carte, ce calcul est fait un très grand nombre de fois :
Autant de fois qu'il y a de tiles dans la carte !
Là bas, le placer dans une fonction séparée aurait engendré un sur coup en temps de calcul important, alors qu'ici cette fonction n'est appelée qu'une fois par itération de la boucle principale, engendrant un coup faible pour une meilleure lisibilité du code, ce qui devient intéressant...
Il est vrai qu'il aurait été possible de faire de même en mettant cette fonction « inline », ou encore sous forme de macro. Au risque de me répéter, libre à vous !

2.3.3.2 Dessin

Maintenant, avant d'examiner la gestion du clavier, voyons la fonction qui prend en charge le dessin du joueur :

int draw_player( level_t *level, player_t *player )
{
    SDL_Rect player_rect;

    if( !(player && level ) )
        return(-1);

    player_rect = player_coords_to_screen_area( level, player );

    SDL_BlitSurface(player->img, NULL, screen, &player_rect);

    return(0);
}

Rien de franchement nouveau jusqu'ici, mais n'ayez crainte, cette fonction elle aussi
est appelée à évoluer.

2.3.4 Déplacements

Il existe des tiles inaccessibles au joueur : Pour prendre en compte ce fait, voici
une petite fonction inline qui vérifie si le joueur peut aller sur sa case de destination :

inline int is_blocked( int x, int y, level_t *level )
{
    return(level->map[x][y].blocked);
}

Pour faire bouger le joueur, nous devons récupérer les événements du clavier :
La fonction move_player va s'en charger :

int move_player( level_t *level, player_t *player )
{
    SDL_Event event;
    SDL_Rect player_rect;

    while( SDL_PollEvent( &event ) ){
        switch( event.type ){
            case SDL_KEYDOWN:
                switch( event.key.keysym.sym ){
                    case SDLK_LEFT:
                        player->vx = -1;
                        break;
                    case SDLK_RIGHT:
                        player->vx =  1;
                        break;
                    case SDLK_UP:
                        player->vy = -1;
                        break;
                    case SDLK_DOWN:
                        player->vy =  1;
                        break;
                    case SDLK_ESCAPE:
                        return(0);
                        break;
                    default:
                        break;
                }
                break;
            case SDL_KEYUP:
                switch( event.key.keysym.sym ){
                    case SDLK_LEFT:
                        if( player->vx < 0 )
                            player->vx = 0;
                        break;
                    case SDLK_RIGHT:
                        if( player->vx > 0 )
                            player->vx = 0;
                        break;
                    case SDLK_UP:
                        if( player->vy < 0 )
                        player->vy = 0;
                        break;
                    case SDLK_DOWN:
                        if( player->vy > 0 )
                        player->vy = 0;
                        break;
                    default:
                        break;
                }
                break;
            default:
                break;
        }
    }
    /* Update the player position */
    player->x += player->vx;
    if( player->x < 0 || player->x >= level->width )
        player->x -= player->vx;

    player->y += player->vy;
    if( player->y < 0 || player->y >= level->height )
        player->y -= player->vy;

    if( is_blocked( player->x, player->y, level) ){
        player->y -= player->vy;
        player->x -= player->vx;
    }
       
    player_rect = player_coords_to_screen_area( level, player );
    if( player_rect.y > 500 )
        level->y_offset += 400;

    if( player_rect.x > 700 )
        level->x_offset += 400;

    if( player_rect.y < 100 && level->y_offset > 0 )
        level->y_offset -= 400;

    if( player_rect.x < 100 && level->x_offset > 0 )
        level->x_offset -= 400;

    return(1);

}

Pour SDL,  l'appui sur une touche, un mouvement de souris ou encore un click sont des
événements.
A chaque fois que l'utilisateur appuie sur une touche ou la relâche, un événement de
produit. S'il ne peut être traité immédiatement, SDL le met dans une file d'attente.
pour traiter l'appui sur les touches du clavier, nous devons donc récupérer chaque
événement dans la file puis verifier s'il s'agit bien d'un evenement clavier :
Pour ce faire nous declarons une variable event de type SDL_Event qui est en fait
une structure contenant plusieurs champs dont l'un nous intéresse particulièrement :
type c'est lui qui va nous indiquer si l'événement est bien un événement clavier.
L'adresse de cette structure sera passée à la fonction SDL_PollEvent qui enlève le prochain évènement de la file et remplit les champs la structure passée en paramètre.
Comme cette fonction retourne 1 tant qu'il reste des évènements à traiter et 0
dans le cas contraire, pour itérer sur le évènements il suffit de faire :

    while( SDL_PollEvent( &event ) ){
        switch( event.type ){
            case SDL_KEYDOWN: //une touche est pressée
                switch( event.key.keysym.sym ){
                    ?
                }               
                ?.
            case SD_KEYUP : //une touche est relachée
                switch( event.key.keysym.sym ){
                    ?
                }
        }
}

Une fois que l'appui ou le relâchement d'une touche est détecté, pour l'identifier,
il suffit de lire event.key.keysym.sym qui contient le code de la touche concernée
et de le comparer aux constantes définies par SDL, consultables soit dans la
documentation, soit dans le fichier /usr/include/SDL/SDL_keysym.h pour les
plus courageux.

Pourquoi traiter les événements SDL_KEYDOWN et SDL_KEYUP ?
Pour SDL, une fois que l'événement KEYDOWN s'est produit sur une touche,
SDLK_LEFT par exemple, il ne se produira plus avant que l'événement
SDL_KEYUP se produise pour la même touche( a moins d'avoir réglé le paramètre
SDL_EnableKeyRepeat, cas que nous ne traiterons pas ).
Ainsi pour se déplacer de 5 tiles, l'utilisateur devra presser et relâcher la touche
« Flèche de gauche » 5 fois !
Afin de remédier à cela, il suffit de considérer que le joueur se déplace à partir
du moment ou un événement KEYDOWN a été reçu sur la touche convenue
jusqu'au moment ou cette touche est relachée : d'où l'importance du vecteur
vitesse du joueur !
La direction voulue(vx ou vy ) est mise à 1 ou -1 selon le sens du déplacement(haut,
bas,droite,gauche) et n'est remise à 0 que lorsque la touche est relâchée.

Enfin nous mettons à jour la position de joueur, en vérifiant qu'il ne sorte
pas de la carte, ou qu'il se ne retrouve pas sur une tile bloquée, puis ayant
converti ses coordonnées en pixels, nous vérifions s'il est temps de déplacer
la carte.
Nous avons choisi pour les valeurs limites avant les bords de l'écran,
ainsi que le nombre de pixels de décalage de la carte de manière
totalement arbitraire, veillez à les modifier si vos tiles sont de taille différente par
rapport aux nôtres( 64x64 pixels).

Notez que la fonction retourne toujours 1 sauf si la touche ESCAPE est pressée,
auquel cas elle retourne 0, ce qui permet de mettre fin au jeu.

2.4 Boucle Principale

Nantis de ces fonctions, nous pouvons maintenant écrire la boucle principale
du jeu, destinée à être appelle par le main() :

void main_loop(level_t *level, player_t *player)
{
    int playing = 1;
    while( playing ){
        playing = move_player(level, player);
        draw_level(level,player);
        draw_player(level, player);
        SDL_Flip(screen);
    }

}


Voilà ! Nous venons de jeter les bases du moteur, il est maintenant parfaitement
fonctionnel.
Cependant, le joueur n'est pas du tout anime, on ne peut pas le voir marcher,
Il semble être toujours dans la même direction qu'il aille en haut, en bas a gauche
ou à droite !
Il n'y a aucun mécanisme correct d'animation, et il faudrait considérablement,
dans l'état actuel des choses, alourdir la fonction d'affichage du niveau pour
faire clignoter une lumière !




Articles de la même catégorie

 Pages : Top


24 Visites
0 Commentaires
Initiation au Perl
[10 mn de lecture - paru le 5/17/2005 5:31:28 PM - Public : Débutant]

En savoir plus


61 Visites
0 Commentaires
Interface graphique Swing avec Visual Editor
[15 mn de lecture - paru le 5/17/2005 5:31:04 PM - Public : Débutant]

En savoir plus


16 Visites
0 Commentaires
Create a graphical interface with dialog under Bash Scripting
[30 mn de lecture - paru le 5/17/2005 4:22:46 PM - Public : Débutant]

En savoir plus

   Tous les Articles


SUPINFO Training Center peut vous proposer une formation ...

   Devenez Ingénieur Système Microsoft en 35 jours avec SUPINFO Training Center
   Devenez Certifiés Cisco en 13 jours avec SUPINFO Training Center
   Devenez Administrateur Système Microsoft avec SUPINFO Training Center
   Devenez Développeur Microsoft .NET en 13 jours avec SUPINFO Training Center



Powered by Campus-Booster Technology
Conditions d'utilisation & Copyright | Respect de la vie privée
© Copyright 1965-2006 Supinfo Paris, Paris Academy of Computer Science
Supinfo, Ecole Supérieure d'Informatique et Paris Academy Of Computer Science are trade marks.
23, rue de Château LANDON - 75010 PARIS - Phone : +33 (0) 153359 700 Fax : +33 (0) 153359 701

Web site autided by :