Big Tuto SDL 2 : Rabidja v. 3.0
Chapitre 11 : Ajoutons une manette !
Tutoriel présenté par : Jérémie F. Bellanger (Jay81)
Ecriture : 24 octobre 2014
Dernière mise à jour : 20 juin 2016
Prologue
Dans ce nouveau chapitre, nous allons voir comment ajouter le support d'une manette à notre jeu, car c'est quand même plus sympa pour jouer !
Dans le tuto précédent, j'avais attendu le chapitre 33 (auquel ce chapitre va souvent faire écho, même si le code a été sensiblement amélioré ici ) pour gérer les manettes. Cela m'a semblé bien trop tardif pour tous ceux qui auraient voulu tester leur jeu dès le début, manette en main ! Cette fois-ci, je m'y prends donc beaucoup plus tôt !
Alors sinon, pour reprendre mes propos du chapitre 33 (que vous n'aurez pas forcément lu, si vous commencez directement ce nouveau tuto), c'était plutôt simple à faire en SDL 1.2, vu le nombre de tutos sur le sujet. Mais voilà, pour la SDL 2, il n'y a pas grand chose à l'heure actuelle... Et malheureusement, la doc officielle n'est pas encore très claire, je trouve (mais il faut bien leur laisser le temps de la constituer ).
C'est pourquoi, je vais vous donner un petit coup de pouce avec ce tuto, si vous aussi vous galérez ! Et vous verrez que ce n'est finalement pas beucoup plus compliqué.
Voilà, maintenant, pour ce tuto, j'aurais pu m'arrêter à la détection de la manette, et voilà !
Sauf que souvent, on lance le programme sans la manette, et on se dit : "Mince, la manette ! " et on la branche. Et là, on s'attend à ce que le programme la détecte et qu'on puisse jouer avec sans avoir rien à faire (et surtout pas relancer la programme ! ).
On va donc essayer de créer un système plus pro qui détecte les manettes (ou leur absence) on the go ! (Mais comme on le verra, il restera quand même un petit bug... qu'on arrivera peut-être à résoudre avec le temps et votre aide ! ).
Sinon, j'ai aussi réussi à gérer en même temps le D-PAD et le thumbpad de la manette Xbox 360 (car c'est avec cette manette, devenue plus ou moins standard que j'ai programmé ce tuto - vous pourrez cependant en essayer d'autres, mais il faudra certainement adapter le mapping des touches ).
J'ai donc créé un système avancé de détection : comme on ne peut pas utiliser le D-PAD et le thumbpad en même temps, à moins d'être un martien avec 4 mains , l'utilisation du D-PAD désactive le thumbpad (pour éviter les conflits ). Il suffit alors de lâcher le D-PAD et d'utiliser le thumbpad pour le réactiver en 1/60ème de seconde (soit un tour de boucle, c'est important pour la réactivité ) ! Du coup, l'utilisateur lambda ne s'en rendra jamais compte, il se dira simplement que sa manette Xbox 360 marche correctement (et c'est le but ).
Et voilà, donc on est parti !
Le code
On va commencer par aller dans le fichier defs.h, pour créer quelques nouvelles defines qui vont nous servir ici .
On aura tout d'abord une def pour définir une dead zone dans le thumbpad de notre manette. Effectivement, le thumbpad n'est jamais réellement à 0. Même au repos, il dévie un peu. On va donc mettre en place une deadzone dans laquelle, on va considérer que le gamepad est au repos. On va prendre comme valeur 8000, ce qui peut paraître beaucoup, mais la valeur max renvoyée par le thumbpad dépasse les 34000 !
On va ensuite définir d'autres defs pour chacun des boutons de notre manette dont nous aurons besoin. Ce mapping concerne la manette Xbox 360 (dont vous trouverez le schéma du mapping ci-dessous). Si vous voulez tester une autre manette, il vous suffira de changer la valeur des touches ici, selon votre manette, et cela vous évitera d'aller chercher partout dans le code !
Fichier : defs.h : Rajoutez :
//Dead zone de la manette pour éviter les mouvements involontaires
#define DEAD_ZONE 8000
//Mapping de la manette Xbox 360
#define BOUTON_HAUT 0
#define BOUTON_BAS 1
#define BOUTON_GAUCHE 2
#define BOUTON_DROITE 3
#define BOUTON_SAUT 10
#define BOUTON_ATTAQUE 12
#define BOUTON_PAUSE 4
#define BOUTON_QUIT 5
|
Et voilà le mapping des touches de la manette Xbox 360 de Microsoft :
Initialisation de notre joystick
Il va maintenant nous falloir initialiser notre joystick, et c'est là que j'ai un peu galéré !
Dans la SDL 1.2, on l'initialisait en même temps que la SDL. Mais maintenant, on n'initialise plus la SDL 2...
Alors, en fait, il faut juste initialiser le sous-sytème Joystick !
Ensuite, on cherche s'il y a au moins un joystick connecté et on initialise le premier qui passe (pour le support multi-manette, je n'ai pas encore testé, mais ça doit être faisable en créant plusieurs variables Joystick).
Fichier : init.c : Rajoutez à la fin de la fonction init() :
// Initialise le sous-sytème de la SDL gérant les joysticks
SDL_InitSubSystem(SDL_INIT_JOYSTICK);
//On cherche s'il y a des joysticks
if (SDL_NumJoysticks() > 0)
{
openJoystick();
}
|
Fermeture de notre joystick
Et bien entendu, comme on est des gens propres, on n'oublie pas de faire le ménage en partant !
Fichier : init.c : Remplacez la fonction précédente par :
void cleanup()
{
//Nettoie les sprites de la map
cleanMaps();
/* Libère le sprite du héros */
cleanPlayer();
/* Ferme la prise en charge du joystick */
closeJoystick();
//On quitte SDL_Mixer 2 et on décharge la mémoire
Mix_CloseAudio();
Mix_Quit();
//On fait le ménage et on remet les pointeurs à NULL
SDL_DestroyRenderer(renderer);
renderer = NULL;
SDL_DestroyWindow(screen);
screen = NULL;
//On quitte SDL_TTF 2
TTF_Quit();
//On quitte la SDL
SDL_Quit();
}
|
Gérons les inputs de notre manette !
Bon, c'est bien, on a une manette, mais pour l'instant, elle ne sert à rien !
On va donc créer une nouvelle fonction dans notre fichier d'input, que nous nommerons simplement getJoystick() !
Mais avant, on va rajouter une variable de type SDL_Joystick pour gérer les inputs du joystick et aussi une nouvelle variable locale : DPADinUse, qui nous permettra de switcher entre le D-Pad et le thumbpad, selon celui que le joueur utilise !
Fichier : input.c : Rajoutez au début du fichier :
#include "prototypes.h"
//Manette
SDL_Joystick *joystick;
int DPADinUse = 0;
void openJoystick(void)
{
//On ouvre le joystick
joystick = SDL_JoystickOpen(0);
if (!joystick)
printf("Le joystick 0 n'a pas pu être ouvert !\n");
}
void closeJoystick(void)
{
/* Ferme la prise en charge du joystick */
if (SDL_JoystickGetAttached(joystick))
SDL_JoystickClose(joystick);
}
|
Ensuite, nous définissons nos fonctions openJoystick() et closeJoystick() dont le nom résume bien ce qu'elles font !
Fonction d'auto-détection
Voilà, maintenant, il nous reste encore à faire appel à notre fonction getjoystick() à la place du clavier, si une manette est connectée. Pour cela, on va mettre à jour notre fonction gestionInputs().
Mais que se passerait-il si on déconnectait la manette en cours de partie ?
A ce moment-là, le jeu ne répondrait plus !
Voici le code commenté, que je vous laisse lire :
Fichier : input.c : Remplacez la fonction précédente par :
void gestionInputs(Input *input)
{
/* On prend en compte les inputs (clavier, joystick... */
if (joystick != NULL)
{
//On vérifie si le joystick est toujours connecté
if (SDL_NumJoysticks() > 0)
getJoystick(input);
//Sinon on retourne au clavier
else
{
SDL_JoystickClose(joystick);
joystick = NULL;
}
}
//S'il n'y a pas de manette on gère le clavier
else
{
//On vérifie d'abord si une nouvelle manette a été branchée
if (SDL_NumJoysticks() > 0)
{
//Si c'est le cas, on ouvre le joystick, qui sera opérationnel au prochain tour de boucle
joystick = SDL_JoystickOpen(0);
if (!joystick)
printf("Couldn't open Joystick 0\n");
}
//On gère le clavier
getInput(input);
}
}
|
Passons maintenant aux choses sérieuses, au nerf de la guerre, avec la fonction getjoystick() !
Je l'ai déjà pas mal commentée, donc je vous laisse la lire.
Fichier : input.c : Ajoutez la fonction :
void getJoystick(Input *input)
{
SDL_Event event;
//Si on ne touche pas au D-PAD, on le désactive (on teste les 4 boutons du D-PAD)
if (SDL_JoystickGetButton(joystick, BOUTON_HAUT) == 0 && SDL_JoystickGetButton(joystick, BOUTON_BAS) == 0
&& SDL_JoystickGetButton(joystick, BOUTON_DROITE) == 0
&& SDL_JoystickGetButton(joystick, BOUTON_GAUCHE) == 0)
DPADinUse = 0;
/* On passe les events en revue */
while (SDL_PollEvent(&event))
{
if (event.type == SDL_QUIT)
exit(0);
else if (event.type == SDL_KEYDOWN)
{
switch (event.key.keysym.sym)
{
case SDLK_ESCAPE:
exit(0);
break;
default:
break;
}
}
else if (event.type == SDL_JOYBUTTONDOWN)
{
if (event.jbutton.button == BOUTON_SAUT)
input->jump = 1;
else if (event.jbutton.button == BOUTON_ATTAQUE)
input->attack = 1;
else if (event.jbutton.button == BOUTON_PAUSE)
input->enter = 1;
else if (event.jbutton.button == BOUTON_QUIT)
exit(0);
else if (event.jbutton.button == BOUTON_HAUT)
{
input->up = 1;
DPADinUse = 1;
}
else if (event.jbutton.button == BOUTON_BAS)
{
input->down = 1;
DPADinUse = 1;
}
else if (event.jbutton.button == BOUTON_GAUCHE)
{
input->left = 1;
DPADinUse = 1;
}
else if (event.jbutton.button == BOUTON_DROITE)
{
input->right = 1;
DPADinUse = 1;
}
}
else if (event.type == SDL_JOYBUTTONUP)
{
if (event.jbutton.button == BOUTON_PAUSE)
input->enter = 0;
else if (event.jbutton.button == BOUTON_SAUT)
input->jump = 0;
else if (event.jbutton.button == BOUTON_HAUT)
input->up = 0;
else if (event.jbutton.button == BOUTON_BAS)
input->down = 0;
else if (event.jbutton.button == BOUTON_GAUCHE)
input->left = 0;
else if (event.jbutton.button == BOUTON_DROITE)
input->right = 0;
}
//Gestion du thumbpad, seulement si on n'utilise pas déjà le D-PAD
else if (event.type == SDL_JOYAXISMOTION && DPADinUse == 0)
{
//Si le joystick a détecté un mouvement
if (event.jaxis.which == 0)
{
//Si le mouvement a eu lieu sur l'axe des X
if (event.jaxis.axis == 0)
{
//Si l'axe des X est neutre ou à l'intérieur de la "dead zone"
if ((event.jaxis.value > -DEAD_ZONE) && (event.jaxis.value < DEAD_ZONE))
{
input->right = 0;
input->left = 0;
}
//Sinon, de quel côté va-t-on ?
else
{
//Si sa valeur est négative, on va à gauche
if (event.jaxis.value < -DEAD_ZONE)
{
input->right = 0;
input->left = 1;
}
//Sinon, on va à droite
else if (event.jaxis.value > DEAD_ZONE)
{
input->right = 1;
input->left = 0;
}
}
}
//Si le mouvement a eu lieu sur l'axe des Y
else if (event.jaxis.axis == 1)
{
//Si l'axe des Y est neutre ou à l'intérieur de la "dead zone"
if ((event.jaxis.value > -DEAD_ZONE) && (event.jaxis.value < DEAD_ZONE))
{
input->up = 0;
input->down = 0;
}
//Sinon, de quel côté va-t-on ?
else
{
//Si sa valeur est négative, on va en haut
if (event.jaxis.value < 0)
{
input->up = 1;
input->down = 0;
}
//Sinon, on va en bas
else
{
input->up = 0;
input->down = 1;
}
}
}
}
}
}
}
|
Bon, vous remarquerez que pour l'essentiel, la boucle des events ressemble à celle du clavier (avec des if au lieu des case, car c'est plus simple ici ).
Pour tout ce qui est boutons (et vous aurez remarqué que le D-PAD de la manette Xbox 360 est maintenant assimilé à 4 boutons alors qu'il était considéré comme un HAT dans la version 1.2 de la SDL), il s'agit de regarder s'ils sont enfoncés ou non, et dans ce cas de changer la valeur de nos inputs, un peu comme pour le clavier.
Pour le thumbpad, il s'agit de voir s'il y a eu un mouvement sur l'un de ses axes (X - gauche/droite ou Y - haut/bas) et si ce mouvement sort de notre deadzone.
S'il n'y a pas de mouvement, tous les inputs reviennent à zéro, et sinon, on passe à 1 l'input correspondant à l'axe du stick poussé.
Mais là, où le problème arrive, c'est que sans l'utilisation de notre variable DPADinUse (qui aurait pu simplement être un booléen en C++ ), il y aurait conflit entre le D-PAD et le thumbpad, le thumbpad remettant les inputs à 0, alors qu'il y a parfois appui sur une touche du D-PAD. C'est pourquoi, on met DPADinUse à 1 (vrai) dès qu'on détecte un appui sur une touche du D-PAD. En outre, la gestion du thumbpad ne se fait plus, si cette condition est vraie (DPADinUse == 1).
Mais pour pouvoir réutiliser le thumbpad, si on lâche le D-PAD, on teste tout au début du programme, s'il n'y a aucun appui sur aucune des touches du D-PAD, et si c'est le cas, on passe DPADinUse à 0 (faux) pour réactiver éventuellement le thumbpad.
Ainsi, le joueur peut passer de l'un à l'autre librement n'importe quand, et ça fait quand même plus pro !
Maintenant, là où il reste encore un bug que je n'ai pas réussi à résoudre (vient-il de moi ou de la SDL ? ), c'est que si on débranche et qu'on rebranche la manette (avec le système de détection auto que nous verrons ci-après), le thumbpad ne réagit plus ?... Si vous trouvez la solution à ce problème, merci de la poster sur le forum !
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 closeJoystick(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 void getJoystick(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(int newLevel);
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 mapCollision(GameObject *entity);
extern void openJoystick(void);
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
|
Voilà, plus qu'à compiler et à lancer le programme !
Vous pouvez maintenant profiter de votre jeu de plateformes avec votre manette préférée !
Elle est pas belle, la vie ?!
Je vous dis donc à bientôt pour le chapitre 12 !
Jay
Abonnez-vous et devenez Premium pour lire la fin de ce tuto ainsi que tous les autres tutos du site !
Et voici maintenant la liste des chapitres suivants, de ce tuto, auxquels vous aurez accès :
- Big Tuto SDL 2 - Chapitre 12: Ajoutons un monstre
- Big Tuto SDL 2 - Chapitre 13 : Affichons le HUD!
- Big Tuto SDL 2 - Chapitre 14: Monte le son !
- Big Tuto SDL 2 - Chapitre 15: Power-ups et tiles spéciales !
- Big Tuto SDL 2 - Chapitre 16: Des plateformes volantes !
- Big Tuto SDL 2 - Chapitre 17: Niveaux et checkpoints
- Big Tuto SDL 2 - Chapitre 18: Ajoutons des menus !
- Big Tuto SDL 2 - Chapitre 19: Des shurikens !
- Big Tuto SDL 2 - Chapitre 20: Des explosions !
- Big Tuto SDL 2 - Chapitre 21: Ajoutons des pentes !
Et voilà le résultat final auquel vous parviendrez à la fin de ce Tuto !
@ bientôt !
Jay