Il s'agit d'une version statique de l'intranet d'A. Jonquet, certaines fonctionnalités sont donc potentiellement non fonctionnelles.
Rejoindre la version dynamique 🔒
R301
Navigation

Découverte de Phaser

Ce premier TP portera sur la mise en place d'un environnement de travail JavaScript moderne dans le cadre de la présentation de la bibliothèque de création de jeu Phaser.

Ce sera l'occasion de mettre en application les bonnes pratiques et les outils associés, largement plébiscités dans le monde du développement JavaScript moderne.

Le code étant relativement simpliste, nous nous focaliserons sur les bonnes pratiques et les outils.

Remarque importante

Les notes des TP seront obtenues à partir de vos dépôts Git, vous devrez donc prendre grand soin de la qualité de ces dépôts et de leurs « commits ».

De manière similaire, les descriptions de vos « commits » devront être claires et informatives, afin de permettre l'évaluation de la progression de votre travail.

Objectifs de la séance

  • Gestion de versions à l'aide de Git
  • Gestion de paquets npm
  • Mise en place d'un environnement de travail JavaScript
  • Découverte de Phaser
  • Approfondissement du langage JavaScript

Mise en place d'un projet npm

La gestion du projet sera réalisée au travers d'un paquet npm.

Remarque importante

Comme pour les projets Composer, votre projet va générer de nombreux petits fichiers qui seront stockés localement dans le répertoire « node_modules » de votre projet.

Les infrastructures des systèmes d'information de l'université et du département informatique compliquent l'accès rapide à de très nombreuses ressources. Afin d'obtenir des temps de réaction convenables pour les différents outils que vous utiliserez, vous êtes encouragé à travailler dans le répertoire local « /working » de votre poste de travail.

Afin de ne pas laisser votre travail uniquement sur la machine, vous devrez impérativement réaliser un push en fin de séance afin de le sauver sur le serveur Gitlab.

Vous pourrez facilement réinitialiser votre projet sur une nouvelle machine à l'aide de la commande git clone suivie de npm install dans le répertoire du projet. Cette dernière commande permet de réinstaller tous les modules npm dont dépend votre projet.

Comme nous le verrons dans les TP suivants, la mise en place du projet peut être réalisée à l'aide de la commande vite que vous utiliserez plus tard, mais dans un but pédagogique, vous allez réaliser la mise en place des éléments, à la main, un par un.

Vous commencerez par créer un répertoire de travail pour votre projet nommé r301-js-introduction.

Dans ce répertoire, vous allez créer un nouveau paquet npm à l'aide de la commande : npm init --yes, permettant d'initialiser le paquet npm en créant un fichier « package.json » dans le répertoire courant. Cette commande est normalement interactive, mais l'option --yes initialise le fichier « package.json » avec les valeurs par défaut.

Vous pouvez modifier ces valeurs en éditant directement le fichier « package.json », une liste exhaustive des différentes options est disponible dans la documentation de npm.

Dans notre cas :

  • Vous supprimerez le point d'entrée (main) du projet,
  • Vous préciserez que le projet est privé (private),
  • Et vous définirez le projet comme un projet « ES module » (type).

La gestion de versions et l'archivage seront réalisés à l'aide de Git. Pour un rapide tour d'horizon de Git, vous pouvez vous référer au site de Monsieur Nourrit.

Vous commencerez par créer un dépôt local à l'aide de la commande  : git init. Cette commande provoque la création d'un répertoire "caché" « .git » contenant les données du dépôt local.

Git propose de définir dans un fichier « .gitignore » les fichiers ou répertoires à ignorer lors de son suivi de version. En vous basant sur la documentation ou le paragraphe relatif du livre « Pro Git », vous ignorerez le répertoire « node_modules » et éventuellement les répertoires de votre éditeur.

Vous pourrez ensuite indiquer à Git de suivre tous les fichiers actuellement dans votre répertoire : git add ..

Vous pourrez contrôler les fichiers suivis (« package.json » et « .gitignore ») à l'aide de la commande git status et corriger le problème sinon. Si tout va bien, vous pourrez réaliser le premier « commit » de votre dépôt : git commit -m "Initial commit".

Vous associerez votre dépôt local à un dépôt distant nommé « r301-js-introduction ». Vous veillerez à ajouter votre intervenant de TP comme membre du projet avec un rôle de « Reporter ».

Vous pourrez ensuite associer votre dépôt local au dépôt distant à l'aide de la commande :git remote add origin URL_DU_DÉPÔT, puis pousser la réplication de votre dépôt local grâce à la commande : git push.

Travail à réaliser
  • Créer un répertoire local.
  • Y créer un module npm en générant et configurant un fichier package.json.
  • Y créer un dépôt Git local.
  • Exclure le répertoire « node_modules » du versionnage.
  • Répliquer le dépôt local sur le serveur distant GitLab.
  • Vérifier les membres du projet distant GitLab et leurs rôles.

Mise en place du projet initial

npm est l'acronyme de Node Package Manager, un gestionnaire de paquet pour Node. Il permet simplement d'ajouter des outils ou des bibliothèques à vos projets dans l'environnement Node.

Remarque importante

Dans le cadre du département Informatique, les paquets npm ajoutés à votre projet sont installés en local, car vous n'avez pas les droits d'écriture sur les machines. Ils sont donc installés dans le sous répertoire « node_modules » de votre projet. Bien que vous travailliez dans le répertoire « /working » de votre poste de travail, npm conserve un cache sur votre compte des paquets installés. Vous êtes invité à configurer npm pour qu'il utilise un répertoire temporaire afin d'économiser de la place sur votre compte et de gagner en performance d'installation.

Dans le cadre des bibliothèques JavaScript pour le web, le support des imports de fichiers dans le navigateur n'est pas encore parfait, il est donc courant d'avoir recours à un « bundler » permettant de regrouper un fichier et ses dépendances en un seul fichier, optimisé, importable dans le navigateur.

Dans notre cas, nous utiliserons le « bundler » vite. Il s'agit d'un outil permettant de mettre en place rapidement des projets JavaScript pour le web, comprenant la compilation du code JavaScript et un serveur web de développement avec rafraîchissement automatique.

Dans ce TP, vous n'utiliserez pas vite pour mettre en place le projet, que vous avez déjà commencé à configurer.

Vous allez installer le paquet npm de vite manuellement, comme dépendance de développement, à l'aide de la commande suivante :

npm install vite --save-dev

Vous venez d'installer l'outil vite dans votre projet, que vous pouvez lancer à l'aide de la commande npx vite.

Cependant, vite lance un serveur web de développement, mais il ne trouve rien à vous fournir pour l'instant.

Par défaut, vite cherche un script JavaScript « index.js » ou une page HTML « index.html » dans le répertoire courant comme point d'entrée de votre projet. Comme nous souhaitons ranger tout notre code dans un répertoire « src », vous ajouterez le fichier de configuration de vite suivant (« vite.config.js ») à la racine de votre projet :

Ce script de configuration importe une fonction de node : join du module path permettant de concaténer des chemins de répertoire indépendamment du système. Et il expose un object JavaScript contenant les options de configuration de vite :

  • le répertoire racine de travail de vite sera « src »,
  • le répertoire où vite produira son travail sera « build »,
  • vite utilisera le répertoire « public » pour fournir les fichiers statiques.

import.meta.dirname est une constante contenant le chemin du répertoire contenant votre projet npm (« package.json »).

Pour finir, vous créerez les répertoires « src » et « public » qui accueilleront respectivement les sources de votre projet et les ressources statiques.

Dans le répertoire « src », vous ajouterez une page HTML 5 « index.html », vide pour l'instant, mais valide, et un script JavaScript « index.js » contenant une alerte affichant un message.

Dans le répertoire « public », vous ajouterez un script CSS qui se contentera d'un peu de mise en forme qui puisse être visible dans la page, comme changer la couleur du fond, par exemple.

Vous référencerez ces deux scripts JavaScript et CSS dans votre page HTML. Pour le script JavaScript, vous prendrez soin d'importer votre script comme un module JavaScript à l'aide de l'attribut type.

Vous pouvez maintenant réaliser la construction de votre projet à l'aide de la commande npx vite build qui créera un répertoire « build » (que vous ignorerez dans votre versionnage) contenant votre projet. Ce répertoire contient toutes les données du site que vous allez créer, c'est lui que vous pouvez distribuer ou qu'il suffit de copier sur un serveur web pour le rendre accessible.

Avant de le distribuer, il peut être utile de contrôler que tout est bien fonctionnel. vite proposant un serveur web, il est possible de le lancer sur ce répertoire à l'aide de la commande npx vite preview, puis de vérifier dans le navigateur.

La construction du projet et le serveur de prévisualisation fonctionnent bien, mais il faut relancer la construction du projet et recharger la page pour chaque modification, ce qui entraîne une piètre expérience de développement. Une expérience plus agréable repose sur l'utilisation du serveur de développement à l'aide de la commande npx vite qui lance un serveur web local (http://localhost:5173/) servant votre page HTML à partir du répertoire « src ». Vous contrôlerez le résultat, en vérifiant que votre JavaScript et votre CSS sont bien executés. Vous constaterez au passage le fonctionnement du « hot reloading », qui actualise la page en direct lors de la modification du code source (dans « src ») mais pas pour les ressources statiques (dans « public »).

Pour finir, la configuration initiale de votre projet, afin de ne pas retenir et/ou de centraliser toutes les commandes disponibles pour le projet, il est courant de les centraliser dans la section scripts du « package.json » sous forme de scripts npm. Vous remplacerez la section scripts actuelle par celle-ci :

Vous pouvez maintenant lancer vos scripts npm à l'aide de la commande npm run NOM_DU_SCRIPT

Travail à réaliser
  • Installation comme dépendance de développement du paquet npm vite.
  • Création du fichier de configuration de vite.
  • Création d'une page HTML.
  • Création d'un script JavaScript.
  • Création d'un script CSS.
  • Création des scripts npm de gestion du projet.

Installation de Phaser

Sur le même principe que précédemment, vous allez installer comme dépendance de développement le paquet npm de la bibliothèque Phaser.

Contrairement au paquet npm vite qui installe un outil JavaScript à utiliser depuis la console, Phaser installe une bibliothèque que vous allez pouvoir exploiter dans votre code. Une base simple d'utilisation que l'on retrouve facilement sur le web, pourrait être le code suivant :

Vous remplacerez le contenu du fichier « index.js » par le code précédent.

Le code commence par importer la bibliothèque Phaser dans une variable nommée Phaser. Il crée ensuite la configuration de l'instance de jeu sous la forme d'un objet JavaScript recevant des couples clé/valeur. Et enfin, il crée l'instance de jeu à l'aide de la classe Game de la bibliothèque et de la configuration définie précédemment.

L'objet de configuration du jeu supporte de nombreuses options. Ici  :

  • type correspond au moteur de rendu employé par Phaser, nous laissons Phaser choisir en fonction du support logiciel. Généralement le rendu sera réalisé en WebGL, et en cas de non-support, il se repliera sur un canvas HTML.
  • width et height définissent la taille du jeu en pixels.
  • scene correspond à la scène du jeu qui est affichée au démarrage du jeu. Nous y reviendrons bientôt, mais pour l'instant, vous définissez une scène ne réalisant rien.
Travail à réaliser
  • Installation de Phaser.
  • Initialisation d'un projet Phaser.

Configuration de l'environnement JavaScript

Avant de commencer à expérimenter la bibliothèque Phaser, vous allez mettre en place des outils de « qualité de code » couramment utilisés par la communauté JavaScript. Ces outils ont pour vocation d'améliorer la « qualité du code » de votre projet en réalisant une analyse statique du code lors de sa production, permettant d'éviter de nombreuses erreurs ou mauvaises pratiques très rapidement, sans avoir à faire de nombreux aller-retours entre le code et le résultat produit. De plus, l'analyse statique de votre code permet d'imposer une mise en forme, qui uniformise votre base de code et simplifie le suivi du versionnage, notamment lors d'un projet collaboratif.

Pour l'analyse de votre code, vous allez installer un linter permettant de contrôler votre code lors de son édition. ESLint est un linter largement employé dans la communauté JavaScript. Il est intégralement configurable, au point qu'il est très complexe et fastidieux de le faire soi-même. Dans notre cas, nous utiliserons un ensemble de règles pré-configuré.

Pour installer ESLint, vous utiliserez l'outil interactif d'ESLint à l'aide de la commande suivante :

npx eslint --init

ESLint vous pose quelques questions pour configurer l'environnement :

  • « How would you like to use ESLint? » : « To check syntax and find problems »,
  • « What type of modules does your project use? » : « JavaScript modules (import/export) »,
  • « Which framework does your project use? » : « None of these »,
  • « Does your project use TypeScript? » : « No »,
  • « Where does your code run? » : « Browser » et « Node » (changer le focus au clavier avec les flêches et sélectionner avec espace, valider avec entrée),
  • ESLint devrait vous proposer d'installer les dépendances suivantes : eslint, globals, @eslint/js, « Would you like to install them now with npm? » : « Yes »,
  • « Which package manager do you want to use? » : « npm »,

Une fois les paquets npm installés, vous disposez de l'outil ESLint configuré et prêt dans votre projet. Cependant, avant de l'utiliser, vous allez ajouter quelques modifications à la configuration par défaut.

Pour commencer, vous allez exclure le répertoire « node_module » des répertoires de travail d'ESLint. La configuration à « plat » d'ESLint se compose d'un tableau contenant une liste de configuration à appliquer successivement. Dans l'état, vous devriez pouvoir ajouter, à la première configuration de votre script de configuration (« eslint.config.js »), une propriété ignores contenant le répertoire « node_module ».

Ensuite, la configuration par défaut d'ESLint est plutôt permissive, vous allez donc ajouter un peu de contraintes sur votre code. Mais afin de ne pas configurer vous-même toutes les règles, vous allez utiliser comme base la configuration ESLint de l'équipe de développement d'ESLint. Vous installerez le paquet npm et vous l'ajouterez à la suite de votre configuration d'ESLint comme précisé dans la documentation.

Vous devriez maintenant être en mesure de constater des erreurs dans votre code à l'aide de la commande

npx eslint *.js **/*.js

Mais avant de résoudre ces erreurs, vous allez lui adjoindre un formateur de code. En effet, même si ESLint dispose de quelques règles sur la mise en forme du code, pour uniformiser le rendu du code d'un projet, il est courant de lui ajouter Prettier.

Cependant, comme il est vite fastidieux de gérer les deux outils en parallèle et que ceux-ci fonctionnent très bien de concert, il est courant d'installer un paquet désactivant les règles d'ESLint en conflit avec Prettier et le plugin ESLint permettant d'exécuter Prettier comme un ensemble de règles d'ESLint.

Comme précédemment, vous ajouterez à la suite de votre configuration d'ESLint conformément à la documentation

En relançant ESLint, vous devriez constater de nouvelles erreurs cette fois-ci concernant la mise en forme de votre code. En étudiant le message d'erreur, vous devriez voir qu'ESLint propose de régler une grosse partie de ces problèmes lui-même, à l'aide de l'option --fix :

npx eslint *.js **/*.js --fix

Vous ajouterez un script npm permettant de fixer tous les problèmes du projet en une fois et vous vérifierez qu'il fonctionne bien.

Lors du développement, il est encore fastidieux de devoir exécuter une commande pour controller son code, vous allez donc configurer votre éditeur pour qu'il utilise ESLint pour vous montrer les erreurs directement dans votre code.

WebStorm/PhpStorm doivent fonctionner directement avec ESLint si vous l'activer dans les paramètres. VS Code nécessite l'installation du greffon ESLint, si celui-ci n'est pas présent. Vous prendrez soin de vérifié que l'option « Use Flat Config » est bien cochée afin qu'il puisse trouver votre fichier de configuration. Vos erreurs devraient maintenant être soulignées dans votre code. Il est important que vous les corrigiez toutes au fur et à mesure de votre développement.

Pour éviter de fixer vous-même toutes les erreurs qu'ESLint peut régler pour vous, vous pouvez configurer votre éditeur pour qu'il tente de fixer toutes les erreurs à la sauvegarde. WebStorm/PhpStorm propose une case à cocher « run eslint --fix on save » dans la configuration et pour VS Code, vous devez ajouter le « hook » suivant dans votre configuration :

Intéressons-nous maintenant à la dernière erreur qu'il doit vous rester, dans le fichier « index.js », qu'ESLint ne veut pas résoudre tout seul. Il s'agit d'une violation de la règle no-new, interdisant l'utilisation de l'opérateur new hors d'une affectation ou d'une comparaison. En effet, vous construisez ici une instance de jeu dont vous ne faites rien, celle-ci semble être détruite juste après. En réalité, l'instance de jeu est référencée dans la page web lors de la création de l'instance, il aurait été plus explicite de déporter cet enregistrement dans une méthode d'instance. Cependant, comme nous ne pouvons pas modifier la bibliothèque Phaser, vous allez définir une exception pour cette règle pour cette ligne (vous pouvez le faire directement depuis votre éditeur en survolant l'erreur avec votre curseur). Une nouvelle erreur devrait être apparue sur votre commentaire. En effet, vous venez de désactiver un contrôle, il est de bon aloi d'en précisé la raison en commentaire pour vos collègues ou votre futur vous-même.

Travail à réaliser
  • Installation d'ESLint.
  • Installation de Prettier.
  • Ajout de la configuration ESLint de l'équipe d'ESLint.
  • Configuration d'ESLint.
  • Ajout d'un script npm.
  • Configuration de l'éditeur de code.

Documentation et surface de jeu

Phaser n'est pas la seule solution pour réaliser des jeux en JavaScript, plusieurs alternatives sont disponibles, certaines plus petites, efficaces ou bas niveau... Mais la bibliothèque Phaser dispose de certains avantages, notamment, elle est fournie avec de nombreuses fonctionnalités déjà incluses, et surtout, elle dispose d'une grande communauté et d'une documentation exhaustive.

Vous avez dû constater que la surface de votre jeu ne correspond pas aux dimensions définies dans les options de configuration (800×600). En effet, par défaut, Phaser construit bien une surface de jeu à la bonne dimension, mais il l'étend ensuite pour qu'elle prenne tout l'espace, sans tenir compte du ratio largeur/hauteur.

Vous en profiterez pour vous familiariser avec la documentation pour trouver dans les options de configuration comment conserver le ratio initial de la surface de jeu.

Vous pourrez faire un tour rapide du site web de Phaser afin de découvrir les possibilités de la bibliothèque, notamment la documentation des concepts, les exemples ou les tutoriels de la communauté.

Travail à réaliser
  • Découvrir la documentation de la bibliothèque Phaser.
  • Corriger la déformation de la surface de jeu.

Ajout d'éléments de jeu

Maintenant que tout est en place, vous allez pouvoir ajouter des éléments de jeu à l'écran.

Phaser organise les différentes phases de jeu en scènes. Vous commencerez par créer un nouvel objet JavaScript en début de script qui contiendra la scène de jeu.

Une scène dans Phaser peut prendre de nombreuses formes, mais fondamentalement, il s'agit d'un conteneur contenant différentes fonctions que le jeu va invoquer à différents moments du jeu. C'est un patron de développement classique permettant d'injecter du code dans un processus, on parle généralement de « hook ».

Pour commencer, vous ajouterez à la scène une méthode create avant de fournir cette scène à la configuration de votre jeu. Il s'agit de la méthode invoquée par Phaser, permettant de créer tous les éléments de la scène.

Conformément aux concepts Objet, vous pouvez accéder, depuis les méthodes de l'instance de scène, à l'instance de scène à l'aide de la variable this. Depuis cette instance, vous pouvez obtenir le générateur d'objets à l'aide de la propriété add. Ce générateur permet d'ajouter toutes sortes d'objets dans la scène, pour commencer simplement, vous ajouterez 2 disques de rayon 25 pixels, centrés respectivement en (200, 200) et (600, 200).

De manière similaire, vous ajouterez à la zone de jeu :

  • un texte contenant "A first scene" en haut au centre,
  • une étoile au centre
  • et un rectangle en bas au centre.

Pour positionner les éléments, tout en vous évitant des calculs fastidieux, vous pourrez redéfinir l'origine des éléments de jeu.

Travail à réaliser
  • Ajout de deux disques.
  • Ajout d'un rectangle.
  • Ajout d'une étoile.
  • Ajout d'un texte.

Gestion du clavier

Vous venez d'ajouter des éléments graphiques à votre jeu, vous allez maintenant, donner un peu de vie à votre jeu, en ajoutant de l'interaction grâce à la gestion du clavier.

Tous les outils de gestion de l'interaction de Phaser sont regroupés dans la propriété input de la scène. Nous nous focaliserons pour le moment sur la gestion du clavier.

Phaser propose plusieurs solutions pour gérer le clavier, vous utiliserez la méthode addKeys du greffon de gestion de clavier. Vous enregistrerez l'état des touches : gauche, droite et espace.

Dans un premier temps, vous vous contenterez d'enregistrer une fonction de rappel sur l'événement "down" de chaque touche :

  • la touche gauche provoquera une réduction de 10 de l'abscisse du rectangle,
  • la touche droite provoquera une augmentation de 10 de l'abscisse du rectangle,
  • la touche espace provoquera un flash de la caméra de la scène.

Pour provoquer le flash de la caméra, vous utiliserez la méthode flash de la caméra principale (main) de la scène.

Travail à réaliser
  • Utilisation de addKeys pour enregistrer l'état des touches : gauche, droite et espace.
  • Enregistrement de fonctions de rappel pour chacune de ces touches.

Ajout du moteur physique

Le déplacement du rectangle est saccadé, car il est réalisé sans étape. Plusieurs solutions pourraient être envisagées pour rendre le déplacement plus fluide, nous allons utiliser le moteur physique pour laisser Phaser réaliser un déplacement visuellement réaliste.

Phaser propose 2 moteurs physiques :

  • arcade est le moteur physique développé pour Phaser. Il a comme principal avantage d'être simple et efficace, cependant, le calcul des collisions n'est supporté que pour des boites alignées sur les axes (AABox) ou des cercles. Il est aussi possible, d'ajouter soi-même des collisions plus précises.
  • matter est un moteur physique complet, permettant une gestion beaucoup plus complète et précise. Vous pouvez définir des formes quelconques et spécifier des interactions complexes : chaînes, frottement, etc. Le moteur est plus lent et plus complexe à utiliser, mais c'est la solution si vous souhaitez utiliser des formes non-supportées par arcade

Vous allez ajouter aux options de configuration les propriétés permettant à Phaser d'utiliser arcade comme moteur physique par défaut.

Maintenant que le moteur physique est défini pour le jeu, vous allez pouvoir ajouter un « corps » physique au rectangle à l'aide de la méthode existing de l'usine à « corps » physique obtenue depuis le moteur physique de la scène.

L'objet retourné est un élément de jeu disposant d'une propriété body référençant le « corps » physique de l'élément de jeu. Lors de l'appui sur la touche gauche, vous affecterez sa vitesse horizontale à -500. Lors de l'appui sur la touche droite, vous l'affecterez à 500. Et lorsqu'elles sont lâchées, vous la réinitialiserez à 0.

Travail à réaliser
  • Définition du moteur physique par défaut.
  • Ajout d'un « corps » physique au rectangle.
  • Gestion de la vitesse du rectangle à l'aide des touches gauche et droite.

Réorganisation du code

Le mouvement est maintenant plus fluide, mais le comportement n'est pas trés intuitif lorsque vous appuyez sur les touches gauche et droite simultanément. Cependant, avant de résoudre ce problème, vous allez réorganiser votre base de code pour qu'il soit plus facile à gérer. En effet, la solution consistant à mettre tout votre code dans un même fichier montre vite ses limites, et on imagine facilement qu'il va être compliqué de travailler collaborativement sur un unique fichier.

Pour commencer simplement, vous allez externaliser la configuration du jeu dans un module JavaScript « src/config.js » qui exportera par défaut la configuration du jeu, que vous importerez par défaut dans « src/index.js » pour l'utiliser lors de l'instanciation du jeu. Cependant, afin de définir la scène hors de la configuration, vous extrairez la scène de la configuration et vous l'ajouterez à l'aide de la méthode add du gestionnaire de scènes du jeu.

Une fois votre projet de nouveau fonctionnel, vous allez créer un nouveau module JavaScript « src/object/Player.js » qui exposera par défaut une classe étendant la classe Phaser.GameObjects.Rectangle.

Son constructeur correspondra au prototype suivant : constructor(scene, width = 50, height = 20, color = 0xffffff) et

  • initialisera la classe parente en bas au centre de la scène,
  • ajoutera le rectangle comme élément de la scène,
  • ajoutera un « corps » physique au rectangle.
Pour obtenir les dimensions de la zone de jeu, vous pourrez utiliser les propriétés de l'élément canvas du jeu (scene.game.canvas).

Vous pourrez ensuite remplacer le rectangle par une instance de cette classe dans votre scène.

Vous intégrerez ensuite la gestion des touches gauche et droite dans la classe Player, au sein d'une méthode handleKeys(keys) qui :

  • recevra en paramètre keys un objet JavaScript contenant les propriétés left et right avec le code touche à associer.
  • invoquera la méthode addKeys pour enregistrer l'état des touches gauche et droite
  • associera pour chaque touche un écouteur pour les événements down et up. Ces écouteurs invoqueront la méthode privée #handleMove(), vous penserez donc à définir le context.
Vous créerez enfin la méthode privée #handleMove() qui modifiera la vitesse horizontale du rectangle en fonction de la touche appuyée :
  • -500 lorsque seule la touche gauche est pressée,
  • 500 lorsque seule la touche droite est pressée,
  • 0 lorsqu'aucune ou les deux sont pressées.

Pour finaliser la classe du joueur, vous activerez la propriété de gestion de collision du joueur avec le monde pour empêcher le joueur de sortir de la zone de jeu.

Sur le même principe que pour le joueur, vous allez externaliser la scène dans un module JavaScript « src/scene/Play.js ». Ce module exportera par défaut une classe étendant la classe Phaser.Scene, comportant une méthode create.

Vous devriez maintenant avoir retrouvé un projet fonctionnel, le déplacement du joueur devrait être plus intuitif et limité à la zone de jeu.

Travail à réaliser
  • Extraction de la configuration du jeu.
  • Extraction du code de gestion du joueur.
  • Gestion des limites de la zone de jeu.
  • Extraction du code de la scène.

Gestion des salves

Vous allez maintenant ajouter la possibilité pour le joueur de tirer des salves de lasers.

La création et la suppression d'objets, lorsqu'elle survient régulièrement au cours du jeu, n'est pas efficace en termes de gestion des ressources. Dans notre cas, le joueur va tirer de nombreux lasers qui vont devoir être supprimés en quittant l'écran. Pour résoudre ce problème, Phaser propose d'utiliser un groupe d'objets, qui peuvent être activés/désactivés afin de réutiliser les objets non-utilisés.

Vous commencerez par créer une nouvelle classe pour représenter un tir de laser. Cette classe étendra la classe Phaser.GameObjects.Ellipse et sera initialisée avec une dimension (10x20 par exemple), une couleur 0xff0000 et une origine par défaut. Vous lui ajouterez un « corps » physique et vous l'ajouterez à la scène.

Vous allez maintenant pouvoir utiliser cette classe dans une nouvelle classe de gestion d'un groupe de tirs de laser. Cette classe étendra Phaser.GameObjects.Group. Son constructeur prendra en paramètre la scene et la taille du groupe. L'instanciation du parent sera réalisée avec la scène et les options de configuration du groupe. Vous ferez en sorte que le groupe soit constitué d'instances de votre classe de tir de laser et qu'il comporte 5 éléments au maximum.

Vous pourrez utiliser une instance de votre classe à la construction de votre scene pour gérer les tirs de laser. Et lors de la gestion de la touche du clavier, vous pourrez utiliser la méthode getfirstdead du groupe pour obtenir le premier élément disponible, ou en créer un si possible. Si un élément est disponible, vous le positionnerez au niveau du joueur, vous initialiserez sa vitesse (à -500 par exemple), vous le rendrez visible et actif.

De manière similaire à la classe Player, vous allez extraire la gestion au clavier du tir pour l'intégrer à la classe de gestion du groupe de tirs.

Vous devriez constater qu'une fois vos 5 tirs réalisés, il n'est plus possible de tirer, en effet, les tirs ne sont jamais désactivés. Vous allez résoudre ce problème en ajoutant une méthode preUpdate à la classe représentant un tir de laser. Cette méthode est invoquée par Phaser avant chaque mise à jour d'une instance. Vous testerez la position de l'ellipse pour la cacher et la désactiver si elle est sortie de la zone de jeu.

Travail à réaliser
  • Création de la classe représentant un tir de laser.
  • Création de la classe de gestion d'un groupe de lasers.
  • Utilisation d'un groupe de laser dans la scène.
  • Extraction de la gestion du tir de laser.
  • Désactivation des lasers sortant de la zone de jeu.

Gestion des ennemis

Sur le même principe, vous allez créer des ennemis descendant progressivement la zone de jeu.

Vous commencerez par créer une classe pour les ennemis étendant la classe Phaser.GameObjects.Arc. Vous ferez en sorte que l'ennemi soit représenté par un cercle, et vous tracerez son périmètre avec une couleur visible.

Vous créerez une classe de gestion d'un groupe d'ennemis, cette classe recevra en paramètre la scène, la taille du groupe et la taille d'un ennemi. Cette classe devra créer un certain nombre d'ennemis régulièrement. JavaScript propose nativement plusieurs outils pour interagir avec le temps (setInterval par exemple), mais Phaser propose ses propres solutions qui sont intégrées avec son moteur, permettant l'arrêt ou la pause automatique en relation avec le jeu.

Vous ajouterez une méthode start à la classe de gestion d'un groupe d'ennemis qui enregistrera un nouvel événement auprès de l'horloge de la scene. Cet événement devra exécuter une fonction de rappel toutes les 2000ms de manière infinie.

La fonction de rappel devra créer de 2 à 6 ennemis. Chaque ennemi devra être positionné aléatoirement sur la largeur de la zone de jeu, sa largeur sera initialisée et sa couleur sera fixée aléatoirement. Dans un premier temps, vous pouvez les placer en haut de l'écran pour les voir apparaître.

Vous utiliserez la classe de gestion d'un groupe d'ennemis dans la scène avec une taille de 60 éléments et vous démarrerez l'apparition des ennemis après son invocation.

Les ennemis apparaissent en haut de la zone de jeu, mais ils ne descendent pas. Pour ne pas utiliser encore le moteur physique, vous allez faire descendre les ennemis par à-coup, en utilisant des « tweens » pour animer la position des ennemis.

Dans la fonction de rappel de création des ennemis, vous ajouterez un nouveau « tween » aux « tweens » de la scène. Les « tweens » sont des objets permettant d'animer les propriétés d'un objet. Dans notre cas, nous allons animer une propriété y de 0 à 1.25 * taille_d_un_ennemi sur 1 seconde avec une interpolation de type Quadratic.InOut. Malheureusement, un groupe n'a pas de position, il faut donc répercuter les évolutions de y aux éléments du groupe à l'aide de la méthode du groupe incY dans la fonction de rappel onUpdate de la définition du « tween ». Pour avoir le détail des paramètres de la fonction de rappel, vous devrez cliquer sur le lien vers le code de sa définition, les paramètres qui vous intéressent particulièrement sont current et previous.

Vous ferez en sorte que les ennemis soient désactivés en arrivant en bas afin qu'ils puissent être réutilisés.

Vous allez maintenant faire en sorte que vos tirs de laser détruisent les ennemis. Pour cela, vous commencerez par détecter la collision entre les lasers et les ennemis à l'aide de la méthode overlap de l'usine à physique de la scène. Pour organiser votre code, vous pourrez utiliser une méthode privée de la scène comme fonction de rappel, mais vous veillerez bien à transmettre le context de la fonction de rappel (callbackContext). Dans cette fonction de rappel, si le laser est actif, vous désactiverez le laser et l'ennemi transmis en paramètre. Le test est nécessaire, pour ne supprimer qu'un ennemi à la fois lorsqu'ils sont superposés. La fonction de rappel est toujours appelée pour chaque collision, mais seul le premier ennemi est désactivé en même temps que le laser.

Si vous êtes attentif, comme dans les tests de collisions, nous n'avons testé que les lasers, vous devriez constater que les lasers actifs entrent en collision avec les ennemis désactivés. Pour éviter les tests de collisions inutiles, lors de la désactivation des ennemis et des lasers, vous les déplacerez hors de la zone de jeu, au-dessus pour les ennemis et en-dessous pour les lasers.

Pour finaliser le mouvement des ennemis, vous allez les faire se déplacer de gauche à droite. Dans ce but, vous commencerez par ajouter le groupe de gestion des ennemis à la scène dans le constructeur (scene.add.existing), la scène tentera maintenant d'invoquer différents « hooks », et dans notre cas la méthode preUpdate.

Dans la méthode preUpdate, vous utiliserez la fonction sinus sur le temps écoulé depuis la précédente étape de rendu en seconde, pour obtenir la valeur à ajouter à l'abscisse de chaque ennemi.

Travail à réaliser
  • Création de la classe représentant un ennemi.
  • Création de la classe de gestion d'un groupe d'ennemis.
  • Utilisation d'un groupe d'ennemis dans la scène.
  • Gestion du déplacement des ennemis.
  • Désactivation des ennemis sortant de la zone de jeu.
  • Désactivation des ennemi/laser lors de la collision.
  • Complexification du mouvement des ennemis avec un déplacement latéral.

Ajout du score et d'une fin de partie

Vous ajouterez à votre scène deux nouvelles propriétés, l'une pour gérer le score et l'autre pour tenir le texte affichant le score. Vous placerez l'affichage du score dans le coin supérieur gauche. Vous prendrez soin de vous assurer que le texte est affiché au-dessus des ennemis et des lasers. Pour finir, vous ferez en sorte que le score augmente pour chaque ennemi détruit.

De manière similaire, vous ajouterez une propriété pour gérer le nombre de vies restantes et l'autre pour tenir le texte affichant ce nombre. Vous voulez décrémenter ce nombre lorsqu'un ennemi atteint le bas de la zone de jeu, mais contrairement au score, vous ne disposez pas de cette information dans la scène.

Plutôt que d'ajouter du code concernant le score dans la classe de gestion des ennemis, celle-ci va émettre un événement personnalisé "bottomReached" signalant qu'un ennemi a atteint le bas de la zone de jeu. Dans la scène, vous ajouterez un écouteur pour cet événement qui décrémentera le nombre de vies restantes.

Dans la classe représentant un ennemi, vous émettrez un événement "bottomReached" lorsque l'ennemi parvient en bas de la zone de jeu.

En l'état, le jeu se poursuit, même avec un nombre de vies négatif. Vous ajouterez un test lors de la modification du nombre de vies pour tester si ce nombre est positif. Sinon, vous bornerez la vie à 0, vous stopperez le jeu et vous afficherez un message indiquant la fin du jeu.

Travail à réaliser
  • Gestion d'un score.
  • Gestion du nombre de vies restantes.
  • Gestion de la fin de partie.

Ajout des bonus

Pour finaliser le jeu, vous allez ajouter des bonus, représenté par des étoiles.

Comme précédemment, vous ajouterez une classe représentant un bonus et une classe de gestion d'un groupe de bonus.

Dans la classe gérant un groupe de bonus, vous ajouterez une méthode privée #setTimeout dont le rôle est de créer un nouveau bonus dans 1 à 4 secondes. Le bonus sera initialisé aléatoirement au-dessus de la zone de jeu, avec une vitesse verticale (500 par exemple). Vous ajouterez aussi une méthode start qui invoquera la méthode #setTimeout pour démarrer l'émission des bonus.

Vous ferez en sorte de désactivé le bonus lorsqu'il sortira de la zone de jeu. Lors de la désactivation vous émettrez un message personnalisé. Dans la méthode start de la classe des groupes de bonus, vous ajouterez un gestionnaire d'événements qui invoquera la méthode #setTimeout lorsqu'un bonus est désactivé.

Lorsque vous utiliserez et démarrerez le groupe de bonus dans la scène, vous devriez maintenant voir des bonus apparaitre régulièrement et traverser la zone de jeu de bas en haut.

Dans la scene, vous mettrez en place la détection de collision entre le joueur et les bonus. Pour réagir à la collision, dans la méthode preUpdate d'un bonus, vous allez émettre un événement personalisé lorsque le bonus rentre en collision, ainsi que désactivé le bonus. Vous pouvez maintenant améliorer le score (de 10 par exemple), lors de l'interseption du bonus par le joueur.

Travail à réaliser
  • Création de la classe représentant un bonus.
  • Création de la classe de gestion d'un groupe de bonus.
  • Utilisation d'un groupe de bonus dans la scène.
  • Désactivation des bonus sortant de la zone de jeu.
  • Gestion de la collision joueur/bonus.
A. Jonquet DUT-INFO/REIMS