07/05/2023 : Malheureusement, suite à un manque de financement et à des échéances prochaines impliquant de refaire TOUT le site en l'adaptant à une nouvelle plateforme pour pallier les risques croissants de sécurité, il est pour l'instant prévu que le site ferme prochainement ses portes...
 
13/06/2023 : Finalement, le site a été refinancé pour une nouvelle année complète. En l'état, il est fonctionnel mais notre hébergeur ne nous permet pas une mise à jour vers Joomla 4 ou php 8 (il a été racheté et n'est plus que l'ombre de lui-même...). Cela nous laisse cependant un peu de temps pour trouver une solution.

 

 
 

 

 

Big Tuto SDL 2 : Rabidja v. 3.0

Chapitre 9 : Déplacement et scrolling

 

Tutoriel présenté par : Jérémie F. Bellanger (Jay81)
Ecriture : 12 octobre 2014

Dernière mise à jour : 9 novembre 2015

 

      Prologue

   Bon, c'est bien sympa, on a un héros dans notre jeu ! cool Mais il est nul : il ne sait même pas marcher ! indecision

   Il va donc être temps de remédier au problème ! wink Cela dit, nous allons procéder en plusieurs chapitres car il y a quand même pas mal de points assez complexes à gérer. cheeky

   En effet, il va nous falloir gérer les inputs clavier pour faire se déplacer le héros dans la bonne direction, et puis il va y avoir la gestion de la physique avec la chute et les sauts, et pour cela, il va aussi falloir gérer les collisions avec la map ! surprise En plus de ça, on va aussi devoir créer un système de caméra pour mettre en place le scrolling autour de notre héros (sinon, il va sortir de l'écran et basta ! indecision).

   Comme vous le voyez, ça fait beaucoup de choses ! cheeky Dans ce chapitre, on va donc se limiter aux déplacements gauche / droite du personnage (avec gestion du changement d'animation pour le faire marcher) et on va mettre en place notre scrolling avec un système de caméra, un peu plus avancé que dans le Big Tuto SDL 1.2 / 2, pour éviter le phénomène de motion sickness (déplacements qui rendent malade) ! angel

   Voilà, donc avant de passer au code, vous pouvez charger, comme d'habitude, les archives complètes du jeu ci-dessous wink : 

 

 

      Le code

   Nous allons d'abord commencer par rajouter quelques nouvelles defs dont nous allons avoir besoin par la suite.

   Ouvrez donc le fichier defs.h et rajoutez les valeurs suivantes :

Fichier : defs.h : Rajoutez :

//Constante pour les limites de la caméra avant scrolling
#define LIMITE_X 400
#define LIMITE_Y 220
#define LIMITE_W 100
#define LIMITE_H 80
 
//Constantes définissant la gravité et la vitesse max de chute
#define GRAVITY_SPEED 0.6
#define MAX_FALL_SPEED 15
#define JUMP_HEIGHT 10

   Les 4 premières defs vont nous servir à définir la sous-boîte limite autour de notre personnage dont le franchissement lancera le scrolling de la caméra.

   Nous expliquerons tout cela ci-dessous, en même temps que nous verrons la fonction centerScrollingOnPlayer(), mais sachez que ces valeurs pourront être modifiées ici. wink

   Des 3 valeurs suivantes, seule MAX_FALL_SPEED va nous servir ici (enfin, virtuellement, car nous ne pourrons pas encore tomber dans ce chapitre cheeky). Concrètement, GRAVITY_SPEED correspond à la gravité que nous aurons dans le jeu (vous voyez qu'elle est plutôt légère par rapport à celle de la terre, qui se situerait entre 0,8 et 0,9 - mais cela nous permettra de faire de super sauts ! wink), MAX_FALL_SPEED à la vitesse de chute maxiamale (soit 15 pixels / frame ! surprise) et JUMP_HEIGHT à la valeur de nos sauts quand nous les ajouterons dans le chapitre suivant. wink

   Voilà, passons maintenant au fichier player.c, dans lequel on va trouver l'essentiel des nouveautés de ce chapitre :

Fichier : player.c : Rajoutez :

void updatePlayer(Input *input)
{
//Pour l'instant, on place automatiquement le joueur
//sur le sol à 302 pixels du haut de l'écran, car
//on ne gère pas encore les collisions avec le sol.
player.onGround = 1;
player.y = 302;
 
 
//Si on détecte un appui sur la touche fléchée gauche
if (input->left == 1)
{
//On diminue les coordonnées en x du joueur
player.x -= PLAYER_SPEED;
//Et on indique qu'il va à gauche (pour le flip
//de l'affichage, rappelez-vous).
player.direction = LEFT;
 
 
//Si ce n'était pas son état auparavant et qu'il est bien sur
//le sol (car l'anim' sera différente s'il est en l'air)
if (player.etat != WALK && player.onGround == 1)
{
//On enregistre l'anim' de la marche et on l'initialise à 0
player.etat = WALK;
player.frameNumber = 0;
player.frameTimer = TIME_BETWEEN_2_FRAMES_PLAYER;
player.frameMax = 8;
}
}
 
 
//Si on détecte un appui sur la touche fléchée droite
else if (input->right == 1)
{
//On augmente les coordonnées en x du joueur
player.x += PLAYER_SPEED;
//Et on indique qu'il va à droite (pour le flip
//de l'affichage, rappelez-vous).
player.direction = RIGHT;
 
 
//Si ce n'était pas son état auparavant et qu'il est bien sur
//le sol (car l'anim' sera différente s'il est en l'air)
if (player.etat != WALK && player.onGround == 1)
{
//On enregistre l'anim' de la marche et on l'initialise à 0
player.etat = WALK;
player.frameNumber = 0;
player.frameTimer = TIME_BETWEEN_2_FRAMES_PLAYER;
player.frameMax = 8;
}
}
 
 
//Si on n'appuie sur rien et qu'on est sur le sol, on charge l'animation marquant
//l'inactivité (Idle)
else if (input->right == 0 && input->left == 0 && player.onGround == 1)
{
//On teste si le joueur n'était pas déjà inactif, pour ne pas recharger l'animation
//à chaque tour de boucle
if (player.etat != IDLE)
{
//On enregistre l'anim' de l'inactivité et on l'initialise à 0
player.etat = IDLE;
player.frameNumber = 0;
player.frameTimer = TIME_BETWEEN_2_FRAMES_PLAYER;
player.frameMax = 8;
}
 
}
 
//On gère le scrolling (fonction ci-dessous)
centerScrollingOnPlayer();
 
}

   Et voici notre fonction updatePlayer() (enfin son ébauche, car elle n'est pas finie wink). On avait, en effet, nos fonctions d'initialisation, de nettoyage et de dessin, mais il nous manquait celle de mise à jour ! cheeky

   En fait, elle n'est pas très compliquée, assez répétitive et déjà largement commentée ! wink

   On commence par considérer que le joueur est sur le sol (onGround = 1), et que sa coordonnée y est à 302. Ceci n'est que temporaire et disparaîtra dès le chapitre prochain, quand nous aurons établi les collisions avec la map. En attendant, cela va nous permettre de pouvoir déplacer notre héros, en le considérant comme étant sur le sol, et sans se soucier de la map (il passera donc à-travers les murs, et flottera constamment à 302 pixels du haut de l'écran, qu'il y ait du sol ou non cheeky).

   Vous aurez remarqué que notre fonction prend les inputs en arguments (qu'on lui passera depuis le main wink). On va ainsi pouvoir gérer les déplacements de notre héros en fonction des appuis sur les touches fléchées du clavier.

   3 possibilités s'offrent alors à nous pour l'instant, et à chaque fois, le code est relativement semblable. wink
- 1. On détecte l'appui sur la touche Gauche : dans ce cas, on diminue les coordonnées en x du joueur (pour qu'il recule) de la valeur PLAYER_SPEED, soit la valeur définie en defs (que vous pouvez modifier pour faire aller le perso plus ou moins vite), puis on enregistre sa direction, pour pouvoir blitter la bonne frame dans notre fonction drawPlayer() (cf. chapitre précédent). Enfin, on regarde quelle est la valeur de l'anim' du perso et s'il touche le sol. S'il touche le sol et qu'il n'était pas considéré comme en train de marcher (WALK), on change l'anim' et on l'initialise. Sinon, on ne fait rien, car cela signifie soit qu'il est en l'air (ce sera bientôt possible), auquel cas, on ne va pas le faire marcher dans le vide ! indecision, soit qu'il est déjà en train de marcher, et si on réinitialisait l'anim' à ce moment-là, il resterait coincé sur la première frame (pas top ! laugh).
- 2. On détecte l'appui sur la touche Droite : et c'est exactement la même chose, mais vers la droite : on augmente donc la valeur de xwink
- 3. Soit on ne détecte rien et dans ce cas, le perso doit s'arrêter de bouger, et l'anim' doit être passée à IDLE (= ne fait rien) si elle avait une autre valeur. wink

   Eh voilà, on termine notre fonction par un appel à centerScrollingOnPlayer() qui va gérer la caméra et le scrolling, et que nous allons implémenter de ce pas, à la suite du même fichier :

Fichier : player.c : Rajoutez :

void centerScrollingOnPlayer(void)
{
// Nouveau scrolling à sous-boîte limite :
//Pour éviter les effets de saccades dus à une caméra qui se centre
//automatiquement et constamment
//sur le joueur (ce qui peut en rendre malade certains...), on crée
//une "boîte" imaginaire autour du joueur.
//Quand on dépasse un de ses bords (en haut, en bas, à gauche ou à
//droite), on scrolle.
//Mais là encore, au lieu de centrer sur le joueur, on déplace
//simplement la caméra jusqu'à arriver au joueur.
//On a choisi la valeur de 3 pixels pour pouvoir avoir le plaisir
//d'aller plus vite que le cameraman
//(Cours, Johnny, cours!!). On accélère aussi la vitesse de la caméra
//en cas de chute rapide (pour ne pas
//perdre le joueur de vue non plus.
int cxperso = player.x + player.w / 2;
int cyperso = player.y + player.h / 2;
int xlimmin = getStartX() + LIMITE_X;
int xlimmax = xlimmin + LIMITE_W;
int ylimmin = getStartY() + LIMITE_Y;
int ylimmax = ylimmin + LIMITE_H;
 
 
//Si on dépasse par la gauche, on recule la caméra de 3 pixels (vous
//pouvez modifier cette valeur)
if (cxperso < xlimmin)
{
setStartX(getStartX() - 3);
}
 
 
//Si on dépasse par la droite, on avance la caméra de 3 pixels (vous
//pouvez modifier cette valeur)
if (cxperso > xlimmax)
{
setStartX(getStartX() + 3);
}
 
 
//Si on arrive au bout de la map à gauche, on stoppe le scrolling
if (getStartX() < 0)
{
setStartX(0);
}
 
 
//Si on arrive au bout de la map à droite, on stoppe le scrolling à la
//valeur Max de la map - la moitié d'un écran (pour ne pas afficher du noir).
else if (getStartX() + SCREEN_WIDTH >= getMaxX())
{
setStartX(getMaxX() - SCREEN_WIDTH);
}
 
 
//Si on dépasse par le haut, on remonte la caméra de 3 pixels (vous
//pouvez modifier cette valeur)
if (cyperso < ylimmin)
{
setStartY(getStartY() - 3);
}
 
 
//Si on dépasse par le bas, on descend la caméra de 3 pixels (vous
//pouvez modifier cette valeur)
if (cyperso > ylimmax)
{
//Sauf si on tombe très vite, auquel cas, on accélère la caméra :
if (player.dirY >= MAX_FALL_SPEED - 2)
{
setStartY(getStartY() + MAX_FALL_SPEED + 1);
}
else
{
setStartY(getStartY() + 3);
}
}
 
 
//Si on arrive au bout de la map en haut, on stoppe le scrolling
if (getStartY() < 0)
{
setStartY(0);
}
 
 
//Si on arrive au bout de la map en bas, on stoppe le scrolling à la
//valeur Max de la map - la moitié d'un écran (pour ne pas afficher du noir).
else if (getStartY() + SCREEN_HEIGHT >= getMaxY())
{
setStartY(getMaxY() - SCREEN_HEIGHT);
}
 
}

   Là encore, j'ai largement commenté la fonction ! cheeky

   Si vous comparez avec la fonction équivalente du Big Tuto SDL 1.2 / 2, vous remarqeurez qu'elle est largement plus complexe ! wink En effet, à l'époque, on se contentait de calculer le centre de notre personnage et d'adapter la caméra (et donc le scrolling) autour. C'est simple, mais l'inconvénient c'est que la caméra se déplace par saccades, et à chaque mouvement du perso (même si ce n'est que d'un pixel). Et cela peut causer de la motion sickness chez certaines personnes (qui sont malades à cause du mouvement - un peu comme en voiture, en fait cheeky).

   Pour éviter cela, nous allons créer une nouvelle caméra plus fluide, qui ne se déplace que si le héros sort du cadre central et qui reste fluide en ayant un mouvement continu et toujours identique.

   Le schéma suivant vous montre comment cela fonctionne :

   On va, en effet, considérer un cadre central (ou sous-boîte limite), dans lequel le joueur pourra se déplacer sans faire scroller la caméra (il est ici assez petit, mais vous pourrez l'adapter comme bon vous semblera à l'aide des defs que nous avons définies ci-haut wink).

   Dès que le joueur va sortir de ce cadre, on fera scroller la map dans la direction dans laquelle se dirige le perso, de 3 pixels.

   J'ai choisi cette valeur de 3 pixels pour bien que vous voyiez le fonctionnement de ce scrolling. Avec cette valeur, notre héros va aller plus vite que le caméraman (Cours Johnny ! Cours ! indecision), et il pourra même semer la caméra ! cheeky

   Vous pourrez cependant jouer avec cette valeur, et en la mettant à 4, le caméraman sera plus sportif et quittera moins notre héros de sa caméra.

   Cet effet est notamment utilisé dans certains jeux Sonic en 2D (à partir du 3 et Sonic & Knuckles, il me semble), pour augmenter l'impression de vitesse dégagée par son héros. wink

   Bien entendu, ce cadre agit aussi bien horizontalement que verticalement. Et c'est pourquoi, nous avons dû moduler son effet en cas de chute (même si pour l'instant, notre héros ne peut pas encore tomber). En effet, quand le héros tombe, il peut atteindre la vitesse hallucinante (mais plutôt réaliste) de 15 pixels / frame, alors que la caméra est limitée à 3. Inutile de dire que le caméraman se retrouverait vite licencié car incapable de suivre l'action ! surprise On le booste donc aux amphétamines dans ce cas-là, et on lui permet de suivre la chute, en utilisant la valeur de MAX_FALL_SPEED + 1, ce qui veut dire, qu'il peut même être plus rapide, car celui-ci a déjà pris de l'avance à cause de la bordure basse du cadre. wink Mais, vous vous rendrez plus facilement compte de tout ça quand notre héros pourra tomber, of course ! angel

 

   Les limites de la map :

   Une dernière chose : vous noterez que le scrolling s'arrêtera automatiquement aux limites de la map, et cela pour éviter de blitter du noir, qui ne ferait pas très joli ! Il ne pourra donc pas avoir de valeurs négatives (en-dehors de la map en haut et à gauche) ou supérieures à maxX - la moitié de la taille de l'écran en largeur, à droite, et maxY - la moitié de la taille de l'écran en hauteur, en bas. Il ne faut pas oublier de compter la moitié de la taille de l'écran dans ce calcul, sinon vous vous retrouveriez avec une moitié d'écran noire voire un beau plantage, car la fonction irait chercher en-dehors des limites du tableau de notre map (vous pouvez tester cheeky).

   Voilà, il ne nous reste maintenant plus qu'à relier tout ça au reste de notre jeu. Ouvrons donc le fichier main.c, pour rajouter un appel à la fonction updatePlayer() :

Fichier : main.c : Modifiez :

// Boucle infinie, principale, du jeu
while (go == 1)
{
//Gestion des inputs clavier
gestionInputs(&input);
 
// On met à jour le jeu, en commençant par le joueur
updatePlayer(&input);
 
//On dessine tout
drawGame();

 

   Plus qu'à compléter notre catalogue de prototypes pour éviter les warnings / erreurs : implicit declaration of xxxxx, qui signifient simplement que le compilo ne trouve pas le prototype de la fonction que vous appelez. wink

Fichier : prototypes.h : Remplacez par :

#ifndef PROTOTYPES
#define PROTOTYPES
 
#include "structs.h"
 
/* Catalogue des prototypes des fonctions utilisées.
On le complétera au fur et à mesure. */
 
extern void centerScrollingOnPlayer(void);
extern void changeLevel(void);
extern void cleanMaps(void);
extern void cleanPlayer(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 drawPlayer(void);
extern void drawTile(SDL_Texture *image, int destx, int desty, int srcx, int srcy);
extern void gestionInputs(Input *input);
extern SDL_Texture *getBackground(void);
extern int getBeginX(void);
extern int getBeginY(void);
extern void getInput(Input *input);
extern int getLevel(void);
extern int getMaxX(void);
extern int getMaxY(void);
extern GameObject *getPlayer(void);
extern int getPlayerDirection(void);
extern int getPlayerx(void);
extern int getPlayery(void);
extern SDL_Renderer *getrenderer(void);
extern int getStartX(void);
extern int getStartY(void);
extern void init(char *);
extern void initializePlayer(void);
extern void initMaps(void);
extern void initPlayerSprites(void);
extern void loadGame(void);
extern SDL_Texture *loadImage(char *name);
extern void loadMap(char *name);
extern void setNombreDeVies(int valeur);
extern void setNombreDetoiles(int valeur);
extern void setStartX(int valeur);
extern void setStartY(int valeur);
extern void SetValeurDuNiveau(int valeur);
extern void updatePlayer(Input *input);
 
#endif

   Et voilà, plus qu'à compiler et Tadaaaa ! cool

          On peut désormais déplacer notre héros à droite et à gauche et la caméra le suit ! angel

   Mais, mais !!?!! sad Il ne touche pas toujours le sol !! crying Et parfois, il sort même de l'écran !!!!!! angry

   Eh oui, c'est normal ! laugh Je vous avais prévenu en début de chapitre. wink On verra tout ça la prochaine fois ! cool

         Alors, @ bientôt pour la suite ! angel

                                                      Jay.

 

 

Connexion

CoalaWeb Traffic

Today169
Yesterday282
This week963
This month3256
Total1742463

19/04/24