Big Tuto SDL 2 : Rabidja v. 3.0
Chapitre 5 : Avec un background, c'est mieux !
Tutoriel présenté par : Jérémie F. Bellanger (Jay81)
Dernière mise à jour : 29 juin 2015
Date de révision : 30 mai 2016
Prologue
Eh, voilà ! Quelle magnificence ! Quelle fenêtre ! Mes aïeux !
Que... quoi ?! Vous trouvez que le monochrome noir, c'est moche ! Mince alors ! Moi, qui espérais m'arrêter ici !
Non, je plaisante ! Bien sûr qu'on va la remplir notre bonne vieille fenêtre ! Et pour vous montrer ma détermination, on va tout de suite se lancer dans l'affichage du background !
Mais c'est quoi le background ?
Non, ne me dites pas que vous êtes nul en anglais, à ce point là !?! Ah, ça me rassure ! Eh bien, ça va tout simplement être le décor de fond de notre map, qui va rester statique.
Pour cela, il va donc nous falloir apprendre à charger, puis décharger une image à partir d'un fichier, et à l'afficher ! Non, ça ne va pas être bien dur.
Alors, on y va ! Banzaï !
Quoi ? Stop ?
Ah oui, j'allais oublier ! Comment peut-on afficher un background si on n'en a pas ?
Deux solutions s'offrent à vous : soit vous dessinez le vôtre (format 800 x 480 pixels, la taille de notre fenêtre), soit vous prenez celui-là et vous le copiez dans le répertoire de votre projet dans le dossier graphics, sous le nom background.png (c'est important de respecter ce nom pour pouvoir le charger dans notre programme ensuite):
background.png (faites en clic droit pour sauvegarder l'image )
Mais, il est plus grand que 800 x 480, ce background ?!?
Eh oui, mon cher Watson ! En effet, il est 2 fois plus long, car il peut aussi se mettre en boucle et défiler en parallaxe avec la map pour donner une impression de profondeur, fort sympathique !
Mais, on verra tout ça plus loin. Pour l'instant, on va se contenter de l'afficher tel quel, et on n'en verra donc que la moitié (mais est-ce la meilleure moitié ? - Bon, faut arrêter de se poser des questions là ! ).
Le code
Nous allons commencer par introduire une nouvelle structure, dans notre header (en-tête) structs.h :
Fichier : structs.h
// Structure pour gérer la map à afficher (à compléter plus tard)
typedef struct Map
{
SDL_Texture *background;
} Map;
|
Il s'agit de la structure Map, que nous utiliserons, bien entendu dans le fichier du même nom (map.c) et qui contiendra les variables relatives à notre map.
Mais c'est quoi une map ?
Cela veut dire carte, en anglais, et cela représentera nos niveaux, car comme sur une carte, nous aurons différents éléments placés à diverses coordonnées bien précises.
Ici, notre première variable sera une SDL_Texture qui contiendra l'image de notre background ci-dessus.
Passons à notre main().
Fichier : main.c
//Rabidja 3 - nouvelle version intégralement en SDL 2.0
//Copyright / Droits d'auteur : www.meruvia.fr - Jérémie F. Bellanger
#include "prototypes.h"
/* Déclaration des variables / structures utilisées par le jeu */
Input input;
int main(int argc, char *argv[])
{
unsigned int frameLimit = SDL_GetTicks() + 16;
int go;
// Initialisation de la SDL
init("Rabidja 3 - SDL 2 - www.meruvia.fr");
// Chargement des ressources (graphismes, sons)
loadGame();
// Appelle la fonction cleanup à la fin du programme
atexit(cleanup);
go = 1;
// Boucle infinie, principale, du jeu
while (go == 1)
{
//Gestion des inputs clavier
gestionInputs(&input);
//On dessine tout
drawGame();
// Gestion des 60 fps (1000ms/60 = 16.6 -> 16
delay(frameLimit);
frameLimit = SDL_GetTicks() + 16;
}
// On quitte
exit(0);
}
|
Vous pouvez supprimer le code précédent et le remplacer par celui-là.
Vous noterez simplement 2 changements : on a 2 nouvelles fonctions :
- loadGame() qui aura pour tâche de charger toutes les ressources de notre jeu (pour l'instant, simplement le background ).
- drawGame() qui affichera toutes les tiles et tous les sprites du jeu (soit tous les éléments graphiques).
Tile, sprite ? Kézako ?
En général, un sprite est un élément mobile du jeu qui interagit avec d'autres sprites (par exemple : le héros, les monstres, les power-ups, etc.). Au contraire, une tile est un élément statique du décor représentant une petite partie du niveau (nos tiles feront par exemple 32x32 pixels, ce qui est une taille standard dans l'industrie du jeu vidéo ). Pour plus d'infos sur le tilemapping, je vous invite à lire cet autre tuto.
Créons désormais le fichier map.c qui se chargera de gérer notre map !
Ajoutez donc un nouveau fichier vide et nommez-le map.c. Copiez-y le code ci-dessous :
Fichier : map.c
#include "prototypes.h"
Map map;
void initMaps(void)
{
// Charge l'image du fond (background)
map.background = loadImage("graphics/background.png");
}
SDL_Texture *getBackground(void)
{
return map.background;
}
void cleanMaps(void)
{
// Libère la texture du background
if (map.background != NULL)
{
SDL_DestroyTexture(map.background);
map.background = NULL;
}
}
|
Voilà, ce fichier n'est pas encore bien conséquent (ça va venir ) mais il contient ce dont nous avons besoin.
D'abord, vous voyez que nous déclarons notre structure map de type Map, tout en haut, que nous avons déjà définie dans defs.h.
Ensuite, nous créons 3 nouvelles fonctions (dont nous devrons ensuite ajouter les prototypes dans le fichier prototypes.h) :
- initMaps() se chargera d'initialiser toutes les variables nécessaires au bon fonctionnement de notre map. Pour l'instant, elle se chargera simplement de charger l'image du background dans notre variable map.background à l'aide de la fonction loadImage() que nous allons écrire ci-après.
- getBackground() renvoie simplement la texture du background. Elle sera pratique pour pouvoir récupérer cette variable depuis un autre fichier, car autrement on n'aurait pas eu le droit d'y accéder vu qu'elle est "encapsulée" dans ce fichier map.c. C'est une pratique qui sera familière à ceux qui programment déjà de façon orientée objet (POO) avec des langages comme le C++, le C# ou le Java (entre autres).
- enfin, cleanMaps() fera le ménage à la fin du programme en supprimant de la mémoire notre fichier map.background. (C'est très important de le faire en C, car il n'y a pas de garbage collector comme dans d'autres langages plus sophistiqués ).
Passons maintenant au fichier init.c !
Là, nous allons créer notre fonction loadGame() qui va charger notre jeu (en faisant ici simplement appel à initMaps() pour l'instant).
Fichier : init.c
void loadGame(void)
{
//On charge les données pour la map
initMaps();
}
|
Nous allons également compléter notre fonction cleanup(), en rajoutant un appel à cleanMaps() tout en haut :
Fichier : init.c
void cleanup()
{
//Nettoie les sprites de la map
cleanMaps();
|
Bien, il ne nous reste plus maintenant qu'à gérer la partie graphique de notre programme dans le fichier draw.c. Supprimez la fonction drawGame() (seulement celle-là !) puis copiez/collez le code suivant :
Fichier : draw.c
void drawGame(void)
{
// Affiche le fond (background) aux coordonnées (0,0)
drawImage(getBackground(), 0, 0);
// Affiche l'écran
SDL_RenderPresent(getrenderer());
// Délai pour laisser respirer le proc
SDL_Delay(1);
}
SDL_Texture *loadImage(char *name)
{
/* Charge les images avec SDL Image dans une SDL_Surface */
SDL_Surface *loadedImage = NULL;
SDL_Texture *texture = NULL;
loadedImage = IMG_Load(name);
if (loadedImage != NULL)
{
// Conversion de l'image en texture
texture = SDL_CreateTextureFromSurface(getrenderer(), loadedImage);
// On se débarrasse du pointeur vers une surface
SDL_FreeSurface(loadedImage);
loadedImage = NULL;
}
else
printf("L'image n'a pas pu être chargée! SDL_Error : %s\n", SDL_GetError());
return texture;
}
void drawImage(SDL_Texture *image, int x, int y)
{
SDL_Rect dest;
/* Règle le rectangle à dessiner selon la taille de l'image source */
dest.x = x;
dest.y = y;
/* Dessine l'image entière sur l'écran aux coordonnées x et y */
SDL_QueryTexture(image, NULL, NULL, &dest.w, &dest.h);
SDL_RenderCopy(getrenderer(), image, NULL, &dest);
}
|
Commençons par drawGame() : vous aurez remarqué qu'on ne remplit plus l'écran en noir (même si on aurait pu le laisser avant, sans que cela ne change grand chose ), mais qu'à la place, on appelle drawImage() pour afficher le background, renvoyé par notre fonction précédente getBackground().
loadImage() prend en argument l'emplacement du fichier à charger (si vous avez une erreur, ou un écran noir, commencez d'ailleurs par vérifier que votre image est au bon endroit et qu'elle a le bon nom ), elle charge ensuite le fichier comme une SDL_Surface en 2D (comme pour la SDL 1.2) avant de la convertir en texture 3D à partir du format du renderer. L'image sera ainsi optimisée pour coller à votre renderer (qui peut par exemple recalculer la dimension des images pour faire du plein écran) et être gérée par votre grosse carte graphique (au boulot, ma vieille ! ). La SDL_Surface est ensuite supprimée de la mémoire (important !) et la fonction renvoie la texture.
drawImage() enfin va nous servir à dessiner nos images. Cette fonction est très basique (nous en créerons une plus complexe plus tard pour gérer les découpages, les flips et les rotations de sprites ) : elle prend une texture et ses coordonnées x et y en arguments et elle blitte (=colle) ensuite l'image toute entière auxdites coordonnées.
Et voilà !
N'oublions pas maintenant de compléter notre fichier prototypes.h avec nos nouveaux prototypes. Je vous redonne le fichier complet ci-dessous :
Fichier : prototypes.h
#ifndef PROTOTYPES
#define PROTOTYPES
#include "structs.h"
/* Catalogue des prototypes des fonctions utilisées.
On le complétera au fur et à mesure. */
extern void cleanMaps(void);
extern void cleanup(void);
extern void delay(unsigned int frameLimit);
extern void drawGame(void);
extern void drawImage(SDL_Texture *, int, int);
extern void drawMap(int);
extern void gestionInputs(Input *input);
extern SDL_Texture *getBackground(void);
extern void getInput(Input *input);
extern SDL_Renderer *getrenderer(void);
extern void init(char *);
extern void initMaps(void);
extern void loadGame(void);
extern SDL_Texture *loadImage(char *name);
#endif
|
Compilez désormais le programme puis lancez-le et TADAAA !
Notre magnifique background s'affiche dans toute l'immensité de sa beauté ! (J'exagère à peine ! )
@ bientôt pour le chapitre 6 !
Jay