Big Tuto SFML 2 : Rabidja v. 3.0

Chapitre 8 : Déplacement et scrolling

Tutoriel présenté par : Jérémie F. Bellanger (Jay81)
Date d'écriture : 16 février 2015
Date de révision : 20 mars 2016

      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 (mais le même que celui du Big Tuto SDL 2 wink), 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 donc continuer à développer notre classe Player, en rajoutant les fonctions update() pour pouvoir déplacer notre héros, et centerScrolling() pour gérer la caméra et le scrolling.

   Mais avant, revenons sur ces quelques constantes que j'avais abordées très rapidement au chapitre précédent :

Fichier : player.h (ne pas copier, elles y sont déjà !) :

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

   Les 4 premières 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 centerScrolling(), 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.cpp, dans lequel on va trouver l'essentiel des nouveautés de ce chapitre:

Fichier : player.cpp : Rajoutez :

void Player::update(Input &input, Map &map)
{
//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.
onGround = true;
y = 302;
 
//Voilà, au lieu de changer directement les coordonnées du joueur, on passe par un vecteur
//qui sera utilisé par la fonction mapCollision(), qui regardera si on peut ou pas déplacer
//le joueur selon ce vecteur et changera les coordonnées du player en fonction.
if (input.getButton().left == true)
{
x -= PLAYER_SPEED;
//Et on indique qu'il va à gauche (pour le flip
//de l'affichage, rappelez-vous).
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 (etat != WALK && onGround == true)
{
//On enregistre l'anim' de la marche et on l'initialise à 0
etat = WALK;
frameNumber = 0;
frameTimer = TIME_BETWEEN_2_FRAMES_PLAYER;
frameMax = 8;
}
}
 
//Si on détecte un appui sur la touche fléchée droite
else if (input.getButton().right == true)
{
//On augmente les coordonnées en x du joueur
x += PLAYER_SPEED;
//Et on indique qu'il va à droite (pour le flip
//de l'affichage, rappelez-vous).
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 (etat != WALK && onGround == true)
{
//On enregistre l'anim' de la marche et on l'initialise à 0
etat = WALK;
frameNumber = 0;
frameTimer = TIME_BETWEEN_2_FRAMES_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.getButton().right == false && input.getButton().left == false && onGround == true)
{
//On teste si le joueur n'était pas déjà inactif, pour ne pas recharger l'animation
//à chaque tour de boucle
if (etat != IDLE)
{
//On enregistre l'anim' de l'inactivité et on l'initialise à 0
etat = IDLE;
frameNumber = 0;
frameTimer = TIME_BETWEEN_2_FRAMES_PLAYER;
frameMax = 8;
}
 
}
 
//On gère le scrolling (fonction ci-dessous)
centerScrolling(map);
 
}

   Et voici notre fonction update() (enfin son ébauche, car elle n'est pas finie wink). On avait, en effet, nos fonctions d'initialisation 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 = true), 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 constante (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 draw() (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

   Et voilà, on termine notre fonction par un appel à centerScrolling() 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.cpp : Rajoutez :

void Player::centerScrolling(Map &map)
{
// 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. 
int cxperso = x + w / 2;
int cyperso = y + h / 2;
int xlimmin = map.getStartX() + LIMITE_X;
int xlimmax = xlimmin + LIMITE_W;
int ylimmin = map.getStartY() + LIMITE_Y;
int ylimmax = ylimmin + LIMITE_H;
 
 
//Si on dépasse par la gauche, on recule la caméra
if (cxperso < xlimmin)
{
map.setStartX(map.getStartX() - 3);
}
 
//Si on dépasse par la droite, on avance la caméra
else if (cxperso > xlimmax)
{
map.setStartX(map.getStartX() + 3);
}
 
//Si on arrive au bout de la map à gauche, on stoppe le scrolling
if (map.getStartX() < 0)
{
map.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 (map.getStartX() + SCREEN_WIDTH >= map.getMaxX())
{
map.setStartX(map.getMaxX() - SCREEN_WIDTH);
}
 
//Si on dépasse par le haut, on remonte la caméra
if (cyperso < ylimmin)
{
map.setStartY(map.getStartY() - 3);
}
 
//Si on dépasse par le bas, on descend la caméra
if (cyperso > ylimmax)
{
//Sauf si on tombe très vite, auquel cas, on accélère la caméra :
if (dirY >= MAX_FALL_SPEED - 2)
{
map.setStartY(map.getStartY() + MAX_FALL_SPEED + 1);
}
else
{
map.setStartY(map.getStartY() + 3);
}
}
 
//Si on arrive au bout de la map en haut, on stoppe le scrolling
if (map.getStartY() < 0)
{
map.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 (map.getStartY() + SCREEN_HEIGHT >= map.getMaxY())
{
map.setStartY(map.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.

   Pour les besoins de ce tuto, nous allons faire exprès d'augmenter la latence de la caméra pour que vous voyiez bien son comportement. Cela dit, c'est pas terrible en jeu : on corrigera donc les valeurs de déplacement dans le prochain chapitre, en même temps qu'on mettra encore à jour la fonction. wink

   Le schéma suivant vous montre maintenant 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 constantes que nous avons revues 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 (ce sera mieux en jeu wink).

   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).

   On n'oublie pas d'ajouter les prototypes de ces 2 fonctions à notre fichier player.h :

Fichier : player.h : Mettre à jour :

//Fonctions
void initialize(Map &map, bool newLevel);
void draw(Map &map, sf::RenderWindow &window);
void update(Input &input, Map &map);
void centerScrolling(Map &map);

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

Fichier : main.cpp : Modifiez :

// Boucle infinie, principale, du jeu
while (window.isOpen())
{
/** GESTION DES INPUTS (CLAVIER, JOYSTICK) **/
input.gestionInputs(window);
 
/** MISES A JOUR - UPDATES **/
//On met à jour le player : Rabidja
player.update(input, map);
 
/** DESSIN - DRAW **/
//On dessine tout
window.clear();

 

   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

Today130
Yesterday282
This week924
This month3217
Total1742424

19/04/24