I. Avant-propos

Ce tutoriel est extrait de mon blog Game in Progress, destiné à partager mon expérience dans la réalisation d'un jeu en HTML 5 - JavaScript.

II. Menu principal

Dans le premier tutoriel dédié à la bibliothèque Box2dWeb nous avons vu comment initialiser un environnement physique 2D au sein d'un canvas HTML.
Ce deuxième tutoriel va nous permettre d'appréhender le déplacement des objets 2D et de mettre en place la gestion des contrôles utilisateur. Nous allons ici voir comment :

  • ajouter la gestion des contrôles utilisateur ;
  • simuler les déplacements et sauts d'un personnage à l'écran.

L'objectif est de parvenir à diriger un personnage dans un environnement 2D :

Image non disponible

II-A. Résultat final

Vous pouvez voir le résultat final ici.
Et pour récupérer les sources correspondantes, c'est ici.

II-B. Pré-requis

Si vous ne l'avez pas déjà fait, je ne saurais que trop vous conseiller de lire mon premier tutoriel.
Il constitue une bonne approche pour la mise en place d'un projet box2d et, si vous ne connaissez pas cette bibliothèque, il devrait vous permettre d'en appréhender les concepts et de découvrir les fonctionnalités de base.
Si vous êtes déjà initié à box2d, vous pouvez tout de même y jeter un œil, ne serait-ce que pour récupérer les fichiers sources qui nous serviront dans ce second tutoriel.

III. Paramétrage

III-A. Bibliothèques

Pas de changement par rapport au précédent tutoriel, nous allons utiliser les mêmes bibliothèques. Vous pouvez donc récupérer, si ce n'est déjà fait, les bibliothèques suivantes :

III-B. Structure du projet

Pas de grosse surprise ici non plus. Nous reprenons exactement la même structure que dans le précédent tutoriel :

Image non disponible

Nous ajoutons tout de même un fichier player.js qui nous servira de classe pour le joueur. N'oublions pas de l'ajouter à l'import des fichiers JavaScript dans le fichier index.html :

 
Sélectionnez
<script type="text/javascript" src="js/player.js"></script>

Hormis cette légère modification, le contenu des fichiers index.html et style.css reste inchangé.
Pour rappel, le fichier box2dUtils.js sert de classe utilitaire pour faciliter la construction des objets dans l'environnement physique, et le fichier gip.js est le moteur du jeu. Les modifications apportées à ces deux fichiers seront détaillées tout au long de ce tutoriel.

IV. Play !

IV-A. Le joueur et les interactions

Dans un premier temps nous allons créer un objet « Player » qui sera l'avatar du joueur à l'écran. Nous ajouterons une gestion des contrôles afin de permettre de le déplacer et de le faire sauter.

IV-A-1. Fichier Player.js

Commençons par créer l'objet Player. La classe Player est définie dans le fichier player.js. Pour le moment, nous allons définir les propriétés de classe et ajouter une première méthode permettant de créer l'objet physique "player" dans le monde box2d :

 
Sélectionnez
(function(){
    // Constructeur
    Player = function(scale) {
        this.scale = scale;             // échelle
        this.box2dUtils = new Box2dUtils(scale);    // instancier la classe utilitaire box2d
        this.object = null;             // l'objet "physique" player
    }
    // classe player
    Player.prototype = {
        // créer l'objet physique "player"
        createPlayer : function(world, x, y, radius) {
            this.object = this.box2dUtils.createPlayer(world, x, y, radius, 'player');
        }
    }
}());

Comme dans le précédent tutoriel, nous utilisons les méthodes de notre classe utilitaire box2dUtils pour créer les objets physiques. Ajoutons donc une fonction createPlayer à la classe box2dUtils :

 
Sélectionnez
createPlayer : function(world, x, y, radius, userData) {
    // Créer le body player
    var playerObject = this.createBall(world, x, y, radius, false, userData);
    playerObject.GetBody().SetSleepingAllowed(false);   // l'objet player n'est pas autorisé à passer au repos
    return playerObject;
}

Ici, le joueur sera représenté par un objet "ball". Nous aurions tout aussi bien pu utiliser un objet de type "box", mais cela donnera à notre avatar des allures de Pacman plus sympathiques !
Par ailleurs, nous spécifions à l'objet "Player" qu'il n'est pas autorisé à passer au repos en mettant sa propriété sleep à false via la méthode SetSleepingAllowed. En effet, le joueur doit toujours rester actif et interagir avec les éléments qui l'entourent.

Ajoutons à la classe Player des méthodes permettant de se déplacer et de sauter :

 
Sélectionnez
// Sauter
jump : function() {
    var vel = this.object.GetBody().GetLinearVelocity();
    vel.y = -200 / this.scale;
},
// Effectuer un déplacement vers la droite
moveRight : function() {
    var vel = this.object.GetBody().GetLinearVelocity();
    vel.x = 140 / this.scale;
},
// Effectuer un déplacement vers la gauche
moveLeft : function() {
    var vel = this.object.GetBody().GetLinearVelocity();
    vel.x = -140 / this.scale;
}

Les trois méthodes fonctionnent de la même façon : nous récupérons la vitesse de l'objet via la méthode GetLinearVelocity et nous la modifions en fonction du type de déplacement :

  • pour un déplacement vers la droite ou vers la gauche : nous modifions la vitesse sur l'axe des abscisses "x" ;
  • de la même manière, pour un déplacement vers le haut (un saut) : nous modifions la vitesse sur l'axe des ordonnées "y".

Notons que dans le cas d'un saut, nous affectons une valeur négative. N'oublions pas que, comme dans toute application, les coordonnées "y" de l'écran sont inversées. Ainsi, les coordonnées (0, 0) correspondent au coin supérieur gauche du canvas, tandis que le coin inférieur droit possède les coordonnées (800, 600). Un saut est un déplacement vers le haut, et donc vers une ordonnée "y" plus petite !
Libre à vous, évidemment, de modifier les valeurs 200 et 140 pour effectuer des déplacements plus rapides ou plus lents.

IV-A-2. Gestion des interactions

Notre avatar possède maintenant toutes les propriétés nécessaires pour s'afficher à l'écran, se déplacer et sauter. Reste à gérer les actions utilisateurs. Tout cela se passe dans le fichier gip.js, le moteur du jeu.

Dans un premier temps, ajoutons le joueur au monde 2dbox. Créons une référence pour le joueur :

 
Sélectionnez
(function(){
/** ... **/
    var player = null;
/** ... **/
}());

Puis instancions un nouveau Player dans la fonction init :

 
Sélectionnez
this.init = function() {
/** ... **/
        // Créer le player
        player = new Player(SCALE);
        player.createPlayer(world, 25, canvasHeight-30, 20);
/** ... **/
}

Il reste maintenant à gérer les actions de l'utilisateur. Pour cela, nous allons mettre en place une gestion des événements. Commençons par ajouter une variable keys en début de fichier :

 
Sélectionnez
(function(){
/** ... **/
    var keys = [];
/** ... **/
}());

Cette variable est un tableau dans lequel nous allons stocker les touches sur lesquelles appuie l'utilisateur. À l'initialisation, le tableau est bien sûr vide.
Revenons maintenant en fin de fonction init pour y ajouter des listeners d'événements :

 
Sélectionnez
// Ajouter les listeners d'événements
window.addEventListener('keydown', handleKeyDown);
window.addEventListener('keyup', handleKeyUp);

Si vous n'êtes pas habitués à la gestion des événements en JavaScript, je vous laisse vous référez à cet article. Ici, nous souhaitons détecter deux événements :

  • keydown : lorsque l'utilisateur appuie sur une touche. Auquel cas, nous appellerons la fonction handleKeyDown ;
  • keyup : lorsque l'utilisateur relâche une touche. Auquel cas, nous appellerons la fonction handleKeyUp.

Ajoutons ensuite ces deux fonctions au fichier :

 
Sélectionnez
// appuyer sur une touche
this.handleKeyDown = function(evt) {
    keys[evt.keyCode] = true;
}
// relâcher une touche
this.handleKeyUp = function(evt) {
    keys[evt.keyCode] = false;
}

Ces deux méthodes permettent de gérer les événements interceptés par les listeners. Elles nous permettent de stocker dans le tableau keys les informations suivantes sous forme de clé - valeur :

  • clé : le code de la touche ;
  • valeur : l'état de la touche sous forme de booléen, true si enfoncée, false si relâchée.

C'est un bon début, mais cela ne nous permet pas encore de déplacer notre joueur. Pour cela, nous allons devoir passer par la fonction magique : update. Souvenez-vous, cette fonction est appelée à intervalle régulier pour mettre à jour le rendu de l'environnement 2D. Nous allons lui demander de gérer en plus les interactions utilisateurs. Ajoutons un appel à une nouvelle méthode au début de la fonction update :

 
Sélectionnez
// Mettre à jour le rendu de l'environnement 2d
this.update = function() {
    // gérer les interactions
    handleInteractions();
    /** ... **/
}

Et ajoutons la fonction handleInteractions à notre fichier :

 
Sélectionnez
// Gérer les interactions
this.handleInteractions = function() {
    // touche "haut"
    if (keys[38]) {
        player.jump();
    }
    // touches "gauche" et "droite"
    if (keys[37]) {
        player.moveLeft();
    } else if (keys[39]) {
        player.moveRight();
    } 
}

À chaque appel de cette fonction, nous analysons le contenu du tableau de touches et effectuons l'action correspondante :

  • si la touche "haut" est enfoncée : appel à la méthode jump du joueur ;
  • si la touche "gauche" ou "droite" est enfoncée : appel à la méthode moveLeft ou moveRight du joueur.

L'instruction est donc reléguée au joueur qui sait comment effectuer l'action correspondante, à savoir sauter et se déplacer vers la gauche ou vers la droite.

IV-B. Environnement et finitions

Pour finir, mettons en place l'environnement de jeu, toujours en utilisant les fonctions de notre classe utilitaire box2dutils :

 
Sélectionnez
// Créer le "sol" et le "plafond" de notre environnement physique
ground = box2dUtils.createBox(world, 400, canvasHeight - 10, 400, 10, true, 'ground');
ceiling = box2dUtils.createBox(world, 400, -5, 400, 1, true, 'ceiling');

// Créer les "murs" de notre environnement physique
leftWall = box2dUtils.createBox(world, -5, canvasHeight, 1, canvasHeight, true, 'leftWall');
leftWall = box2dUtils.createBox(world, canvasWidth + 5, canvasHeight, 1, canvasHeight, true, 'leftWall');

// Créer les "box"
box2dUtils.createBox(world, 250, 0, 40, 40, false, 'box');
box2dUtils.createBox(world, 450, 0, 100, 100, false, 'box');

Nous avons ajouté ici les objets physiques suivants :

  • un sol afin que nos objets ne tombent pas dans le vide ;
  • un plafond et des murs pour "limiter" la zone et ainsi empêcher le joueur de sortir de l'espace de jeu ;
  • deux box "mobiles" afin d'observer leur comportement par défaut lorsque le joueur entrera en collision avec elles.

Enfin, nous allons parfaire cette application en désactivant le scrolling vertical de la fenêtre lors de l'appui sur les touches haut et bas du clavier. Cette petite amélioration reste optionnelle mais rend toutefois l'utilisation plus confortable. En effet, cela permet d'éviter de déclencher le défilement de la page lors de l'appui sur les touches directionnelles. Pour ce faire, insérons simplement ce petit bout de code juste après l'ajout des listeners :

 
Sélectionnez
// Désactiver les scrollings verticaux lors d'un appui sur les touches directionnelles "haut" et "bas"
document.onkeydown = function(event) {
    return event.keyCode != 38 && event.keyCode != 40;
}

V. Game over

Ce second tutoriel est maintenant terminé. Si tout s'est déroulé comme prévu, vous devriez obtenir ce résultat. Et je vous rappelle également que les sources sont à votre disposition.

Dans ce tutoriel, nous avons vu comment déplacer un personnage à l'écran en gérant les contrôles utilisateurs via la gestion des événements. Par ailleurs, on peut remarquer que le moteur physique gère lui-même les interactions entre le joueur et les éléments qui l'entourent puisque aucun code n'a été nécessaire pour permettre au joueur de pousser les caisses.

En revanche, le résultat n'est pas encore complètement satisfaisant puisque le saut n'est pas vraiment convaincant ! En effet, il est possible d'effectuer un saut "en l'air", de "grimper" aux murs, de marcher au plafond et, pire encore, un maintien très long de la touche directionnelle "haut" donne la sensation de flotter ou de voler…

Pas d'inquiétude cependant, la résolution de ces problèmes fera l'objet du prochain tutoriel. Celui-ci nous permettra de voir comment simuler les sauts de manière plus réaliste et comment gérer les collisions.

Je vous rappelle enfin que ce tutoriel est extrait de mon blog, n'hésitez pas à aller y faire un petit tour !

VI. Remerciements

Je tiens à remercier LittleWhite pour son aide dans la finalisation du document et pour l'intérêt qu'il porte à ma série de tutoriels sur Box2d Web.

Je remercie également ced pour son travail de relecture et les corrections apportées à cet article.