Chapitre 33
Ajoutons une manette en SDL 2 !
Tutoriel présenté par : Jérémie F. Bellanger (Jay)
Dernière mise à jour : 21 juillet 2014
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 !
Je l'avais déjà fait en SDL 1.2, et vu le nombre de tutos sur le sujet, ça avait été plutôt simple. 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 !
Voilà, maintenant, pour ce tuto, j'aurais pu m'arrêter à la détection de la manette, et voilà ! :D
Sauf que souvent, on lance le programme sans la manette, et on se dit : "Mince, la manette ! :shock: " 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 ! :mrgreen: ).
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 :lol: , 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, 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 :P ).
Eh voilà, donc on est parti !
De nouvelles defs
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
//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 :
Mise à jour de notre structure Gestion jeu
On va ensuite simplement rajouter une variable de type SDL_Joystick à notre structure Gestion :
Fichier : structs.h / Gestion
/* Structure pour gérer le niveau */ typedef struct Gestion { SDL_Window *screen; SDL_Renderer *renderer; //Manette SDL_Joystick *joystick;
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 / 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) { //On ouvre le joystick jeu.joystick = SDL_JoystickOpen(0); if (!jeu.joystick) printf("Le joystick 0 n'a pas pu être ouvert !\n"); }
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 / cleanup()
/* Ferme la prise en charge du joystick */ if (SDL_JoystickGetAttached(jeu.joystick)) SDL_JoystickClose(jeu.joystick);
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 ajouter notre structure Gestion dans l'en-tête du fichier, afin de pouvoir avoir accès au fichier, et nous allons aussi créer 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.h
int DPADinUse = 0; extern Gestion jeu;
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 / nouvelle fonction
void getJoystick() { 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(jeu.joystick, BOUTON_HAUT) == 0 && SDL_JoystickGetButton(jeu.joystick, BOUTON_BAS) == 0 && SDL_JoystickGetButton(jeu.joystick, BOUTON_DROITE) == 0 && SDL_JoystickGetButton(jeu.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 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 !
Le main() et sa fonction d'auto-détection
Voilà, maintenant, il nous reste encore à faire appel à notre fonction getjoystick() à la place du clavier, dans le main(), si une manette est connectée.
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 !
On va donc rajouter 2 sécurités :
- la première va détecter si on déconnecte la manette, et dans ce cas, fermer le support du joystick et revenir au clavier.
- la seconde va détecter si on branche une nouvelle manette (s'il n'y en a pas déjà une), et si c'est le cas, il va l'initialiser pour qu'on puisse jouer avec tout de suite !
Voici le code commenté, que je vous laisse lire :
Fichier : main.c / main()
/* Boucle infinie, principale, du jeu */ while (go == 1) { /* On prend en compte les inputs (clavier, joystick... */ if (jeu.joystick != NULL) { //On vérifie si le joystick est toujours connecté if(SDL_NumJoysticks() > 0) getJoystick(); //Sinon on retourne au clavier else { SDL_JoystickClose(jeu.joystick); jeu.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 jeu.joystick = SDL_JoystickOpen(0); if (!jeu.joystick) printf("Couldn't open Joystick 0\n"); } //On gère le clavier getInput(); }
Et voilà, en fait, ce n'était pas si compliqué.
Maintenant, pour finir, n'oublions pas notre en-tête :
Fichier : main.h
extern void getJoystick(void);
Eh voilà, vous pourrez maintenant profiter de votre jeu de plateformes avec votre manette préférée !
Elle est pas belle, la vie ?!
Avant de refermer ce chapitre, vous noterez que j'ai rajouté la possibilité de démarrer le jeu en appuyant sur la touche A (jump) de la manette Xbox 360 (dans la fonction d'update du menu), car c'est quand même mieux ainsi ! Et sinon, dans le prochain chapitre, on fera encore quelques petites améliorations, comme la possibilité de choisir son niveau de départ (mieux si vous en avez créé 99 ! ), un menu Pause plus développé, avec la possibilité de retourner à l'écran-titre pour changer de niveau, et si on a le temps, une nouvelle anim' d'explosion quand les monstres meurent !
@ bientôt sur Meruvia !
Jay.