Moteur JS : créer une Game Loop en JavaScript avec requestAnimationFrame

HTML5 et Game Developement

Le cœur de tout moteur de jeu est une fonction appelée Game Loop, qui permet de synchroniser les mises à jour de la scène et des objets avec l’affichage. En JavaScript (et HTML5), cette boucle passe désormais par requestAnimationFrame, qui offre de meilleures performances que les anciens timers. Dans cet article, nous verrons comment implémenter une game loop simple et efficace.

Qu’est-ce qu’une Game Loop ?

Une Game Loop est une fonction récurrente qui gère, à chaque frame, deux étapes clés :

  • La mise à jour de la logique du jeu (Update)
  • Le rendu visuel des éléments à l’écran (Draw)

Elle constitue la base de tous les moteurs de jeux, quels que soient le langage ou la plateforme.

Une fonctionnalité qui unit tous les moteurs de jeu, est la Game Loop : une fonction qui gère le cycle du jeu ; la physique (la mise à jour de la position de nos éléments de jeu, le contrôle des collisions), et le Draw, des objets qui font partie du jeu (les GameObject).


requestAnimationFrame : l’API moderne pour l’animation

Avant HTML5, on utilisait setTimeout ou setInterval pour les animations. requestAnimationFrame est bien plus efficace :

  • Synchronisation native avec le rafraîchissement de l’écran
  • Consommation réduite en énergie
  • Meilleure fluidité (jusqu’à 60 FPS)

Cette fonction a été ajoutée pour remplacer l’ancienne méthode d’animation, qui utilisait un timer (setTimeout) pour afficher une image toutes les « quelques » millisecondes.

Avec requestAnimationFrame, nous pouvons indiquer au moteur JavaScript la fonction à lancer la prochaine fois qu’un frame est déclenchée. Voici la syntaxe :

<em>(request ID)</em> = requestAnimationFrame(<em>callback</em>)

Le navigateur appelle callback au moment optimal pour redessiner l’écran.

Grace à cette fonction native, le navigateur peut optimiser le rendering nativement en gardant le framerate plus stable et fluide (avec un maximum de 60fps), ce qui réduit considérablement les « coups » ennuyeux qui ont été notées en utilisant le Timer (setTimeout, setInterval).

Dans le cas où un autre onglet du navigateur est ouvert, le rendering serait temporairement désactivé, contrairement à setTimeout, qui continuerait à fonctionner en utilisant la CPU, GPU et de la mémoir e: l’utilisation accrue de la performance du hardware, qui dans les appareils portables et mobiles, se résume à un gaspillage de batterie.


Polyfill pour requestAnimationFrame

Pour garantir la compatibilité avec d’anciens navigateurs, on utilise un polyfill qui fournit une alternative en cas d’absence de l’API native.

Vu que l’HTML5 n’était pas encore la norme en 2014, requestAnimationFrame a été mis en place temporairement par un préfixe dans l’identifiant (webkit, moz, ou, ms), qui fait référence aux différents noms de navigateur : nous définissons une fonction générique, un polyfill, qui renvoie l’identifiant de la fonction correcte.

Pour éviter d’avoir à appeler chaque fois toutes les possibles fonctions créons ce que l’on nomme un Polyfill, ou une fonction qui rappelle la fonction correcte pour chaque client.

Créons un nouveau fichier JavaScript (utils.js), dans lequel on va insérer toutes les fonctions pratiques que nous utiliserons dans notre framework. Commençons par ce polyfill :

 window.requestAnimFrame = (function(callback) {
 
    return window.requestAnimationFrame ||
           window.webkitRequestAnimationFrame ||
           window.mozRequestAnimationFrame ||
           window.oRequestAnimationFrame ||
           window.msRequestAnimationFrame ||
            
           function(callback) {
              window.setTimeout(callback, 1000 / 60);
           }; 
})();

Bien que la probabilité soit éloignée, nous pouvons avoir un navigateur qui ne supporte pas requestAnimationFrame, auquel cas notre fonction utilisera l’ancienne méthode setTimeout.

Pendant que nous y sommes, déplaçons l’event listener load de main.js vers utils.js

 window.addEventListener('load', function() {
  StartGame();
},

Compatibilité navigateurs

💻 Desktop : Chrome 10+, Firefox 4+, IE 10+, Safari 6+
📱 Mobile : Android 4.4+, iOS 6.1+

FeatureChromeFirefox (Gecko)Internet ExplorerOperaSafari (WebKit)
Basic support10.0 webkit
24.0
4.0 moz [1]
23 [2]
10.0(Oui) -o
15.0
6.0 webkit
6.1
requestID return value23.0 webkit
24.0
11.0 (11.0) moz10.0(Oui) -o
15.0
(Oui)

Implémenter une Game Loop dans l’objet Game

Nous pouvons enfin définir le GameLoop, c’est-à-dire la fonction qui nous permet de créer une boucle infinie dans laquelle on pourra gérer les animations et les événements frame après frame.

La méthode GameLoop() doit être appelée une fois au lancement. Elle se rappelle elle-même à chaque frame via requestAnimFrame, créant une boucle infinie.

Définissons la fonction GameLoop, à l’intérieur de l’objet Game :

 function Game() {
 
    // ... choses définies dans la leçon précédente
 
    this.GameLoop = function() {
   
        if(!this.paused) {
             
            // mettre à jour tous les objets
            this.Update(); 
        }
 
        // dessine toute la scène sur l'écran
        this.Draw();
         
        window.requestAnimFrame(function() {
     
            // relance la fonction GameLoop sur chaque image
            game.GameLoop(); 
        });
    }
}

Le cœur de la boucle est requestAnimFrame, dans lequel nous appelons à nouveau la fonction GameLoop de l’instance Game, de sorte qu’un mécanisme récursif est déclenché, dans lequel Update () et Draw () sont exécutés à chaque frame.

La plupart des frameworks de jeux vidéo prévoient que les objets du jeu (GameObject, que nous allons voir) ont des fonctions Update et Draw, ce qui en fait deux éléments fondamentaux du moteur et même s’ils ne sont pas encore définis, nous nous engageons à les définir rapidement.

Mise en pause de la logique du jeu

On peut également définir la variable paused, qui bloquera la fonction Update lorsqu’elle est définie sur true.

La variable paused permet d’interrompre les calculs de jeu tout en continuant à dessiner. Cela sera utile pour gérer les menus, pauses, ou écrans de chargement.

Remarque : Draw () reste toujours actif, de manière à afficher quand même les images.

🧠 Astuce : on pourra optimiser cette pause plus tard avec le canvas Offscreen.

➡️ Créer des jeux HTML5, le guide

Sudoku Quest Banner

Formations de Itamde

« 

Itamde est également une école de programmation en ligne.

Itamde

Apprenez ce que vous voulez, à votre rythme

0 commentaires

Soumettre un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Vous pourriez être intéressé par…

Restez informé des dernières actualités et mises à jour

Accédez au contenu réservé

Découvrez les coulisses de nos projets, des ressources exclusives et l’avancée de nos créations en temps réel.

Inscrivez-vous à la newsletter

Recevez nos actualités, nos réflexions créatives et les nouveautés de l’atelier directement dans votre boîte mail.

Suivez-nous

Rejoignez notre communauté sur les réseaux pour suivre nos projets au quotidien et échanger avec nous.