CI9 : Alien vs ananas
L'objectif de cet exercice est de vous faire manipuler des classes anonymes et de vous introduire la bibliothèque JavaFX qui permet de créer facilement des applications graphiques.
Durée des exercices obligatoires : 2h30mn en présentiel- Exercice 1 : obligatoire (facile, ∼10mn)
- Exercice 2 : obligatoire (facile, ∼45mn)
- Exercice 3 : obligatoire (facile, ∼30mn)
- Exercice 4 : obligatoire (moyen, ∼30mn)
- Exercice 5 : obligatoire (moyen, ∼45mn)
Dans cet exercice, nous concevons un jeu vidéo simple nommé Alien vs Pinapples : une armée d'ananas mutants attaque votre galaxie et vous jouez un alien en charge de sauver l'univers, yipa !
Préparation (∼10mn – facile – obligatoire)
Avant de pouvoir programmer votre jeu, vous devez préparer votre environnement de travail afin d'utiliser la bibliothèque graphique JavaFX. Cette activité devrait vous prendre environ 10 minutes si tout se passe bien. Dans la suite de l'exercice, on considère que vous utilisez eclipse. Si vous utilisez un autre environnement de développement, il faut vous référer à la documentation de votre environnement.
Pour commencer, sachez que cette procédure est susceptible de ne pas fonctionner sur votre machine si vous avez une configuration originale. À cette question, on considère que vous pouvez être sous Linux ou MacOS, mais pas sous Windows.
- Pour commencer, créez un projet Java alien dans eclipse. Dans votre projet alien, créez une classe Alien dans le package tsp.alien. Pour le moment, laissez le contenu de la classe Alien vide. Attention, le fait que la classe soit dans le package tsp.alien est important.
- Maintenant, il faut trouver la version Java que vous utilisez.
Si vous êtes en salle TP, vous devez suivre la procédure pour la version Java 17/Linux/Intel.
Si vous n'êtes pas en salle TP, cliquez sur Properties dans le menu Project.
Cliquez Libraries.
Dans Java build path, sélectionnez Libraries.
Vous devriez voir la version de Java que vous utilisez sur la ligne JRE System Library.
Il est aussi possible que vous utilisiez la version de Java renvoyée par la commande java -version (à lancer dans un terminal).
En fonction de votre version de Java, de votre système et de votre processeur :
- Vous avez un Java 17, un Linux et un Intel, téléchargez la version de JavaFX qui se trouve ici
- Vous avez une version de Java inférieure à 11, vous devez passer à l'ancienne procédure qui se trouve à la fin de l'exercice.
- Sinon, télécharger le SDK (et non le jmods) de JavaFX qui convient à Java 19 et à
votre système d'exploitation à partir de ce lien.
- Si vous avez un PC ou un Mac Intel, il faut utiliser la version x64.
- Si vous avez un Mac M1, il est conseillé de commencer par la version x64 (probablement pour les versions de MacOS < 13).
- Pour les versions les plus à jour de MacOS M1, il faut utiliser la version aarch64 (probablement pour les versions de MacOS >= 13).
- Extrayez l'archive dans un répertoire ou double cliquant sur l'archive ou en utilisant la commande unzip dans un terminal, par exemple avec :
$ unzip ~/Téléchargement/openjfx-17.0.1_linux-x64_bin-sdk.zip
- À partir de cette étape, ne déplacez plus les fichiers extraits ailleurs dans votre système de fichier.
- Dans Eclipse, allez dans le menu Project puis Properties. Dans Java build path, sélectionnez Libraries et cliquez sur Modulepath, ce qui devrait dégriser le bouton Add External Jars. Cliquez sur ce bouton et ajoutez tous les fichiers jar contenus dans le sous-répertoire lib de JavaFX (le sous-répertoire lib du répertoire dans lequel vous avez extrait l'archive).
- Vérifiez qu'il existe bien un fichier nommé module-info.java à la racine des sources de votre projet (c'est-à-dire dans le répertoire src de votre projet). Si ce n'est pas le cas, c'est que vous utilisez une vieille version de eclipse. Vous devez donc créer manuellement ce fichier.
- Copiez-collez le contenu du fichier suivant dans votre module-info.java :
- Enfin, copiez-collez le code suivant dans vitre fichier Alien.java :
- Si vous arrivez à exécuter ce programme et que vous voyez une fenêtre s'afficher, félicitation, vous avez réussi à configurer JavaFX ! Pensez à fermer la fenêtre JavaFX avant de passer à la suite.
Si à cette étape, vous avez encore des erreurs ou ne voyez pas de fenêtre s'afficher, vous pouvez essayer les solutions proposées ci-après.
Sur un MacOS, tout à l'air de fonctionner mais aucune fenêtre ne s'affiche
Pour les utilisateurs de MacOS, vous allez peut-être avoir le problème suivant. L'application se lance correctement, mais aucune fenêtre n'est affichée. Pour corriger le problème, il faut aller dans Run As->Run configurations, puis dans l'onglet (x)= Arguments, et désélectionner Use the -FstartOnFirstThread argument when launching with SWT.
Sur un Linux, votre IDE se demande où sont les bibliothèques natives (installation avec apt-get)
Si vous avez installé JavaFX avec l'outil apt (ou apt-get) de ubuntu ou debian, il faut parfois explicitement indiquer à la machine virtuelle Java le chemin où se trouvent les bibliothèques natives JavaFX. Pour cela, il faut d'abord identifier ce chemin avec :
Cette commande devrait vous donner un chemin comme /usr/lib/x86_64-linux-gnu/jni/. Dans Eclipse, cliquez dans le menu Run->Run configurations puis selectionner l'onglet (x)= Arguments. Dans VM Arguments, ajoutez le paramètre -Djava.library.path=/usr/lib/x86_64-linux-gnu/jni/ dans lequel vous remplacerez le /usr/lib/x86_64-linux-gnu/jni/ par le chemin identifié avec la commande dpkg -L libopenjfx-jni
Tout à l'air correct, mais Eclipse se plaint toujours de ne pas trouver le package javafx
Sur de vielles versions d'Eclipse, Eclipse peut considérer qu'il vaut mieux ne pas utiliser JavaFX car cette bibliothèque n'a été que récemment intégrée à la bibliothèque standard Java. Pour corriger ce problème, il faut cliquer dans le menu Project puis dans le sous-menu Properties.
Dans la fenêtre ouverte par Eclipse, il faut ensuite sélectionner Java Build Path dans la partie gauche de la fenêtre. Dans la fenêtre qui apparaît, il faut ensuite sélectionner l'onglet Libraries (partie droite de la fenêtre). Vous devriez pouvoir dérouler un menu JRE System Library ce qui devrait vous donner une fenêtre similaire à la fenêtre de gauche présentée dans la figure ci-dessous.
En Cliquant sur la ligne Access rule, vous devrez voir une fenêtre similaire à celle du milieu sur la figure ci-dessous, mais sans la règle javafx. Cliquez sur add, vous devriez voir une fenêtre similaire à celle de droite. Ajoutez une règle Accessible (par défaut la règle est à Forbidden). Autorisez ensuite tous les packages de JavaFX en mettant javafx/** dans Rule Pattern. Vous devriez obtenir l'affichage représenté sur la figure de droite. Après avoir cliqué sur OK, vous devriez voir votre règle autorisant JavaFX, comme dans la fenêtre représentée sur la figure du milieu.
Procédure pour des versions de Java strictement inférieur à 11 (attention, cette procédure n'a plus été testée depuis des années)
Avec de nombreuses distributions Linux (typiquement Debian ou Ubuntu), les versions Java inférieures strictement à 11 ne fournissent pas la bibliothèque JavaFX. Pour cette raison, vous allez devoir installer une version complète de Java 8 qui contient cette bibliothèque. Pour cela, il vous suffit de lancer la commande suivante dans un terminal après avoir remplacé [login] par votre nom de connexion sur les machines de Telecom SudParis :
Suite à cette étape, vous devriez avoir un jdk 8 dans ce répertoire que nous noterons JVM_PATH dans la suite : ~/jdk1.8.0_13.
Dans eclipse, dans le menu File->New, sélectionnez Java Project. Avant de saisir le nom du projet, il faut cliquer sur le lien Configure JREs qui se trouve vers le milieu de la fenêtre.
Dans la fenêtre qui vient de s'ouvrir, cliquez sur le bouton Add. Sélectionnez Standard JVM, puis cliquez sur Next. Dans la zone de saisie JRE Home, copiez-collez le chemin JVM_PATH correspondant au répertoire dans lequel se trouve la version Java fournissant JavaFX (voir le début de la question). En salle de TP, il vous suffit de copier-coller le texte suivant /mci/inf/thomas_g/jfx/jdk1.8.0_131. Vous devriez voir tout un tas de lignes s'afficher dans la zone JRE System libraries. Cliquez alors sur Finish.
Dans la fenêtre dans laquelle vous venez de revenir indiquant les Installed JREs, sélectionnez jdk1.8.0_131 et cliquez sur ok.Dans la fenêtre de création de projets dans laquelle vous devriez être revenu, sélectionner Use default JRE (currently jdk1.8.0_131).
Enfin, donnez le nom alien à votre projet (zone de saisie Project Name) avant de cliquer sur Finish.
L'univers (∼45mn – facile – obligatoire)
Cette question a pour but de vous expliquer le code que vous copié/collé dans la classe Alien lors de la procédure d'installation. Elle ne nécessite pas de répondre à une question spécifique. Il vous suffit de lire le texte qui suit.
Pour initialiser JavaFX, il faut créer une classe qui hérite de javafx.application.Application. Dans notre exercice, nous créons donc la classe nommée tsp.alien.Alien héritant de javafx.application.Application.
La classe Application offre une méthode publique statique nommée void launch(String... args). Cette méthode permet de démarrer une application avec une unique fenêtre. Elle alloue une instance de la classe qui a généré l'appel (dans notre cas, une instance de tsp.alien.Alien), prépare le moteur graphique, crée une fenêtre (une instance de Stage) et finalement délègue le remplissage de la fenêtre à la méthode d'instance void start(Stage primaryStage). C'est en redefinissant cette méthode dans tsp.alien.Alien que nous pouvons associer une scène à la fenêtre et des éléments à la scène avant d'afficher la fenêtre avec la méthode void Stage.show().
Dans le programme que vous avez copié/collé, la méthode statique main de Alien appelle la méthode statique launch héritée de Application. Cette dernière crée donc une fenêtre, puis une instance de Alien afin de déléguer le remplissage de la fenêtre à la méthode d'instance start de la classe Alien. Comme cette méthode start ne fait rien, la fenêtre est encore vide.
- créer un groupe avec le constructeur par défaut de la classe Group,
- créer une scène associée à ce groupe avec le constructeur de la classe Scene
- et enfin associer la scène à la fenêtre avec la méthode setScene de la classe Stage.
- utiliser Group.getChildren pour récupérer la liste des enfants du groupe,
- et List.add pour ajouter le canevas à cette liste.
- Utilisez le constructeur javafx.scene.image.Image(String, double, double, boolean, boolean) pour charger l'image. Le premier argument est le chemin vers l'image, c'est-à-dire space.jpg dans notre cas. Les deux arguments suivants sont la taille que nous souhaitons donner à notre image, c'est à dire la taille de la fenêtre dans notre cas. Les deux argument suivants doivent être positionnés à false : il ne faut pas préserver les ratios de l'image d'origine et il n'est pas nécessaire d'utiliser un algorithme de qualité pour mettre l'image à l'échelle.
- Utilisez javafx.scene.canvas.GraphicsContext.drawImage pour afficher l'image aux coordonnées (0, 0) dans le canevas.
Les lutins (∼30mn – facile – obligatoire)
- le nom de l'image associée au lutin (utilisez javafx.scene.image.Image(String, double, double, boolean, boolean) pour charger l'image dans le constructeur de Sprite),
- la largeur et la hauteur du lutin.
Les lutins volants (– 30mn – moyen – obligatoire)
- s'assure que l'ananas est toujours entièrement visible dans la fenêtre (attention, pensez que votre ananas à une largeur et une hauteur),
- s'occupe de faire rebondir l'ananas lorsqu'il atteint un des 4 bords de la fenêtre.
L'alien volant (∼45mn – moyen – obligatoire)
bonus, nécessite de lire le sous-cours sur les classes anonymes
- Si score est un champ de classe anonyme, on peut bien sûr y accéder en lecture/écriture à partir de la méthode de la classe anonyme.
- Si score est une variable de la méthode Alien.start, comme le classe anonyme est une classe interne de méthode, Java effectue une copie de score dans l'instance de la classe anonyme lorsque l'instance est créée. De façon à éviter que les scores de la méthode et de l'instance de la classe anonyme divergent, Java interdit tout accès en écriture au champ score à partir de la création de l'instance de la classe anonyme. Comme score est modifié dans la méthode handle de la classe anonyme (c'est-à-dire qu'on y accède en écriture dans la méthode handle), on ne peut pas définir score comme variable de la méthode start().
- Si score est un champ de la classe Alien, la classe anonyme y accède via son champ Alien.this, et ce champ peut bien être accédé en lecture/écriture.