Réaliser un RPG en C avec SDL et FMOD/Les cartes
Réaliser un RPG en C avec SDL et FMOD
<< Précédent | Sommaire | Suivant >>
Difficultée estimée de cette étape: moyen
Pré-requis:
- Connaissance moyenne du C (allocations dynamiques, pointeurs et gestion des entrées/sorties)]]
Une carte dans un jeu en 2d représente le décor sur lequel le personnage évolue. Il s'agit du fondement même d'un jeu en deux dimensions dans la mesure où la qualité de ce système de cartes va influencer sur le rendu graphique final du jeu.
Le but est de créer via une mosaïque de tuiles (ou tiles) un fond cohérent.
Le schéma de principe est le suivant:
- on charge une image regroupant toutes les tuiles (appellé chipset)
- à chaque tuile on associe un certain nombre de caractéristiques (peut-on traverser la tuile? est-ce que le décor sera animé? etc.) et surtout un numéro.
- on indique pour chaque case du décor quel est le numéro de la tuile correspondante.
[modifier] Création du fichier map.c et map.h
Comme précédemment avec main.c et main.h, vous devez créer deux nouveaux fichiers et insérer le squelette initial sur lequel nous allons développer le système de cartes.
Fichier map.c
/* ** map.c ** ** Gestion des cartes. ** ** Rôle: ** - chargement / sauvegarde des cartes ** - affichage des cartes ** - génération automatique d'une carte */ #include "map.h"
Fichier map.h
/* ** map.h ** ** Gestion des cartes. ** ** Rôle: ** - chargement / sauvegarde des cartes ** - affichage des cartes ** - génération automatique d'une carte */ #ifndef MAP_H # define MAP_H # define TILE_WIDTH 32 # define TILE_HEIGHT 32 # include <stdio.h> # include <stdlib.h> # include <string.h> # include <SDL/sdl.h> # include <FMOD/fmod.h> # include <FMOD/fmod_errors.h> #endif /* !MAP_H */
[modifier] Le type struct s_map
Il faut désormais définir dans map.h le type que nous allons utiliser pour gérer les cartes.
La structure s_chipset contient les informations caractérisant une tuile.
struct s_chipset { /* coordonnée du tile dans le chipset */ unsigned int x; unsigned int y; /* indique si l'on peut traverser ou pas le tile */ unsigned char collision; /* indique le numéro du prochain tile dans l'animation ou -1 si le tile n'est pas animé. */ int nextTile; };
La structure s_map contient l'ensemble des informations se rapportant à une carte.
struct s_map { unsigned int n_tile; /* nombre de tiles dont est composé la carte */ struct s_chipset *chipset; /* tableau des tiles (allocation dynamique) */ SDL_Surface *surf_chipset; /* pointeur vers la surface contenant le chipset */ unsigned int width; /* largeur de la carte */ unsigned int height; /* hauteur de la carte */ unsigned int *data; /* pointeur vers la carte (allocation dynamique) */ unsigned int offsetX; /* décalage sur l'axe X lors de l'affichage de la carte */ unsigned int offsetY; /* décalage sur l'axe Y lors de l'affichage de la carte */ FMUSIC_MODULE *bg_sound; /* pointeur vers la musique de fond jouée pendant cette carte */ };
Les deux éléments importants sont chipset qui est un tableau de n_tile éléments et data qui est un tableau de width*height éléments. Ce dernier tableau contient la carte proprement dite, c'est à dire pour chaque case quelle tuile va-t-on afficher.
Profitons-en pour définir une macro qui va simplifier l'accès à une case de notre tableau data:
# define GET_TILE(x,y,map) map->data[x+y*map->width][modifier] Initialisation et libération de la structure
L'initialisation de la carte consiste tout simplement à mettre les pointeurs à NULL et les valeurs numériques à 0.
void InitMap(struct s_map *map) { map->n_tile=map->width=map->height=map->offsetX=map->offsetY=0; map->chipset=NULL; map->surf_chipset=NULL; map->bg_sound=NULL; }
Lors de la libération de la carte il faut:
- libérer la surface qui contient le chipset
- libérer les tableaux data et chipset qui sont alloués dynamiquements
- stopper la musique de fond et la libérer
void FreeMap(struct s_map *map) { SDL_FreeSurface(map->surf_chipset); free(map->data); free(map->chipset); FMUSIC_StopSong(map->bg_sound); FMUSIC_FreeSong(map->bg_sound); }
[modifier] Génération d'une carte aléatoire
Pour générer une carte aléatoire, nous allons:
- initialiser la carte
- charger la surface (le chemin vers le chipset est passé en paramètre)
- on fixe la couleur de transparence pour la surface
- on détermine le nombre de tiles que l'image comporte
- on alloue le tableau chipset qui contient les caractéristiques de chaque tile
- on le remplit avec chaque tile présent dans l'image
- on alloue le tableau data et on le remplit avec des valeurs aléatoires.
void RandomizeMap(struct s_map *map, int width, int height, char *chipset) { int i,x,y; InitMap(map); map->surf_chipset=SDL_LoadBMP(chipset); SDL_SetColorKey(map->surf_chipset, SDL_SRCCOLORKEY, SDL_MapRGB(map->surf_chipset->format,8,33,82)); map->n_tile=(map->surf_chipset->w/TILE_WIDTH)*(map->surf_chipset->h/TILE_HEIGHT); map->chipset=(struct s_chipset*)calloc((map->n_tile),sizeof(struct s_chipset)); x=y=0; for(i=0;i<map->n_tile;i++) { map->chipset[i].x=x; map->chipset[i].y=y; map->chipset[i].collision=0; map->chipset[i].nextTile=0; x += TILE_WIDTH; if(x>(map->surf_chipset->w-TILE_WIDTH)) { x=0; y += TILE_HEIGHT; } } map->width = width; map->height = height; map->data =(unsigned int*) malloc(map->width*map->height*sizeof(unsigned int)); for(x=0;x<map->width;x++) for(y=0;y<map->height;y++) map->data[x+y*map->width]=rand()%(map->n_tile); }
[modifier] Affichage d'une carte
L'affichage de la carte est sans conteste le point le plus difficile car il faut gérer le déplacement de la caméra. On parcours l'écran de 32 pixels en 32 pixels en y blittant (copiant) un morceau de l'image contenant le chipset.
void HandleMap(struct s_map *map) { int x,y, tx,ty; SDL_Rect src, dest; x=-(map->offsetX%TILE_WIDTH); y=-(map->offsetY%TILE_HEIGHT); tx=map->offsetX/TILE_WIDTH; ty=map->offsetY/TILE_HEIGHT; while(y<600) { x=-(map->offsetX%TILE_WIDTH); tx=map->offsetX/TILE_WIDTH; while(x<800) { src.x=map->chipset[GET_TILE(tx,ty,map)].x; src.y=map->chipset[GET_TILE(tx,ty,map)].y; dest.x=x; dest.y=y; src.w=dest.w=TILE_WIDTH; src.h=dest.h=TILE_HEIGHT; if(dest.x<0) { src.x-=dest.x; src.w+=dest.x; dest.w+=dest.x; dest.x=0; } if(dest.y<0) { src.y-=dest.y; src.h+=dest.y; dest.h+=dest.h; dest.y=0; } SDL_BlitSurface(map->surf_chipset,&src,SDL_GetVideoSurface(),&dest); x+=TILE_WIDTH; tx++; } y+=TILE_HEIGHT; ty++; } }
[modifier] Sauvegarde d'une carte
Il est évident que toutes les cartes ne seront pas "codée" en dur dans notre programme, il faudra les charger à partir de fichiers externes. Pour cela, on peut utiliser les fonctions d'entrée/sortie haut niveau du C.
Il s'agit ici de lire l'ensemble du contenu de la carte, y compris les tableaux alloués, et de l'écrire dans un fichier.
void SaveMap(struct s_map *map, char *filename, char *bg_sound, char *chipset) { FILE *f; int i,x,y; char buffer[256]; if(!map) return; f=fopen(filename,"wb"); if(!f) return; memset(&buffer,0,sizeof(buffer)); strcpy(buffer,bg_sound); fwrite(&buffer,255,sizeof(char),f); memset(&buffer,0,sizeof(buffer)); strcpy(buffer,chipset); fwrite(&buffer,255,sizeof(char),f); fwrite(&map->n_tile,1,sizeof(unsigned int),f); fwrite(&map->width,1,sizeof(unsigned int),f); fwrite(&map->height,1,sizeof(unsigned int),f); fwrite(&map->offsetX,1,sizeof(unsigned int),f); fwrite(&map->offsetY,1,sizeof(unsigned int),f); for(i=0;i<map->n_tile;i++) { fwrite(&map->chipset[i].x, 1, sizeof(unsigned int),f); fwrite(&map->chipset[i].y, 1, sizeof(unsigned int),f); fwrite(&map->chipset[i].collision, 1, sizeof(unsigned char),f); fwrite(&map->chipset[i].nextTile, 1, sizeof(unsigned int),f); } for(x=0;x<map->width;x++) for(y=0;y<map->height;y++) fwrite(&GET_TILE(x,y,map), 1, sizeof(unsigned int),f); fclose(f); }
[modifier] Chargement d'une carte
Le chargement d'une carte est l'exacte fonction inverse de la fonction d'écriture, il faut juste en plus allouer les tableaux, charger le chipset et définir la couleur de transparence.
void LoadMap(struct s_map *map, char *filename) { FILE *f; int i,x,y; char buffer[256]; if(!map) return; f=fopen(filename,"rb"); if(!f) return; /* BG SOUND */ memset(&buffer,0,sizeof(buffer)); fread(&buffer,255,sizeof(char),f); map->bg_sound = FMUSIC_LoadSong(buffer); if (!map->bg_sound) fprintf(stderr,"Erreur lors du chargement de la carte: %s\n", FMOD_ErrorString(FSOUND_GetError())); /* CHIPSET */ memset(&buffer,0,sizeof(buffer)); fread(&buffer,255,sizeof(char),f); map->surf_chipset=SDL_LoadBMP(buffer); SDL_SetColorKey(map->surf_chipset, SDL_SRCCOLORKEY, SDL_MapRGB(map->surf_chipset->format,8,33,82)); fread(&map->n_tile,1,sizeof(unsigned int),f); fread(&map->width,1,sizeof(unsigned int),f); fread(&map->height,1,sizeof(unsigned int),f); fread(&map->offsetX,1,sizeof(unsigned int),f); fread(&map->offsetY,1,sizeof(unsigned int),f); map->chipset=malloc((map->n_tile)*sizeof(struct s_chipset)); for(i=0;i<map->n_tile;i++) { fread(&map->chipset[i].x, 1, sizeof(unsigned int),f); fread(&map->chipset[i].y, 1, sizeof(unsigned int),f); fread(&map->chipset[i].collision, 1, sizeof(unsigned char),f); fread(&map->chipset[i].nextTile, 1, sizeof(unsigned int),f); } map->data = calloc(map->width*map->height,sizeof(unsigned int)); for(x=0;x<map->width;x++) for(y=0;y<map->height;y++) { fread(&GET_TILE(x,y,map), 1, sizeof(unsigned int),f); if(GET_TILE(x,y,map)>=map->n_tile) { fprintf(stderr,"Carte corrompue! (%d,%d)\n",x,y); GET_TILE(x,y,map)=0; } } fclose(f); FMUSIC_PlaySong(map->bg_sound); }
[modifier] Vers un programme utilisable... la boucle de messages
Revenons désormais à main.c. Il est temps d'ajouter une boucle de jeu sommaire de manière à pouvoir tester notre programme.
Ainsi notre fonction main() va désormais ressembler à ceci:
int main(int argc, char **argv) { SDL_Event event; struct s_map map; int isdone; if(Init()>0) return 1; RandomizeMap(&map,100,100,"tiles\\mchip0.bmp"); SaveMap(&map, "map\\save.map", "musique\\Opening1.mid", "tiles\\mchip0.bmp"); isdone=0; while (!isdone) { /* Lecture des évènements dans la queue d'évènements */ while (SDL_PollEvent (&event)) { MapEvent(&event, &map); switch (event.type) { case SDL_KEYDOWN: if(event.key.state==SDL_PRESSED) { if(event.key.keysym.sym==SDLK_ESCAPE) isdone = 1; else if (event.key.keysym.sym==SDLK_F1) SDL_SaveBMP(SDL_GetVideoSurface(),"screenshot.bmp"); } break; case SDL_QUIT: isdone = 1; break; } } HandleMap(&map); SDL_Flip(SDL_GetVideoSurface()); } FreeMap(&map); return 0;
Et dans map.c, la fonction MapEvent va gérer les messages qui intéressent notre système de carte:
void MapEvent(SDL_Event *event, struct s_map *map) { if(!event) return; switch(event->type) { case SDL_KEYDOWN: if(event->key.state==SDL_PRESSED) { if(event->key.keysym.sym==SDLK_RIGHT) { if (map->offsetX<(map->width*TILE_WIDTH-800)) map->offsetX++; } else if(event->key.keysym.sym==SDLK_LEFT) { if (map->offsetX!=0) map->offsetX--; } else if(event->key.keysym.sym==SDLK_DOWN) { if (map->offsetY<(map->height*TILE_HEIGHT-600)) map->offsetY++; } else if(event->key.keysym.sym==SDLK_UP) { if (map->offsetY!=0) map->offsetY--; } } break; } }
[modifier] Démo 1: gestion de la carte avec scrolling
Cette première démo permet déjà de générer une carte aléatoire, de l'afficher et de la faire défiler en utilsant les flèches directionnelles.
Catégories: C | SDL | FMOD


