Gestion simple des éléments pour les jeux HTML5

Introduction

HTML5 fournit de nombreuses API utiles pour créer des applications Web modernes, responsives et puissantes dans le navigateur. C'est bien, mais vous voulez vraiment créer et jouer à des jeux ! Heureusement, HTML5 a également ouvert une nouvelle ère du développement de jeux qui utilise des API telles que Canvas et de puissants moteurs JavaScript pour diffuser des jeux directement dans votre navigateur sans avoir besoin de plug-ins.

Cet article vous explique comment créer un composant de gestion des éléments simple pour votre jeu HTML5. Sans gestionnaire d'assets, votre jeu aura du mal à compenser les temps de téléchargement inconnus et le chargement d'images asynchrone. Suivez-nous pour découvrir un exemple de gestionnaire d'assets simple pour vos jeux HTML5.

Le problème

Les jeux HTML5 ne peuvent pas supposer que leurs éléments tels que les images ou l'audio se trouvent sur l'ordinateur local du joueur, car les jeux HTML5 sont joués dans un navigateur Web avec des éléments téléchargés via HTTP. Étant donné que le réseau est impliqué, le navigateur ne sait pas quand les composants du jeu seront téléchargés et disponibles.

Le code suivant est la méthode de base pour charger une image de manière programmatique dans un navigateur Web:

var image = new Image();
image.addEventListener("success", function(e) {
  // do stuff with the image
});
image.src = "/some/image.png";

Imaginez maintenant que vous devez charger et afficher une centaine d'images au démarrage du jeu. Comment savoir quand les 100 images sont prêtes ? Ont-ils tous été chargés ? À quel moment le jeu doit-il commencer ?

La solution

Laissez un gestionnaire d'assets gérer la mise en file d'attente des éléments et renvoyer un rapport au jeu lorsque tout est prêt. Un gestionnaire d'éléments généralise la logique de chargement des éléments sur le réseau et permet de vérifier facilement leur état.

Notre gestionnaire d'assets simple doit respecter les exigences suivantes:

  • mettre en file d'attente des téléchargements ;
  • lancer des téléchargements ;
  • suivre les réussites et les échecs ;
  • signaler lorsque tout est terminé ;
  • récupération facile des éléments

Mise en file d'attente

La première exigence consiste à mettre les téléchargements en file d'attente. Cette conception vous permet de déclarer les composants dont vous avez besoin sans les télécharger. Cela peut être utile si, par exemple, vous souhaitez déclarer tous les éléments d'un niveau de jeu dans un fichier de configuration.

Le code du constructeur et de la mise en file d'attente se présente comme suit:

function AssetManager() {
  this.downloadQueue = [];
}

AssetManager.prototype.queueDownload = function(path) {
    this.downloadQueue.push(path);
}

Lancer les téléchargements

Une fois que vous avez mis en file d'attente tous les composants à télécharger, vous pouvez demander au gestionnaire d'assets de commencer à tout télécharger.

Heureusement, le navigateur Web peut paralléliser les téléchargements, généralement jusqu'à quatre connexions par hôte. Pour accélérer le téléchargement des éléments, vous pouvez utiliser une gamme de noms de domaine pour l'hébergement des éléments. Par exemple, au lieu de diffuser tout à partir de assets.example.com, essayez d'utiliser assets1.example.com, assets2.example.com, assets3.example.com, etc. Même si chacun de ces noms de domaine est simplement un CNAME du même serveur Web, le navigateur Web les considère comme des serveurs distincts et augmente le nombre de connexions utilisées pour le téléchargement d'éléments. Pour en savoir plus sur cette technique, consultez Diviser les composants entre les domaines dans les bonnes pratiques pour accélérer votre site Web.

Notre méthode d'initialisation du téléchargement s'appelle downloadAll(). Nous allons l'étoffer au fil du temps. Pour l'instant, voici la première logique permettant de lancer les téléchargements.

AssetManager.prototype.downloadAll = function() {
    for (var i = 0; i < this.downloadQueue.length; i++) {
        var path = this.downloadQueue[i];
        var img = new Image();
        var that = this;
        img.addEventListener("load", function() {
            // coming soon
        }, false);
        img.src = path;
    }
}

Comme vous pouvez le voir dans le code ci-dessus, downloadAll() itère simplement dans la file d'attente de téléchargement et crée un objet Image. Un écouteur d'événements pour l'événement de chargement est ajouté et la source de l'image est définie, ce qui déclenche le téléchargement réel.

Cette méthode vous permet de lancer les téléchargements.

Suivre les réussites et les échecs

Une autre exigence consiste à suivre à la fois les succès et les échecs, car malheureusement, tout ne fonctionne pas toujours parfaitement. Pour le moment, le code ne suit que les éléments téléchargés avec succès. En ajoutant un écouteur d'événements pour l'événement d'erreur, vous pourrez capturer à la fois les scénarios de réussite et d'échec.

AssetManager.prototype.downloadAll = function(downloadCallback) {
  for (var i = 0; i < this.downloadQueue.length; i++) {
    var path = this.downloadQueue[i];
    var img = new Image();
    var that = this;
    img.addEventListener("load", function() {
        // coming soon
    }, false);
    img.addEventListener("error", function() {
        // coming soon
    }, false);
    img.src = path;
  }
}

Notre gestionnaire d'actifs doit savoir combien de succès et d'échecs nous avons rencontrés, sinon il ne saura jamais quand le jeu pourra commencer.

Tout d'abord, nous allons ajouter les compteurs à l'objet dans le constructeur, qui se présente désormais comme suit:

function AssetManager() {
<span class="highlight">    this.successCount = 0;
    this.errorCount = 0;</span>
    this.downloadQueue = [];
}

Ensuite, augmentez les compteurs dans les écouteurs d'événements, qui se présentent désormais comme suit:

img.addEventListener("load", function() {
    <span class="highlight">that.successCount += 1;</span>
}, false);
img.addEventListener("error", function() {
    <span class="highlight">that.errorCount += 1;</span>
}, false);

Le gestionnaire d'assets suit désormais à la fois les éléments chargés avec succès et ceux qui ont échoué.

Signaler la fin

Une fois que le jeu a mis en file d'attente ses éléments à télécharger et demandé au gestionnaire d'éléments de les télécharger tous, il doit être informé lorsque tous les éléments sont téléchargés. Au lieu que le jeu demande sans cesse si les composants sont téléchargés, le gestionnaire de composants peut renvoyer un signal au jeu.

Le gestionnaire d'éléments doit d'abord savoir quand chaque élément est terminé. Nous allons maintenant ajouter une méthode isDone:

AssetManager.prototype.isDone = function() {
    return (this.downloadQueue.length == this.successCount + this.errorCount);
}

En comparant le nombre de succès et d'erreurs à la taille de la file d'attente de téléchargement, le gestionnaire d'éléments sait si chaque élément a bien été importé ou s'il a rencontré une erreur.

Bien sûr, savoir si c'est fait n'est que la moitié de la bataille. Le gestionnaire d'assets doit également vérifier cette méthode. Nous allons ajouter cette vérification dans nos deux gestionnaires d'événements, comme le montre le code ci-dessous:

img.addEventListener("load", function() {
    console.log(this.src + ' is loaded');
    that.successCount += 1;
    if (that.isDone()) {
        // ???
    }
}, false);
img.addEventListener("error", function() {
    that.errorCount += 1;
if (that.isDone()) {
        // ???
    }
}, false);

Une fois les compteurs incrémentés, nous verrons s'il s'agit du dernier élément de la file d'attente. Si le gestionnaire d'assets a bien terminé le téléchargement, que devons-nous faire exactement ?

Si le gestionnaire d'assets a terminé de télécharger tous les composants, nous appelons bien sûr une méthode de rappel. Modifions downloadAll() et ajoutons un paramètre pour le rappel:

AssetManager.prototype.downloadAll = function(downloadCallback) {
    ...

Nous appellerons la méthode downloadCallback dans nos écouteurs d'événements:

img.addEventListener("load", function() {
    that.successCount += 1;
    if (that.isDone()) {
        downloadCallback();
    }
}, false);
img.addEventListener("error", function() {
    that.errorCount += 1;
    if (that.isDone()) {
        downloadCallback();
    }
}, false);

Le gestionnaire d'assets est enfin prêt pour la dernière exigence.

Récupération facile des éléments

Une fois que le jeu a reçu le signal qu'il peut commencer, il commence à afficher des images. Le gestionnaire d'éléments est chargé non seulement de télécharger et de suivre les éléments, mais aussi de les fournir au jeu.

Notre exigence finale implique une sorte de méthode getAsset. Nous allons donc l'ajouter maintenant:

AssetManager.prototype.getAsset = function(path) {
    return this.cache[path];
}

Cet objet de cache est initialisé dans le constructeur, qui se présente désormais comme suit:

function AssetManager() {
    this.successCount = 0;
    this.errorCount = 0;
    this.cache = {};
    this.downloadQueue = [];
}

Le cache est renseigné à la fin de downloadAll(), comme indiqué ci-dessous:

AssetManager.prototype.downloadAll = function(downloadCallback) {
  ...
      img.addEventListener("error", function() {
          that.errorCount += 1;
          if (that.isDone()) {
              downloadCallback();
          }
      }, false);
      img.src = path;
      <span class="highlight">this.cache[path] = img;</span>
  }
}

Bonus: Correction de bug

Avez-vous repéré le bug ? Comme indiqué ci-dessus, la méthode isDone n'est appelée que lorsque des événements de chargement ou d'erreur sont déclenchés. Mais que se passe-t-il si le gestionnaire d'assets n'a aucun élément en file d'attente pour le téléchargement ? La méthode isDone n'est jamais déclenchée et le jeu ne démarre jamais.

Pour ce scénario, vous pouvez ajouter le code suivant à downloadAll():

AssetManager.prototype.downloadAll = function(downloadCallback) {
    if (this.downloadQueue.length === 0) {
      downloadCallback();
  }
 ...

Si aucun composant n'est mis en file d'attente, le rappel est appelé immédiatement. Bug corrigé

Exemple d'utilisation

L'utilisation de ce gestionnaire d'assets dans votre jeu HTML5 est assez simple. Voici la méthode la plus simple pour utiliser la bibliothèque:

var ASSET_MANAGER = new AssetManager();

ASSET_MANAGER.queueDownload('img/earth.png');

ASSET_MANAGER.downloadAll(function() {
    var sprite = ASSET_MANAGER.getAsset('img/earth.png');
    ctx.drawImage(sprite, x - sprite.width/2, y - sprite.height/2);
});

Le code ci-dessus illustre les points suivants:

  1. Crée un gestionnaire de composants
  2. Mettre en file d'attente des éléments à télécharger
  3. Lancer les téléchargements avec downloadAll()
  4. Signalez lorsque les composants sont prêts en appelant la fonction de rappel.
  5. Récupérez des composants avec getAsset().

Domaines à améliorer

Vous allez sans doute dépasser ce simple gestionnaire d'assets à mesure que vous développerez votre jeu, mais j'espère qu'il vous a servi de point de départ. Voici quelques-unes des fonctionnalités futures possibles:

  • indiquer quel composant a généré une erreur ;
  • rappels pour indiquer la progression
  • récupérer des éléments à partir de l'API File System ;

Veuillez indiquer les améliorations, les fourchettes et les liens vers le code dans les commentaires ci-dessous.

Source complète

Le code source de ce gestionnaire d'assets et du jeu dont il est extrait est Open Source sous licence Apache. Il est disponible sur le compte GitHub de Bad Aliens. Vous pouvez jouer au jeu Bad Aliens dans votre navigateur compatible avec HTML5. Ce jeu a fait l'objet de ma conférence Google IO intitulée Super Browser 2 Turbo HD Remix: introduction au développement de jeux HTML5 (diapositives, vidéo).

Résumé

La plupart des jeux disposent d'un gestionnaire d'éléments, mais les jeux HTML5 nécessitent un gestionnaire d'éléments qui charge les éléments sur un réseau et gère les échecs. Cet article a décrit un gestionnaire d'assets simple que vous devriez pouvoir utiliser et adapter facilement pour votre prochain jeu HTML5. Amusez-vous bien et n'hésitez pas à nous donner votre avis dans les commentaires ci-dessous. Merci !