Maîtriser le Développement de Jeux Web : Créez Vos Propres Expériences Ludiques en HTML5 et JavaScript
Maîtriser le Développement de Jeux Web : Créez Vos Propres Expériences Ludiques en HTML5 et JavaScript

Gérer les Interactions Utilisateur : Clavier, Souris et Écran Tactile pour vos Jeux

Bienvenue, futurs créateurs d'expériences ludiques ! Dans le monde fascinant du développement de jeux Web avec HTML5 et JavaScript, l'interaction utilisateur est le cœur battant de toute expérience engageante. Sans elle, votre jeu n'est qu'une démo statique. C'est elle qui transforme un simple programme en une aventure interactive, où le joueur se sent maître de son destin (ou du moins de son personnage !).

Cette leçon va vous plonger au cœur des mécanismes permettant à vos jeux de "ressentir" la présence et les actions de l'utilisateur. Nous allons explorer comment capturer les pressions de touches, les mouvements de la souris et les gestes tactiles, et comment traduire ces signaux en actions concrètes dans votre univers de jeu. Préparez-vous à donner vie à vos créations !


1. Introduction à l'Interaction Utilisateur dans les Jeux Web

Un jeu est par définition interactif. Qu'il s'agisse de déplacer un personnage, de tirer une flèche, de construire un empire ou de résoudre un puzzle, toutes ces actions sont initiées par le joueur. En JavaScript, cette interaction est gérée grâce à un modèle événementiel.

1.1 Le Modèle Événementiel : Le Cœur de l'Interactivité

Le navigateur écoute en permanence une multitude d'événements : un clic de souris, une touche pressée, un doigt touchant l'écran, le chargement d'une image, etc. Lorsque l'un de ces événements se produit, il déclenche un signal que nous, développeurs, pouvons intercepter et auquel nous pouvons "répondre".

Pour cela, nous utilisons des écouteurs d'événements (event listeners) qui attendent un type d'événement spécifique sur un élément du DOM (Document Object Model), puis exécutent une fonction de rappel (callback) lorsque cet événement se produit.

// Syntaxe générale
element.addEventListener('typeEvenement', fonctionDeRappel);

La fonctionDeRappel reçoit généralement un objet Event en argument, qui contient toutes les informations pertinentes sur l'événement qui vient de se produire.


2. Gérer les Interactions Clavier

Le clavier est l'outil d'interaction principal pour de nombreux types de jeux, des plateformers aux RPG en passant par les jeux de stratégie.

2.1 Les Événements Clavier Clés

Trois événements principaux sont liés au clavier :

  • keydown : Se déclenche dès qu'une touche est pressée. Il peut se déclencher plusieurs fois si la touche est maintenue enfoncée (auto-répétition). C'est l'événement le plus couramment utilisé pour détecter les actions du joueur.
  • keyup : Se déclenche lorsqu'une touche est relâchée. Utile pour savoir quand arrêter une action continue (ex: arrêter de courir).
  • keypress : (Déprécié pour la plupart des usages modernes) Se déclenchait lorsqu'une touche qui produit un caractère (lettre, chiffre, symbole) était pressée et relâchée. Il ne gère pas les touches non-caractères (flèches, Shift, Ctrl). keydown et keyup sont préférés car ils sont plus universels.

2.2 L'Objet KeyboardEvent

Lorsque keydown ou keyup se déclenchent, la fonction de rappel reçoit un objet de type KeyboardEvent. Ses propriétés les plus utiles sont :

  • event.key : Une chaîne de caractères représentant la touche pressée (ex: 'a', 'ArrowUp', 'Shift', ' ' pour l'espace). C'est la méthode recommandée et la plus lisible.
  • event.code : Une chaîne de caractères représentant la position physique de la touche sur le clavier (ex: 'KeyA', 'ArrowUp', 'ShiftLeft'). Utile si vous voulez que la disposition du clavier (AZERTY/QWERTY) n'affecte pas votre jeu.
  • event.keyCode : (Déprécié) Un numéro représentant la touche. Moins lisible et moins robuste que event.key ou event.code.
  • event.ctrlKey, event.shiftKey, event.altKey, event.metaKey : Des booléens indiquant si les touches de modification (Ctrl, Shift, Alt, Cmd/Win) étaient pressées en même temps.
  • event.repeat : Un booléen qui est true si la touche est maintenue enfoncée et que l'événement est une auto-répétition.

2.3 Gestion des Entrées Clavier pour le Mouvement (Exemple Pratique)

Pour un jeu, vous ne voulez généralement pas juste réagir à un keydown une seule fois. Souvent, vous avez besoin de savoir quelles touches sont actuellement enfoncées pour permettre un mouvement fluide ou des combinaisons d'actions.

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Gestion Clavier pour Jeu</title>
    <style>
        body { margin: 0; overflow: hidden; background-color: #222; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
        #player {
            width: 50px;
            height: 50px;
            background-color: dodgerblue;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
        }
    </style>
</head>
<body>
    <div id="player"></div>

    <script>
        const player = document.getElementById('player');
        let playerX = window.innerWidth / 2 - 25; // Centre le joueur
        let playerY = window.innerHeight / 2 - 25;
        const playerSpeed = 5;

        // On utilise un objet pour suivre l'état des touches
        const keysPressed = {};

        // Mettre à jour l'état quand une touche est pressée
        document.addEventListener('keydown', (event) => {
            // Empêcher le défilement par défaut du navigateur avec les flèches ou l'espace
            if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' '].includes(event.key)) {
                event.preventDefault();
            }
            keysPressed[event.key] = true;
        });

        // Mettre à jour l'état quand une touche est relâchée
        document.addEventListener('keyup', (event) => {
            keysPressed[event.key] = false;
        });

        // Fonction de boucle de jeu pour mettre à jour la position du joueur
        function gameLoop() {
            if (keysPressed['ArrowUp'] || keysPressed['w']) {
                playerY -= playerSpeed;
            }
            if (keysPressed['ArrowDown'] || keysPressed['s']) {
                playerY += playerSpeed;
            }
            if (keysPressed['ArrowLeft'] || keysPressed['a']) {
                playerX -= playerSpeed;
            }
            if (keysPressed['ArrowRight'] || keysPressed['d']) {
                playerX += playerSpeed;
            }

            // Limiter le joueur à l'écran
            playerX = Math.max(0, Math.min(window.innerWidth - player.offsetWidth, playerX));
            playerY = Math.max(0, Math.min(window.innerHeight - player.offsetHeight, playerY));


            player.style.left = `${playerX}px`;
            player.style.top = `${playerY}px`;

            requestAnimationFrame(gameLoop); // Rappeler la boucle pour le prochain frame
        }

        // Démarrer la boucle de jeu
        gameLoop();
    </script>
</body>
</html>

Explication du code :

  1. Nous avons un élément div représentant notre player.
  2. keysPressed est un objet qui stocke l'état (true/false) de chaque touche intéressante.
  3. Lorsque keydown se déclenche, nous marquons la touche correspondante comme true dans keysPressed. Nous utilisons event.preventDefault() pour empêcher le navigateur de réagir aux flèches (comme faire défiler la page).
  4. Lorsque keyup se déclenche, nous marquons la touche comme false.
  5. La fonction gameLoop() est appelée de manière répétée par requestAnimationFrame (optimisée pour l'animation). Dans cette boucle, nous vérifions quelles touches sont true dans keysPressed et ajustons la position du joueur en conséquence.
  6. requestAnimationFrame est crucial pour les jeux : il indique au navigateur que vous souhaitez effectuer une animation et demande qu'il appelle une fonction avant le prochain rafraîchissement de l'écran. C'est plus performant et plus fluide que setInterval.

3. Gérer les Interactions Souris

La souris est indispensable pour les jeux de stratégie, les "point-and-click", les jeux de tir ou tout jeu nécessitant une précision de visée ou d'interaction avec des éléments d'interface.

3.1 Les Événements Souris Clés

  • click : Se déclenche lorsqu'un bouton de la souris est pressé puis relâché sur le même élément.
  • mousedown : Se déclenche lorsqu'un bouton de la souris est pressé.
  • mouseup : Se déclenche lorsqu'un bouton de la souris est relâché.
  • mousemove : Se déclenche chaque fois que la souris bouge au-dessus de l'élément auquel l'écouteur est attaché. Très fréquent, attention aux performances !
  • mouseenter : Se déclenche lorsque le curseur de la souris entre dans la zone d'un élément.
  • mouseleave : Se déclenche lorsque le curseur de la souris quitte la zone d'un élément.
  • contextmenu : Se déclenche lorsqu'un clic droit a lieu. Pensez à utiliser event.preventDefault() pour empêcher l'affichage du menu contextuel du navigateur.
  • wheel : Se déclenche lorsque la molette de la souris est utilisée.

3.2 L'Objet MouseEvent

L'objet MouseEvent fournit des informations essentielles sur l'événement de la souris :

  • event.clientX, event.clientY : Coordonnées X et Y du curseur de la souris par rapport à la fenêtre visible (viewport).
  • event.offsetX, event.offsetY : Coordonnées X et Y du curseur de la souris par rapport à l'élément sur lequel l'événement s'est produit. Très utile pour des interactions précises sur un canvas ou un élément de jeu.
  • event.button : Indique quel bouton de la souris a été pressé (0 pour le bouton gauche, 1 pour le bouton du milieu/molette, 2 pour le bouton droit).
  • event.buttons : (Attention, au pluriel) Un masque binaire indiquant tous les boutons actuellement pressés. Utile pour savoir si plusieurs boutons sont enfoncés simultanément.
  • event.target : L'élément DOM sur lequel l'événement a initialement eu lieu.

3.3 Dessiner sur un Canvas avec la Souris (Exemple Pratique)

Un scénario courant est d'interagir avec un élément canvas pour dessiner ou sélectionner des objets.

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Gestion Souris sur Canvas</title>
    <style>
        body { margin: 0; overflow: hidden; background-color: #333; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
        canvas { border: 2px solid white; cursor: crosshair; }
    </style>
</head>
<body>
    <canvas id="gameCanvas" width="800" height="600"></canvas>

    <script>
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d'); // Contexte de dessin 2D

        let isDrawing = false; // Pour savoir si on est en train de dessiner

        canvas.addEventListener('mousedown', (event) => {
            isDrawing = true;
            // Commencer un nouveau chemin à la position actuelle de la souris
            ctx.beginPath();
            ctx.moveTo(event.offsetX, event.offsetY);
        });

        canvas.addEventListener('mousemove', (event) => {
            if (isDrawing) {
                // Dessiner une ligne vers la position actuelle de la souris
                ctx.lineTo(event.offsetX, event.offsetY);
                ctx.strokeStyle = 'lime'; // Couleur du trait
                ctx.lineWidth = 2;       // Épaisseur du trait
                ctx.stroke();            // Appliquer le trait
            }
        });

        // Arrêter de dessiner quand le bouton est relâché (sur le canvas ou ailleurs)
        document.addEventListener('mouseup', () => {
            isDrawing = false;
        });

        // Empêcher le menu contextuel du clic droit sur le canvas
        canvas.addEventListener('contextmenu', (event) => {
            event.preventDefault();
        });

        // Exemple d'interaction simple : détecter un clic sur un "bouton" imaginaire
        const button = { x: 50, y: 50, width: 100, height: 50, text: "Cliquez-moi" };

        function drawButton() {
            ctx.fillStyle = 'blue';
            ctx.fillRect(button.x, button.y, button.width, button.height);
            ctx.fillStyle = 'white';
            ctx.font = '20px Arial';
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillText(button.text, button.x + button.width / 2, button.y + button.height / 2);
        }

        canvas.addEventListener('click', (event) => {
            const mouseX = event.offsetX;
            const mouseY = event.offsetY;

            // Vérifier si le clic est à l'intérieur de notre "bouton"
            if (mouseX >= button.x && mouseX <= button.x + button.width &&
                mouseY >= button.y && mouseY <= button.y + button.height) {
                alert("Bouton cliqué !");
            }
        });

        drawButton(); // Dessiner le bouton au démarrage
    </script>
</body>
</html>

Explication du code :

  1. Un élément canvas est créé, et son contexte de dessin 2D (ctx) est récupéré.
  2. isDrawing est une variable d'état pour savoir si l'utilisateur est en train de dessiner.
  3. Au mousedown, on commence à dessiner (isDrawing = true, beginPath, moveTo).
  4. Au mousemove, si isDrawing est true, on continue la ligne (lineTo, stroke).
  5. Au mouseup (sur document pour capturer même si le curseur sort du canvas), on arrête de dessiner.
  6. Un exemple de "bouton" est dessiné, et un écouteur click est utilisé pour vérifier si les coordonnées du clic (event.offsetX, event.offsetY) se trouvent à l'intérieur de la zone du bouton.

4. Gérer les Interactions Tactiles (Écran Tactile)

Les jeux Web doivent être adaptés aux appareils mobiles. Pour cela, il est crucial de comprendre les interactions tactiles. Elles sont légèrement plus complexes que la souris car elles peuvent impliquer plusieurs doigts simultanément (multi-touch).

4.1 Les Événements Tactiles Clés

Les événements tactiles (TouchEvent) sont similaires aux événements souris mais adaptés aux écrans tactiles :

  • touchstart : Se déclenche lorsqu'un ou plusieurs points de contact (doigts) sont posés sur l'écran.
  • touchmove : Se déclenche lorsqu'un ou plusieurs points de contact se déplacent sur l'écran.
  • touchend : Se déclenche lorsqu'un ou plusieurs points de contact sont retirés de l'écran.
  • touchcancel : Se déclenche lorsqu'un contact est interrompu d'une manière anormale (par exemple, le navigateur affiche son interface ou le nombre de touches dépasse les limites supportées).

4.2 L'Objet TouchEvent et les Objets Touch

L'objet TouchEvent est la clé. Il ne contient pas directement les coordonnées, mais une liste de tous les points de contact actifs :

  • event.touches : Une TouchList contenant tous les objets Touch actuellement actifs sur l'écran.
  • event.targetTouches : Une TouchList des Touch objets qui sont sur l'élément sur lequel l'écouteur est attaché. Très utile pour cibler des interactions spécifiques à un élément de jeu.
  • event.changedTouches : Une TouchList des Touch objets qui ont changé depuis l'événement précédent. Par exemple, pour touchstart, ce sera la nouvelle touche ajoutée. Pour touchend, ce sera la touche qui vient d'être retirée.

Chaque objet Touch dans ces listes possède des propriétés comme :

  • identifier : Un ID unique pour ce point de contact particulier, qui reste constant tant que le doigt est sur l'écran.
  • clientX, clientY : Coordonnées X et Y de la touche par rapport à la fenêtre.
  • pageX, pageY : Coordonnées X et Y de la touche par rapport au document HTML.
  • screenX, screenY : Coordonnées X et Y de la touche par rapport à l'écran de l'appareil.
  • target : L'élément DOM sur lequel cette touche a commencé.

4.3 Glisser un Objet avec le Touch (Exemple Pratique)

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>Gestion Tactile pour Jeu</title>
    <style>
        body { margin: 0; overflow: hidden; background-color: #444; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
        #draggable {
            width: 100px;
            height: 100px;
            background-color: darkorange;
            border-radius: 10px;
            position: absolute; /* Permet de le déplacer librement */
            display: flex;
            justify-content: center;
            align-items: center;
            color: white;
            font-family: sans-serif;
            font-size: 1.2em;
            user-select: none; /* Empêche la sélection de texte sur l'élément */
        }
    </style>
</head>
<body>
    <div id="draggable">Glisse-moi</div>

    <script>
        const draggable = document.getElementById('draggable');
        let isDragging = false;
        let activeTouchId = null; // Pour gérer un seul doigt à la fois
        let initialX, initialY; // Position du doigt au début du glissement
        let xOffset = 0, yOffset = 0; // Décalage entre le doigt et le coin de l'élément

        // Positionner l'élément au centre initialement
        function setDraggablePosition(x, y) {
            draggable.style.left = `${x}px`;
            draggable.style.top = `${y}px`;
        }
        setDraggablePosition(window.innerWidth / 2 - 50, window.innerHeight / 2 - 50);


        draggable.addEventListener('touchstart', (event) => {
            // Empêche le comportement par défaut (comme le défilement de la page)
            event.preventDefault(); 

            // Nous nous intéressons uniquement à la première touche pour le glissement
            if (event.touches.length === 1 && !isDragging) {
                const touch = event.touches[0];
                activeTouchId = touch.identifier;
                isDragging = true;

                // Calculer le décalage initial pour éviter que l'objet ne "saute" sous le doigt
                initialX = touch.clientX;
                initialY = touch.clientY;
                xOffset = touch.clientX - draggable.getBoundingClientRect().left;
                yOffset = touch.clientY - draggable.getBoundingClientRect().top;
            }
        });

        document.addEventListener('touchmove', (event) => {
            if (!isDragging) return;

            // Trouver la touche active
            let touch = null;
            for (let i = 0; i < event.touches.length; i++) {
                if (event.touches[i].identifier === activeTouchId) {
                    touch = event.touches[i];
                    break;
                }
            }

            if (touch) {
                // Nouvelle position basée sur le doigt actuel et le décalage initial
                const newX = touch.clientX - xOffset;
                const newY = touch.clientY - yOffset;
                setDraggablePosition(newX, newY);
            }
        });

        document.addEventListener('touchend', (event) => {
            // Vérifier si la touche qui s'est terminée est celle que nous suivions
            for (let i = 0; i < event.changedTouches.length; i++) {
                if (event.changedTouches[i].identifier === activeTouchId) {
                    isDragging = false;
                    activeTouchId = null;
                    break;
                }
            }
        });

        document.addEventListener('touchcancel', (event) => {
            // Gérer l'annulation comme une fin de glissement
            for (let i = 0; i < event.changedTouches.length; i++) {
                if (event.changedTouches[i].identifier === activeTouchId) {
                    isDragging = false;
                    activeTouchId = null;
                    break;
                }
            }
        });

        // Pour la compatibilité avec la souris (optionnel)
        draggable.addEventListener('mousedown', (event) => {
            event.preventDefault();
            isDragging = true;
            initialX = event.clientX;
            initialY = event.clientY;
            xOffset = event.clientX - draggable.getBoundingClientRect().left;
            yOffset = event.clientY - draggable.getBoundingClientRect().top;
        });

        document.addEventListener('mousemove', (event) => {
            if (!isDragging) return;
            const newX = event.clientX - xOffset;
            const newY = event.clientY - yOffset;
            setDraggablePosition(newX, newY);
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
        });
    </script>
</body>
</html>

Explication du code :

  1. Un div avec l'ID draggable est notre objet à glisser.
  2. isDragging et activeTouchId gèrent l'état de glissement et quel doigt est en train de glisser.
  3. xOffset et yOffset sont cruciaux : ils stockent la distance entre le point de contact du doigt et le coin supérieur-gauche de l'élément au moment du touchstart. Cela empêche l'élément de "sauter" et le fait suivre le doigt de manière naturelle.
  4. Sur touchstart, nous enregistrons l'ID de la touche et son point de départ. event.preventDefault() est essentiel pour empêcher le défilement et le zoom par défaut.
  5. Sur touchmove, nous trouvons la touche correspondant à activeTouchId (car d'autres doigts peuvent être apparus ou disparus), et nous déplaçons l'élément en fonction de la position actuelle du doigt et de xOffset/yOffset.
  6. Sur touchend et touchcancel, nous réinitialisons l'état de glissement.
  7. Une gestion similaire pour mousedown/mousemove/mouseup est ajoutée pour la compatibilité avec la souris, ce qui est une bonne pratique pour les jeux multi-plateformes.

5. Bonnes Pratiques pour la Gestion des Entrées dans les Jeux

  • event.preventDefault() : Toujours utiliser cette méthode pour les événements d'entrée dont vous ne voulez pas le comportement par défaut du navigateur (ex: défilement avec les flèches, menu contextuel du clic droit, zoom tactile).
  • Gestion de l'état : Pour les inputs continus (mouvement, tir continu), ne réagissez pas uniquement sur l'événement keydown ou mousemove. Stockez l'état des entrées dans des variables ou objets (comme keysPressed) et mettez à jour votre jeu dans la boucle de jeu principale (requestAnimationFrame).
  • Performance (mousemove, touchmove) : Ces événements peuvent se déclencher très fréquemment. Évitez d'effectuer des calculs complexes ou des manipulations DOM intenses directement dans leurs écouteurs. Si possible, mettez à jour l'état et laissez la boucle de jeu gérer le rendu.
  • Déléguer les événements : Pour les jeux avec beaucoup d'éléments interactifs, au lieu d'ajouter un écouteur à chaque élément, ajoutez un écouteur à un conteneur parent et utilisez event.target pour identifier l'élément spécifique cliqué ou touché.
  • Multi-touch : Soyez conscient que event.touches contiendra tous les doigts actuellement sur l'écran. event.targetTouches est utile pour les interactions avec un élément spécifique, tandis que event.changedTouches est idéal pour identifier quelle touche a initié ou terminé un événement.
  • Normalisation : Pour les jeux complexes, vous pourriez vouloir créer une couche d'abstraction pour les entrées, qui "normalise" les événements clavier, souris et tactiles en un ensemble d'actions de jeu (ex: "DéplacerHaut", "Attaquer"). Cela simplifie le code de votre logique de jeu.
  • Accessibilité : Pensez aux joueurs qui pourraient avoir des besoins spécifiques. Offrez des options de remappage de touches, une sensibilité réglable, ou des alternatives aux interactions complexes si possible.

6. Conclusion et Résumé

La gestion des interactions utilisateur est une pierre angulaire du développement de jeux Web. Nous avons exploré :

  • Le modèle événementiel en JavaScript et l'utilisation de addEventListener.
  • Les interactions clavier avec keydown, keyup, et l'objet KeyboardEvent, en privilégiant event.key ou event.code. Nous avons vu comment maintenir un état des touches pressées pour un mouvement fluide.
  • Les interactions souris avec mousedown, mouseup, mousemove, click, et l'objet MouseEvent, en utilisant event.offsetX / event.offsetY pour des interactions précises sur un canvas.
  • Les interactions tactiles avec touchstart, touchmove, touchend, et l'objet TouchEvent contenant des objets Touch, essentiel pour les jeux mobiles. Nous avons appris à gérer le glisser-déposer en multi-touch.
  • Des bonnes pratiques pour assurer la fluidité, la performance et l'accessibilité de vos systèmes d'entrée.

En maîtrisant ces techniques, vous avez désormais les outils pour donner à vos joueurs le contrôle total de vos mondes virtuels. N'hésitez pas à expérimenter, à combiner ces techniques et à les adapter aux besoins spécifiques de vos créations. À vous de jouer !