Codage d’un jeu de plateforme avec SDL 2.0 Formation C Codage d’un jeu de plateforme avec SDL 2.0
Sommaire Présentation et installation de la SDL2.0 Principes de base Gestion du monde Mouvements du personnage Animations
La SDL 2.0 Bibliothèque 2D pour le jeu vidéo Écrite en C Libre et gratuite Gestion d’images, de son Prise en charge de manettes Utilisation de la carte graphique
Installation de la SDL Suivre le tutoriel de developpez.com (http://alexandre- laurent.developpez.com/tutoriels/sdl-2/installation-et-configuration/) Projets préconfigurés pour CodeBlocks sur windows et makefile pour linux Rajouter l’option -std=c99 (project build options/compiler settings/other options sous code blocks)
Principes de base Boucle de jeux initialisations update Boucle principale draw free
Initialisation de la SDL int main(int argc, char** argv) { /* initialisation de la SDL */ if (SDL_Init(SDL_INIT_VIDEO) != 0 ) fprintf(stdout,"Échec de l'initialisation de la SDL (%s)\n",SDL_GetError()); return -1; } else /* Création de la fenêtre */ SDL_Window* window = NULL; window = SDL_CreateWindow("Ma première application SDL2",SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, SDL_WINDOW_SHOWN); //création du renderer SDL_Renderer* renderer = NULL; renderer = SDL_CreateRenderer( window, 0, SDL_RENDERER_ACCELERATED); /* Initialisations */ if(window) /* Boucle principale */ /* Destructions */ /* Destruction de la fenêtre */ SDL_DestroyWindow(window); fprintf(stderr,"Erreur de création de la fenêtre: %s\n",SDL_GetError()); /* Fermeture de la SDL */ SDL_Quit(); return 0;
Gestion du temps SDL_GetTicks() : temps écoulé depuis le lancement du programme (en ms) SDL_Delay(int time) : met en pause durant la durée time (en ms) Framerate cherché : 60 fps Attente entre 2 frames pour limiter le framerate frameTime = SDL_GetTicks(); /* update et draw */ currentTime = SDL_GetTicks(); if(currentTime - frameTime < 1000/60) SDL_Delay(1000/60-currentTime+frameTime);
Principes de base Structure du code main.c le plus léger possible Définit la boucle de jeu et délègue Préfixes ou suffixe pour les noms de fonctions (exemple : updatePlayer() ou player_update()) Choisir une convention de nommage et s’y tenir Coder en anglais ou en français, mais pas les deux
Principes de base Gestion des entités Utilisation de structures : Instantiation et destruction via des fonctions typedef struct Player { float x, y, w, h, vx, vy; int isOnGround; } Player; Player* initPlayer(float x, float y, float w, float h) { Player* player = malloc(sizeof(Player)); player->x = x; player->y = y; player->w = w; player->h = h; player->vx = 0; player->vy = 0; player->isOnGround = 0; return player; } void freePlayer(Player *player) free(player);
L’affichage avec SDL Utilisation du renderer SDL_SetRenderDrawColor : choisit la couleur à utiliser Arguments : renderer et composantes RGBA de la couleur SDL_RenderFillRect : dessine un rectangle avec la couleur choisie Arguments : SDL_Renderer et SDL_Rect Autres fonctions : SDL_RenderDrawLine, SDL_RenderDrawPoint, SDL_RenderClear void drawRect(SDL_Renderer *renderer, int x, int y, int w, int h) { SDL_Rect rect; rect.x = x; rect.y = y; rect.w = w; rect.h = h; SDL_RenderFillRect(renderer, &rect); }
L’affichage avec SDL Utilisation du renderer SDL_SetRenderDrawColor : choisit la couleur à utiliser Arguments : renderer et composantes RGBA de la couleur SDL_RenderFillRect : dessine un rectangle avec la couleur choisie Arguments : SDL_Renderer et SDL_Rect Autres fonctions : SDL_RenderDrawLine, SDL_RenderDrawPoint, SDL_RenderClear void drawRect(SDL_Renderer *renderer, int x, int y, int w, int h) { SDL_Rect rect; rect.x = x; rect.y = y; rect.w = w; rect.h = h; SDL_RenderFillRect(renderer, &rect); } ATTENTION : l’axe y est vers le bas 800 600
Affichage du monde Monde = quadrillages avec cases remplies ou non
Affichage du monde Monde = tableau 2D hTile wTile h w
Affichage du monde Tableau 2D de dimensions (w, h) = tableau 1D de longueur w*h 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 id ligne i colonne j = i*w + j Colonne de la case k = k%w Ligne de la case k = k/w
Gestion du monde #ifndef WORLD_H_INCLUDED #define WORLD_H_INCLUDED typedef struct World { int *tiles; int w, h, wTile, hTile; } World; World* initWorld(int w, int h, int wTile, int hTile); void freeWorld(World *world); void setTile(World *world, int x, int y, int type); int getTile(World *world, int x, int y); void drawWorld(World *world, SDL_Renderer *renderer); #endif // WORLD_H_INCLUDED World* initWorld(int w, int h, int wTile, int hTile) { World* world = malloc(sizeof(World)); world->tiles = malloc(sizeof(int)*w*h); for(int i=0; i<w*h; i++) world->tiles[i] = 0; world->w = w; world->h = h; world->wTile = wTile; world->hTile = hTile; } void freeWorld(World *world) free(world->tiles); free(world); void setTile(World *world, int x, int y, int type) { world->tiles[x+world->w*y] = type; } int getTile(World *world, int x, int y) return world->tiles[x+world->w*y];
Gestion du monde void drawWorld(World *world, SDL_Renderer* renderer) hTile void drawWorld(World *world, SDL_Renderer* renderer) { SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); for(int i=0; i<world->w; i++) for(int j=0; j<world->h; j++) if(getTile(world, i, j)) drawRect(renderer, world->wTile*i, world->hTile*j, world->wTile, world->hTile); } wTile j i
Gestion des événements Utilisation de la structure SDL_Event SDL_PollEvent : remplit un SDL_Event Traite le plus vieil événement non traité Renvoie 0 si tous les événements ont été traités Utilisation dans une boucle : SDL_Event event; while(SDL_PollEvent(&event)) { /* Traitement de l’événement */ }
Gestion des événements SDL_Event est une structure Event.type donne le type d’événement : SDL_WINDOWEVENT : événement concernant la fenêtre SDL_MOUSEBUTTONDOWN/ SDL_MOUSEBUTTONUP : bouton de la souris appuyé/relâché SDL_KEYDOWN/SDL_KEYUP : touche du clavier enfoncée/relâchée
Gestion des événements SDL_WINDOWEVENT : event.window.event donne le type d’événement (fermer la fenêtre, agrandissement, etc) SDL_MOUSEBUTTONDOWN/ SDL_MOUSEBUTTONUP event.button.button donne l’id du bouton enfoncé Clic gauche => 1, clic droit => 3 SDL_KEYDOWN/SDL_KEYUP event.key.keysym.sym donne le code de la touche appuyée Les codes des touches sont définies dans des macros (SDLK_SPACE, SDLK_RIGHT, etc)
Gestion des événements void updateInput(int *exitGame, World *world, Player *player) { SDL_Event event; while(SDL_PollEvent(&event)) switch(event.type) case SDL_WINDOWEVENT: if(event.window.event == SDL_WINDOWEVENT_CLOSE) *exitGame = 1; break; case SDL_MOUSEBUTTONDOWN: if(event.button.button == 1) int tileId = getTile(world, event.button.x / world->wTile, event.button.y / world->hTile); setTile(world, event.button.x / world->wTile, event.button.y / world->hTile, 1-tileId); } break; } Gestion des événements
Gestion du personnage Variables importantes : Player* initPlayer(float x, float y, float w, float h) { Player* player = malloc(sizeof(Player)); player->x = x; player->y = y; player->w = w; player->h = h; player->vx = 0; player->vy = 0; player->isOnGround = 0; player->walkDir = 0; return player; } void freePlayer(Player *player) free(player); Variables importantes : Position x et y Dimensions Vitesse x et y (vx et vy) Deux autres valeurs seront nécessaires : isOnGround, pour savoir si on peut sauter walkDir donne la direction appuyée (-1 à gauche, 1 à droite et 0 si pas de touche appuyée) typedef struct Player { float x, y, w, h, vx, vy; int isOnGround, walkDir; } Player;
Gestion du personnage Constantes à définir : Gravité Accélération lors de la marche Vitesse de marche maximum Le coefficient de ralentissement (friction) Force des sauts #define MAX_FALL_SPEED 10 #define GRAVITY 0.5 #define ACCELERATION 1 #define MAX_WALK_SPEED 10 #define FRICTION 0.90 #define JUMP_STRENGTH 10
Gestion du personnage void updatePlayer(Player* player, World *world) { player->vx += player->walkDir*ACCELERATION; if(player->vx > MAX_WALK_SPEED) player->vx = MAX_WALK_SPEED; if(player->vx < -MAX_WALK_SPEED) player->vx = -MAX_WALK_SPEED; player->vx *= FRICTION; if(player->vy < MAX_FALL_SPEED) player->vy += GRAVITY; player->x += player->vx; player->y += player->vy; } void jump(Player *player) { if(player->isOnGround) player->vy -= JUMP_STRENGTH; } void drawPlayer(SDL_Renderer *renderer, Player* player) { SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255); drawRect(renderer, (int)player->x, (int)player->y, (int)player->w, (int)player->h); }
Gestion des collisions v v v v
Gestion des collisions ? v
Gestion des collisions Solution : décomposition du mouvement vx vx v vx vx vx
Gestion des collisions void updatePlayer(Player* player, World *world) { player->vx += player->walkDir*ACCELERATION; if(player->vx > MAX_WALK_SPEED) player->vx = MAX_WALK_SPEED; if(player->vx < -MAX_WALK_SPEED) player->vx = -MAX_WALK_SPEED; player->vx *= FRICTION; if(player->vy < MAX_FALL_SPEED) player->vy += GRAVITY; player->x += player->vx; checkCollisionsX(player, world); player->y += player->vy; checkCollisionsY(player, world); } void checkCollisionsX(Player *player, World* world) { int topY = (player->y+1)/world->hTile; int bottomY = (player->y+player->h-1)/world->hTile; int leftX = player->x/world->wTile; int rightX = (player->x+player->w)/world->wTile; for(int j=topY; j<=bottomY; j++) if(player->vx < 0 && getTile(world, leftX, j)) player->x = leftX*world->hTile+world->wTile; player->vx = 0; } if(player->vx > 0 && getTile(world, rightX, j)) player->x = rightX*world->hTile-player->w;
Gestion des collisions void updatePlayer(Player* player, World *world) { player->vx += player->walkDir*ACCELERATION; if(player->vx > MAX_WALK_SPEED) player->vx = MAX_WALK_SPEED; if(player->vx < -MAX_WALK_SPEED) player->vx = -MAX_WALK_SPEED; player->vx *= FRICTION; if(player->vy < MAX_FALL_SPEED) player->vy += GRAVITY; player->x += player->vx; checkCollisionsX(player, world); player->y += player->vy; checkCollisionsY(player, world); } void checkCollisionsY(Player *player, World* world) { player->isOnGround = 0; int topY = player->y/world->hTile; int bottomY = (player->y+player->h)/world->hTile; int leftX = (player->x+1)/world->wTile; int rightX = (player->x+player->w-1)/world->wTile; for(int i=leftX; i<=rightX; i++) if(player->vy < 0 && getTile(world, i, topY)) player->y = topY*world->hTile+world->hTile; player->vy = 0; } if(player->vy > 0 && getTile(world, i, bottomY)) player->y = bottomY*world->hTile-player->h; player->isOnGround = 1;
Affichage d’images Chargement de bitmap : SDL_Surface* SDL_LoadBMP(char *path); Autres format : voir SDL_Image Générer une texture à partir de la SDL_Surface : SDL_CreateTextureFromSurface(SDL_Renderer* renderer, SDL_Surface *image)
Affichage d’images SDL_RenderCopyEx(SDL_Renderer *renderer, SDL_Texture *texture, SDL_Rect *src, SDL_Rect *dst, float rotation, SDL_Point *rotationCenter, int flip); SDL_FLIP_HORIZONTAL, SDL_FLIP_VERTICAL ou SDL_FLIP_NONE
Affichage d’images : les Sprites
Affichage d’images : les Sprites src dst
Animation du personnage #define MAX_WALK_SPEED 5 #define FRICTION 0.90 #define JUMP_STRENGTH 5 #define ANIM_SPEED 6 #define W_IMG 48 #define H_IMG 48 typedef struct Player { float x, y, w, h, vx, vy; int isOnGround, walkDir; SDL_Texture* texture; SDL_Surface* image; int animX, animY, animTime, animDir; } Player; animX animY
Animation du personnage Player* initPlayer(SDL_Renderer* renderer, float x, float y, float w, float h) { Player* player = malloc(sizeof(Player)); player->x = x; player->y = y; player->w = w; player->h = h; player->vx = 0; player->vy = 0; player->isOnGround = 0; player->walkDir = 0; player->image = SDL_LoadBMP("img/player.bmp"); SDL_SetColorKey( player->image, SDL_TRUE, SDL_MapRGB(player->image->format, 255, 0, 255)); player->texture = SDL_CreateTextureFromSurface(renderer, player->image); player->animX = 0; player->animY = 0; player->animTime = 0; return player; } Couleur transparente void freePlayer(Player *player) { SDL_DestroyTexture(player->texture); SDL_FreeSurface(player->image); free(player); }
Animation du personnage au sol void updatePlayer(Player* player, World *world) { /* Animation au sol */ if(player->isOnGround) player->animY = 0; if(player->walkDir == 0) player->animX = 6; } else player->animTime++; if(player->animTime > ANIM_SPEED) player->animTime = 0; player->animX++; if(player->animX >= 6) player->animX = 0; /* … */ 1 2 3 4 5 6
Animation du personnage au sol /* … */ else { player->animY = 3; player->animTime++; if(player->animTime > ANIM_SPEED) player->animTime = 0; player->animX++; if(player->animX >= 3) player->animX = 0; } 1 2 3
Affichage du personnage if(event.key.keysym.sym == SDLK_RIGHT) { player->animDir = 1; player->walkDir = 1; } else if(event.key.keysym.sym == SDLK_LEFT) player->animDir = -1; player->walkDir = -1; void drawPlayer(SDL_Renderer *renderer, Player* player) { SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255); SDL_Rect srcrect = {player->animX*W_IMG, player->animY*H_IMG, W_IMG, H_IMG}; SDL_Rect dstrect = {(int)player->x, (int)player->y, (int)player->w, (int)player->h}; SDL_Point center = {0, 0}; if(player->animDir == -1) SDL_RenderCopyEx(renderer, player->texture, &srcrect, &dstrect, 0, ¢er, SDL_FLIP_HORIZONTAL); else SDL_RenderCopyEx(renderer, player->texture, &srcrect, &dstrect, 0, ¢er, SDL_FLIP_NONE); }