Le clavier dans une boucle de jeu

Gérer le clavier dans une boucle de jeu ou d'animation demande un peu plus qu'un simple écouteur déclenchant une action à l'appui d'une touche. Pour un mouvement continu, on souhaite souvent effectuer l'action dans la boucle plutôt qu'au moment de l'événement. Il faut donc mémoriser l'état des touches actuellement appuyées ou non.

event.code ou event.key ?

La disposition des touches varie selon le clavier : les commandes WASD sur un clavier QWERTY correspondent aux positions ZQSD sur un clavier AZERTY.

Pour conserver la même position des commandes sur différentes dispositions, event.code est pratique. Pour une commande associée à une lettre précise, event.key peut être plus adapté.

Pour observer ces différences, ouvrez keypress.io et comparez event.code et event.key en appuyant sur différentes touches, avec et sans Majuscule, et si possible en changeant la disposition du clavier.

Références : KeyboardEvent.code et KeyboardEvent.key.

Événement ou état ?

keydown signale l'appui sur une touche et keyup son relâchement. Si une touche reste appuyée, le système peut répéter les événements keydown, après un délai et selon ses réglages. Déclencher chaque déplacement dans cet écouteur ferait dépendre le mouvement de cette répétition.

On utilise donc ces événements pour mémoriser l'état du clavier : une touche devient appuyée lors de keydown et ne l'est plus lors de keyup. Cet état reste consultable entre les événements.

Certains claviers ne reconnaissent pas toutes les combinaisons de touches simultanées : une touche peut ne pas être signalée alors qu'elle est physiquement appuyée. Les fonctions dites anti-ghosting permettent de mieux gérer certaines combinaisons, mais leur couverture dépend du clavier. Notre code ne peut mémoriser que les appuis effectivement signalés. Démonstration et explications de Microsoft.

Mémorisation des états

Une version simple d'un gestionnaire de clavier peut utiliser un Set pour conserver l'état des touches : présence dans le Set = touche appuyée ; absence = touche non appuyée. Comme un Set stocke des valeurs uniques, les événements keydown répétés ne créent pas de doublons.

const keysPressed = new Set();

window.addEventListener('keydown', (event) => {
  keysPressed.add(event.code);
});

window.addEventListener('keyup', (event) => {
  keysPressed.delete(event.code);
});

Dans l'étape update de la boucle, on peut alors tester quelles touches sont appuyées avec keysPressed.has('KeyW'), sans effectuer l'action dans l'écouteur au moment de l'appui. Plusieurs états peuvent être testés ensemble, par exemple keysPressed.has('KeyW') && keysPressed.has('KeyA') pour une combinaison de deux touches.