Votre premier jeu Windows - C++/DirectX
 |
8 input.h
Nous allons voir dans ce chapitre trois fonctions DirectInput utilisées pour gérer nos périphériques (clavier et souris). Les autres fonctions se chargent d'initialiser, gérer et "libérer" nos périphériques DirectInput, elles sont "trop complexes" pour l'instant, je tiens surtout à ne pas trop vous "bourrer" le crâne pour l'instant, nous reviendrons en détails sur la gestion de DirectInput dans un article futur. Les deux "sous chapitres" qui suivent tenterons d'éxpliquer comme fonctionne le clavier et la souris, ainsi que la manière que nous avons utilisée pour lire et stocker les enfoncements et relachements de touches et de boutons.
8.1 Boutons et axes
Un bouton est un commutateur qui a deux positions : ouvert (on) et fermé (off). DirectInput ne fait aucune distinction entre les boutons, qu'ils se trouvent sur une souris, un clavier, une manette ou sur une tablette de jeu (game pad). La plupart des souris ont deux axes, et certaines trois. Par défaut, les axes se comportent tous de la même manière.
La souris est un animal qui bouge ; elle peut aller aussi loin qu'elle veut, dans toutes les directions. Il est donc naturel que, par défaut, DirectInput considère les axes de la souris comme relatifs. Lors de l'acquisition du périphérique, DirectInput affecte une valeur arbitraire à la position de chaque axe. Lorsque DirectInput lit les données d'un axe, celles-ci sont exprimées par rapport à la dernière position connue. La valeur initiale n'est pas forcément 0 ; il faut donc mémoriser cette valeur initiale afin d'avoir quelque chose à comparer avec la valeur courante. Les déplacements de la souris fonctionnent de la manière suivante : les déplacements vers la gauche (ceux qui s'éloignent de l'utilisateur) sont négatifs et ceux vers la droite (ils se rapprochent de l'utilisateur) sont positifs. En résumé, nous comparons les coordonnées de la dernière position connue (figure 8.1.1, temps a) aux coordonnées de la position actuelle (figure 8.1.1, temps b).

 Figure 8.1.1: Déplacements de la souris |
8.2 Stocker et lire les boutons
Si vous ne connaissez pas les opérations binaires, je vous conseil vivement de lire l'article Opération Binaire qui traite justement de ce sujet 
Voilà, notre fonction ReadKeyboardInput() (pour le clavier) et ReadMouseInput() (pour la souris) se chargent de lire les boutons appuyés ou non et renvoit l'êtat de ces derniers. Nous utilisons alors la DoKeyboardInput() et DoMouseInput() pour effectuer les actions désirées. Chaque bouton est représenté par une valeur hexadécimale d'un octet (char (octet signé de -127 à 127) ou BYTE (octet non signé de 0 à 255)), nous allons stocker ces valeurs dans une variable de type DWORD soit 32 octets non signés, nous pourrons donc stocker 32 touches simultanément. Nous devons donc donner une valeur hexadécimale unique à chaqu'un de nos boutons, nous le faisons par l'intermédiaire de macros définit dans notre fichier globals.h comme ceci :
 | #define KEY_ESCAPE 0x00000001l #define KEY_UP 0x00000002l #define KEY_DOWN 0x00000004l #define KEY_LEFT 0x00000008l #define KEY_RIGHT 0x00000010l #define KEY_ENTER 0x00000020l #define KEY_CONTROL 0x00000040l #define KEY_S 0x00000080l #define KEY_L 0x00000100l #define KEY_ADD 0x00000200l #define KEY_SUBTRACT 0x00000400l #define KEY_SPACE 0x00000800l #define KEY_SHIFT 0x00001000l #define BUTTON_LEFT 0x00000001l #define BUTTON_RIGHT 0x00000002l |
Regardons notre premier macro KEY_ESCAPE, il vaut 0x00000001 (le l pour long, norme ANSI) soit 1. Le deuxième KEY_UP vaut 0x00000002 soit 2, le troisième : 4, puis 8, 16, 32, 64, 128, 256, etc. Chaque bouton a donc une valeur unique. Lorsqu'un bouton est enfoncé nous utilisons l'opérateur binaire OR afin de stocker la valeur du bouton. Lorsqu'un bouton n'est pas enfoncé nous utilisons l'opérateur binaire AND et ~ (tilde) afin de "déstocker" la valeur du bouton.
Voyons maintenant comment interpreter et gérer les actions pour nos différents boutons.
8.3 DoKeyboardAction()
Listing 8.3.1 : DoKeyboardAction() (ouvrir dans une nouvelle fenêtre)
 |  |  | 1 | | void DoKeyboardAction( void ) | 2 | | { | 3 | | DWORD key; | 4 | | key = ReadKeyboardInput(); | 5 | | | 6 | | // Rotation droite | 7 | | if ( key & KEY_RIGHT ) | 8 | | balle.dir += 1; | 9 | | | 10 | | // Rotation gauche | 11 | | else if ( key & KEY_LEFT ) | 12 | | balle.dir -= 1; | 13 | | | 14 | | // Vérifie les dépassements de directions | 15 | | // Si la direction est inférieure à zéro, par exemple "-1" nous ajoutons le nombre de directions (NBRE_DIRS) à la direction actuelle, par exemple pour "-1 + 40" cela nous donnera "39" l'objet à fait un tour | 16 | | if ( balle.dir < 0 ) | 17 | | balle.dir += NBRE_DIRS; | 18 | | | 19 | | // La meme chose qu'avant mais à l'inverse | 20 | | else if ( balle.dir >= NBRE_DIRS ) | 21 | | balle.dir -= NBRE_DIRS; | 22 | | | 23 | | // On avance | 24 | | if ( key & KEY_UP ) | 25 | | { | 26 | | balle.posX += dirX[balle.dir]; | 27 | | balle.posY += dirY[balle.dir]; | 28 | | } | 29 | | | 30 | | // On recule | 31 | | else if ( key & KEY_DOWN ) | 32 | | { | 33 | | balle.posX -= dirX[balle.dir]; | 34 | | balle.posY -= dirY[balle.dir]; | 35 | | } | 36 | | | 37 | | // L'utilisateur désire quitter l'application | 38 | | if ( key & KEY_ESCAPE ) | 39 | | PostMessage(hWnd, WM_CLOSE, 0, 0); | 40 | | } |
Cette fonction se charge des actions à effectuer lors de l'appuie d'une touche au clavier, elle est de type void (donc ne retourne rien) et n'a pas d'arguement. Ligne 3, nous définissons une variable de type DWORD afin de lire le status des touches que nous optenons grâce à la fonction ReadKeyboardInput() en ligne 4. Lignes 7 à 35 nous gérons les rotations et déplacements de notre objet balle, pour plus d'informatons sur ces opérations, consulter l'article Déplacements & Directions dans un jeu 2D. Remarquez juste que nous utilisons l'opérateur AND afin de lire l'état des boutons et ainsi définir s'il est enfoncé ou non. Ligne 38, si le bouton KEY_ESCAPE est enfoncé, nous postons un message Windows (géré par WindowProc(), voir chapitre 3) grace a la fonction PostMessage(), qui prend les arguments suivant :
- HWND hWnd : L'handle de la fenêtre recevant le message, ici hWnd
- UINT Msg : Le message à poster, ici WM_CLOSE qui aura pour effet de fermer notre application
- WPARAM wParam : Message additionel
- LPARAM lParam : Message additionel
8.4 DoMouseAction()
Listing 8.4.1 : DoMouseAction() (ouvrir dans une nouvelle fenêtre)
 |  |  | 1 | | void DoMouseAction( void ) | 2 | | { | 3 | | DWORD button; | 4 | | | 5 | | button = ReadMouseInput(); | 6 | | | 7 | | // if ( button & BUTTON_LEFT ) | 8 | | } |
Cette fonction, comme la précédente ne prend pas d'argument et ne retourne rien. Elle se limite pour l'instant à lire le status des boutons de la souris avec la fonction ReadMouseInput(), ligne 5 et la place dans la variable button déclaré en ligne 3. Ligne 7, il s'agit d'un commentaire, il montre comment tester si un bouton est enfoncé ou non.
8.5 UpdateCursorPos()
Listing 8.5.1 : UpdateCursorPos() (ouvrir dans une nouvelle fenêtre)
 |  |  | 1 | | void UpdateCursorPos( int x, int y ) | 2 | | { | 3 | | cursor.x += x; | 4 | | cursor.y += y; | 5 | | | 6 | | // clip le curseur | 7 | | if ( cursor.x < 0 ) | 8 | | cursor.x = 0; | 9 | | if ( cursor.x >= RES_X ) | 10 | | cursor.x = RES_X-1; | 11 | | if ( cursor.y < 0 ) | 12 | | cursor.y = 0; | 13 | | if ( cursor.y >= RES_Y ) | 14 | | cursor.y = RES_Y-1; | 15 | | } |
Cette fonction ne retourne rien, elle s'occupe uniquement de mettre à jour la position du curseur de notre souris, elle prend les arguments suivant :
- int x : Nouvelles coordonnées de la souris par rapport aux dernières coordonnées enregistrées sur l'axe X (horizontal)
- int y : Nouvelles coordonnées de la souris par rapport aux dernières coordonnées enregistrées sur l'axe Y (vertical)
Ligne 3 et 4, nos deux coordonnées (x et y) sont incrémentés à la position actuelle de la souris. Lignes 7 à 14 nous testons si le curseur est hors de l'écran :
- Ligne 7 : est-ce que la position X de notre curseur est plus petite que 0 ? Si oui nous fixons (ligne 8) sa position X à 0 (tout a gauche de l'écran)
- Ligne 9 : est-ce que la position X de notre curseur est plus grande ou égale à la longueur de l'écran (RES_X) ? Si oui nous fixons (ligne 10) sa position X à la longueur de l'écran (RES_X) - 1 (tout a droite de l'écran)
- Ligne 11 : est-ce que la position Y de notre curseur est plus petite que 0 ? Si oui nous fixons (ligne 12) sa position Y à 0 (tout en haut de l'écran)
- Ligne 13 : est-ce que la position Y de notre curseur est plus grande ou égale à la hauteur de l'écran (RES_Y) ? Si oui nous fixons (ligne 14) sa position Y à la hauteur de l'écran (RES_Y) - 1 (tout en bas de l'écran)
|
|  |
 |