Cet article est le second article d’une série consacrée au développement Java. Ainsi, si vous ne l’avez pas déjà fait, nous vous conseillons vivement de lire attentivement le premier, sans quoi vous risqueriez d’avoir du mal à suivre celui-ci.
Nous allons commencer par vous présenter un aspect incontournable du développement Java, à savoir la gestion des dépendances.
- Gestion des dépendances
- Gestion de version
- Le traditionnel “Hello World”
- Votre première commande en Java
- Écoutons un événement
- La programmation en Java
- Améliorer la structure de notre code Java
- Trouver des solutions à vos problèmes
- Lire dans la console ou dans les logs une stacktrace d’exception
- Mieux comprendre son propre code en suivant l'exécution
- Un petit exercice
Gestion des dépendances
Si vous avez suivi les tutoriels transmis précédemment, vous n’aurez pas d’action à réaliser lors de la lecture de ce chapitre. Ils incluent déjà les dépendances nécessaires pour créer votre premier plugin. Cependant, nous avons choisi de vous présenter cette notion car il nous semble essentiel de comprendre ce concept avant d’aller plus loin.
En informatique, nous évitons de réinventer la roue. Vouloir tout faire soi-même est une erreur de débutant commune, c’est évidemment un bon moyen d’apprendre lorsque l’on débute. Mais pour réaliser un logiciel ou un plugin de qualité, il est plus pertinent d’inclure du code source réalisé par une communauté de développeurs compétents. Sinon on risquerait de passer des semaines ou des mois à refaire la même chose pour, au final, obtenir un résultat de moins bonne qualité.
Il est donc très courant, au sein d’un projet, d’inclure des dépendances, c’est-à-dire du code source assemblé dans un ensemble, une sorte de paquet, que l’on nomme une librairie (ou une API). Notre propre code source va ensuite se reposer sur ces dépendances. Faire appel à leur code source pour répondre à certains de nos besoins.
Bien sûr, comme notre propre code source, le code source de ces dépendances peut évoluer au rythme des développements réalisés par la communauté. Il peut devenir complexe de gérer toutes ces versions de dépendances externes. C’est pour cette raison que l’on utilise des outils comme Maven et Gradle. Ce sont des logiciels qui gèrent, entre autres, les dépendances et leurs versions. Ainsi, on décrit ce que l’on souhaite inclure dans un fichier (pom.xml si l’on utilise Maven ou gradle.build si l’on utilise plutôt Gradle). On définit aussi la version que l’on souhaite obtenir. Ainsi, lorsque notre code sera compilé, Maven ou Gradle y intégrera automatiquement les dépendances définies.
Plus précisément, on peut inclure ou bien utiliser une dépendance. Comme expliqué précédemment, une dépendance que l’on inclut se retrouvera dans le fichier plugin.jar de notre code compilé. Par contre, si on se contente d’utiliser une dépendance, la dépendance n’est pas intégrée à l’intérieur du plugin.jar.
Vous percevez peut-être un problème en lisant ces lignes. En effet, si on utilise du code source provenant d’une dépendance sans pour autant l’intégrer dans notre plugin.jar, comment est-il possible que notre plugin puisse fonctionner ? Comment peut-il avoir accès à ce code source auquel il n’aurait pas accès ? Et bien cela ne fonctionnera qu’à condition que notre plugin soit exécuté dans un contexte où il peut y avoir accès. Par exemple, un plugin Spigot étant forcément exécuté par l’intermédiaire d’un serveur Spigot (ou Paper), il aura nécessairement accès au code de l’API Spigot. Ainsi il ne servirait à rien d’inclure le code de l’API Spigot dans nos plugins. On se contente alors d’utiliser cette dépendance et cela suffit.
Cette différence entre inclusion et utilisation est ce que l’on nomme le scope (la portée). Il y a en réalité bien plus de possibilités que l’inclusion ou l’utilisation d’une dépendance. Pour en savoir plus, nous vous invitons à regarder cette explication si vous utilisez Maven ou celle-ci si vous utilisez Gradle.
Dans certains cas, une autre notion peut être importante pour véritablement maîtriser l’usage de Maven / Gradle. C’est le shading (on pourrait peut-être traduire cela par le terme ombrage en français). Il est rare d’avoir besoin de cette fonctionnalité lorsque l’on débute, mais étant donné que vous risquez de croiser, dans vos lectures, des références à ce concept, vous pouvez lire ce post pour le comprendre.
Enfin, Maven et Gradle permettent de faire bien d’autres choses.
Par exemple, déplacer le plugin compilé dans le dossier /plugins/ du serveur à chaque fois que l’on souhaite tester peut s’avérer laborieux.
Or, lorsque l’on suit un cursus en informatique, il est courant d’entendre, de la bouche d’un professeur, l’adage suivant : “Un bon informaticien est un informaticien flemmard”.
Le professeur explique ensuite, au grand dam des étudiants, alors emplis d’espoir, que cela ne veut pas dire qu’un informaticien travaille peu. Cela signifie au contraire que, dès lors qu’un informaticien compétent réalise une action laborieuse à de nombreuses reprises, il va travailler plus pour réussir à l’automatiser.
Grâce à Maven ou Gradle (ou ant), il est donc possible d’automatiser cette copie du plugin.jar compilé vers votre serveur de test. Malheureusement, vous risquez de trouver cela un peu complexe pour l’instant. Si vous souhaitez tout de même savoir comment vous y prendre, vous pouvez consulter cette explication si vous utilisez Gradle, ou celle-ci si vous utilisez Maven. Vous pouvez aussi regarder cet exemple (où les commandes de copies sont écrites dans un fichier à part (basé sur ant) de manière à permettre à chaque développeur d’une équipe d’avoir ses serveurs de test à des emplacements différents au sein de son ordinateur personnel).
Si l’on en revient à l’usage de ces outils dans le cadre de Minecraft, plus d’informations sur les dépendances nécessaires et la manière de les inclure sont disponibles sur les sites de chaque outils (fabric, paper, …) et pour ce qui est de Spigot, tout est expliqué ici :
- Pour Maven : https://www.spigotmc.org/wiki/spigot-maven/
- Et pour Gradle : https://www.spigotmc.org/wiki/spigot-gradle/
Nous allons maintenant poursuivre sur une deuxième notion théorique. Accrochez-vous, c’est la dernière avant que nous commencions à coder en Java.
Gestion de version
Pendant de longues années les développeurs se sont confrontés à différents problèmes et ont mis en place différentes stratégies pour les résoudre. Voici quelques-uns de ces problèmes :
- Mon disque dur fait un bruit étrange, je n’arrive plus à y accéder. J’ai bien peur d’avoir perdu toutes mes données, et plusieurs heures de travail que j’avais réalisé sur mon code source. Je vais, très calmement, lancer mon PC par la fenêtre et aller élever des chèvres sur le plateau du Larzac.
- Je travaille sur un projet informatique avec Jean-Claude, je venais de publier le résultat de 8 heures de travail. C’est-à-dire une modification conséquente du fichier “5000-lignes-de-code-super-complique.java”. Dans la minute qui a suivi, Jean-Claude a publié sa modification sur ce même fichier. C’est-à-dire 2 lignes de code qu’un enfant de 5 ans pourrait comprendre. Ça a écrasé tout mon travail. Je n’ai plus qu’à recommencer (et à réfléchir à la manière la plus discrète de mettre en application mes cours de Karaté sur ce cheeeeer Jean-Claude pendant ce temps).
- Lionel, tu te souviens du bout de code de 2000 lignes que l’on a supprimé parce que nous pensions qu’il ne servait plus à rien ? En fait, il était utile. Tu n’aurais pas une sauvegarde ? Parce que moi non…
Bref, comme vous l’aurez compris, coder peut s’avérer périlleux, surtout si l’on n’utilise pas des outils appropriés.
Et pour éviter ces problématiques gênantes, des systèmes de gestion de version ont été développés. Celui qui, de par ses fonctionnalités et son efficacité, est devenu la référence en la matière se nomme GIT [1][2]. Que vous travaillez seul ou en équipe, nous vous conseillons vivement de l’installer (ici) et de l’utiliser dans vos projets. Pour avoir une visualisation plus agréable du terminal à travers lequel vous pourrez utiliser git, vous pouvez également installer le logiciel cmder.
Par contre, Git est un peu complexe à prendre en main. Nous vous invitons donc à suivre ce cours qui explique avec précision les bases de son usage. Et si vous préférez une vidéo vous pouvez regarder celle-ci.
Suite à la lecture ou le visionnage d’un des deux liens précédents, nous vous proposons de réaliser votre premier commit. C’est-à-dire de sauvegarder l’état de votre code (celui du plugin que vous avez créé au cours du premier tutoriel) .
Nous pourrions faire cela à travers votre IDE mais il est plus pertinent d’apprendre à utiliser le terminal pour mieux comprendre chaque étape. Ainsi nous vous proposons de réaliser les étapes suivantes :
- Lancer gitbash (ou cmder) et vous déplacer dans le dossier qui contient votre code source :
cd /c/Users/MonNomDUtilisateurWindows/IdeaProjects/NomDuProjet
- Créer un dépôt local pour votre projet :
git init NomDuProjet
- Créer un compte GitHub ou GitLab (nous préférons GitLab mais qu’importe).
- Élaborer un projet sur le site de GitHub ou GitLab et donnez-lui un nom tel que “first-spigot-plugin”.
- Générer votre clé privée pour pouvoir transmettre votre code source en toute sécurité/confidentialité (la documentation de GitLab ou de GitHub expliquent comment faire).
- Transmettre le certificat public correspondant à GitLab ou GitHub.
- Définir le nom avec lequel vous souhaitez signer vos commit :
git config --global user.name MonNomPrenomOuPseudo
git config --global user.email [email protected]
- Relier votre dépôt local (ce dossier sur votre ordinateur) à votre dépôt distant (stocké sur les serveurs de GitLab ou de GitHub) :
git remote add origin [email protected]:Pseudo/NomDuDépôt.git
Vous trouverez l’adresse exacte sur la page d’accueil de votre repository GitLab ou GitHub.
- Créer un fichier “.gitignore” de manière à éviter d’envoyer des fichiers qu’on ne souhaite pas partager. Cela concerne typiquement les fichiers de configuration cachés générés par Windows/Linux/Mac et ceux générés automatiquement par votre IDE ou d’autres logiciels. Pour cela vous pouvez utiliser ce générateur.
- Créer un premier commit avec votre plugin vide (celui que vous avez créé au cours du premier tutoriel) et envoyer le code sur GitHub/GitLab :
#Pour vérifier quels fichiers doivent être ajouter
git status
#Pour ajouter tous les fichiers d’un coup
git add -A
#Si l’on ne souhaite être plus prudent on peut ajouter les fichiers un par un:
git add dossier/NomDuFichier.java
#Maintenant que tous les fichiers ont été marqués comme devant être commité, nous allons sauvegarder cet état du code, c’est à dire faire un commit et inscrire un commentaire pour le décrire:
git commit -m “first commit”
#La sauvegarde de cette version du code a été faite sur votre ordinateur, nous allons maintenant la propager sur le dépôt GitHub/GitLab.
git push --set-upstream origin master
Grâce à ce que nous venons de faire, ce premier état de votre code est désormais sauvegardé à la fois sur votre ordinateur et en ligne. Ainsi, vous ne risquez plus de perdre votre avancement. Qui plus est, si un jour vous souhaitez créer un autre plugin ou si vous souhaitez revenir à cet état (à ce commit) pour faire autre chose, vous pourrez le faire facilement.
Le traditionnel “Hello World”
Nous allons enfin commencer à coder. Comme la majorité des informaticiens nous allons commencer par le fameux “Hello World”.
Il s’agit d’ajouter, au démarrage du plugin, l’affichage dans la console du serveur d’un petit texte en inscrivant cette ligne au sein de la méthode onEnable :
// Ce qui est précédé par deux slash est un commentaire, cette ligne ne sera donc pas interprétée lors de l’exécution du code de notre plugin.
/* Ceci est également un commentaire
Par contre il est multi-lignes */
/* Nous importons tout d’abord le fichier JavaPlugin qui est présent dans l’API de bukkit.
Il nous sera utile. */import org.bukkit.plugin.java.JavaPlugin;
/* Nous créons ensuite une classe que l’on nomme SkyTuto, le même nom que celui du fichier dans
lequel nous nous trouvons.
Une classe est une structure logique un peu particulière, mais nous vous fournirons, sur ce sujet, des explications plus claires et détaillées par la suite.
L’instruction hérite de certaines caractéristiques d’une autre classe nommée JavaPlugin.
C’est grâce à cet héritage que certaines parties du code inscrit dans ce fichier pourront
être appelées au démarrage et à l’arrêt de notre plugin. */
public final class SkyTuto extends JavaPlugin {
/* L’inscription “@Override” ci-dessous sert à dire que la méthode suivante, “onEnable”,
n’est pas simplement présente dans notre classe SkyTuto. Elle est également présente
dans JavaPlugin.
Nous allons donc la surcharger avec le fonctionnement de notre choix. */
@Override
/* Nous créons une méthode, c’est-à-dire un bloc de code que l’on peut ensuite exécuter
tout à la fois.
C’est également ce que l’on nomme une fonction mais, étant donné que cette fonction
se trouve au sein d’une classe, il s’agit bien, ici, d’une méthode.
Nous lui donnons le nom “onEnable”. Nous précisons également qu’elle est publique,
c’est-à-dire que l’on donne ainsi le droit à n’importe quel autre fichier de notre
programme de l’utiliser.
C’est cette méthode qui sera appelée au démarrage de notre plugin. Ainsi, les lignes de
code qu’elle contient seront exécutées à chaque fois que, lorsque le serveur démarre,
notre plugin est initialisé.
Le mot clé void et l’absence de texte entre les parenthèses permet de préciser qu’elle
ne renvoie aucune information et n’en récupère pas non plus. */
public void onEnable() {
/* Nous accédons à la sortie standard (c’est-à-dire un moyen d’écrire dans la console),
et nous lui demandons d’écrire une ligne grâce à la méthode “println”.
Nous donnons à cette méthode “println” un paramètre, c’est à dire que nous lui
envoyons une information (inscrite entre les parenthèses) et cette information est
le texte à afficher : Hello World ! */
System.out.println("Hello World !");
//Nous fermons l'accolade pour signifier que le code de cette méthode se termine ici.
}
//Nous fermons l'accolade pour signifier que le code de cette classe se termine ici.
}
Compilez (autrement dit, réalisez un build, comme expliqué dans les tutoriels du premier article), déplacez votre plugin dans le dossier du serveur (en remplaçant le fichier précédent) et vérifiez qu’au démarrage du serveur, le message “Hello World” s’affiche bien dans la console.
Bien que le code source correspondant à cet exemple vous soit probablement inutile, si besoin, il se trouve ici. En effet, comme pour chaque exercice, si vous souhaitez récupérer la “correction”, vous pourrez le faire grâce à “git clone”. Référez-vous au chapitre précédent (Gestion de version) pour cela.
À ce stade, vous pouvez commiter et pusher, au sein d’une branche, cet état de votre code source. Cela permettra de le conserver dans votre dépôt GitLab/GitHub. Et si dans 10 ans vous êtes devenus un(e) développeur(euse) très expérimenté(e), vous retrouverez peut-être ce Hello World avec nostalgie !
Voilà, vous avez créé votre premier plugin ! Même si, au vu de son unique fonctionnalité, cela ne semble pas être grand chose, cela constitue véritablement un jalon dans votre progression !
Votre première commande en Java
Nous allons désormais créer une commande en Java qui affiche un message prédéfini au joueur qui l’exécute en jeu.
Il faut pour cela créer un fichier, une nouvelle “classe” (vous découvrirez plus tard la signification de ce terme).
Pour l’instant, nous allons la nommer “PremiereCommande”. En Java la norme consiste à mettre une majuscule au début de chaque mot dans les noms de fichiers et dans les noms de classe. On enlève les accents car ils risqueraient d’être mal interprétés. Si vous voulez en savoir plus sur comment bien structurer vos fichiers au sein d’un programme, vous pourrez en savoir plus dans le chapitre correspondant.
// Comme auparavant, nous importons plusieurs classes qui nous seront utiles par la suite.import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
/** Nous créons la classe qui représente notre première commande.
* Cette classe (nous expliquerons plus tard la signification de ce terme),
* hérite du fonctionnement d’un code générique, celui du fichier CommandExecutor
* présent dans l’API de Bukkit.
* Hériter de CommandExecutor permet de faire en sorte que cette classe soit apte
* à traiter des commandes envoyées par les joueurs, par la console ou même par des
* blocs de commande.
*/
public class PremiereCommande implements CommandExecutor {
/* Cette méthode, basée sur l’interface (nous expliquerons ce terme plus tard)
CommandExecutor, sera appelé lors de l’exécution de notre commande.
Elle prend 4 paramètres:
- sender: De type CommandSender, il représente la source de la commande.
C’est à dire la console, le joueur ou le command block qui a envoyé la
commande.
- command: De type Command, il représente la commande qui a été envoyée.
- label: De type chaîne de caractère, il représente l’alias qui a été utilisé pour
l’envoi de cette commande.
- args: de type tableau contenant des chaînes de caractères, il contient chaque
argument qui a été ajouté à la commande.
*/
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
/* On vérifie que c'est un joueur qui a utilisé la commande et non la console
du serveur ou un command bloc. */
if (sender instanceof Player) {
/* Si la commande qui a été exécutée est bien celle à laquelle nous souhaitons
réagir, le code situé entre les accolades sera exécuté. En vérité cette
vérification est inutile. Elle n’est là que pour vous montrer que le nom de
la commande envoyée par le joueur aura bien, pour valeur “premierecommande”
(en faisant abstraction de la casse). */
if (command.getName().equalsIgnoreCase("premierecommande")) {
// envoyons un message au joueur: “PLIF PLOUF PLAF”.
sender.sendMessage("PLIF PLOUF PLAF");
/* Retourner true (vrai) permet de signifier que la commande a fonctionné.
Cela met également fin à l’exécution du code de cette méthode */
return true;
}
}
/* Ici, l’une des deux conditions n’a pas été vérifiée.
Ainsi nous retournons faux pour signifier que la commande n’a pas fonctionné. */
return false;
}
}
Nous avons terminé de créer la classe qui sera capable de traiter notre commande, mais pour l’instant, elle n’est pas référencée. Autrement dit, Spigot ne sait pas encore qu’il doit utiliser cette classe lorsque les joueurs inscrivent “/premierecommande” dans le chat du jeu.
Pour référencer cette commande deux choses sont nécessaires. La première c’est d’inscrire, au sein de la classe principale de notre plugin (et en remplaçant potentiellement l’affichage du Hello World), une nouvelle ligne de code. Cette ligne permet de dire à Bukkit que, lorsque la commande “/premierecommande” est inscrite dans le chat, la classe PremiereCommande est celle qui devra être utilisée pour la traiter.
import org.bukkit.Bukkit;import org.bukkit.plugin.java.JavaPlugin;
public final class SkyTuto extends JavaPlugin{
@Override
public void onEnable() {
/* On informe Bukkit que la commande “/premierecommande” sera traitée par une
instance de la classe PremiereCommande.
Nous reviendrons plus tard sur ce que signifie le mot instance
(et bien sûr le mot classe). */
Bukkit.getPluginCommand("premierecommande").setExecutor(new PremiereCommande());
}
}
La seconde chose à réaliser est la modification du plugin.yml. Ce fichier sert à déclarer tout un tas de choses sur notre plugin auprès de l’API Bukkit (et, par extensions, à l’API de Spigot et/ou à celle de Paper).
Ici nous devons déclarer, au minimum, le nom de notre commande, ainsi que sa description.
name: SkyTutoversion: 1.0
main: fr.skytale.skytuto.SkyTuto
api-version: 1.16
author: Skytale
commands:
premierecommande:
description: Description de notre première commande.
Le fichier plugin.yml permet de définir énormément de choses comme les permissions et cela est largement documenté par Bukkit ou Spigot. Si vous voulez en savoir plus, veuillez vous référer à ce lien.
Au besoin, vous pouvez cloner l’ensemble du code source correspondant à ce que nous venons de faire.
Testez tout cela en compilant votre code (build) puis en déployant votre plugin.jar dans le dossier /plugins/ du serveur. Lancez le serveur avant de vous connecter dessus avec votre client Minecraft et vérifiez que tout fonctionne en inscrivant “/premierecommande” dans le chat.
Vous pouvez là encore sauvegarder votre code grâce à GIT.
Écoutons un événement
Nous allons maintenant faire en sorte que lorsqu’un joueur saute, des explosions se déclenchent sous ses pieds et le projettent en l’air à la manière d’une fusée.
Nous vous conseillons vivement de créer un nouveau monde de manière à éviter de détruire vos constructions lorsque vous testerez cette fonctionnalité. Si vous avez envie de vous amuser avec, sachez que le désert est un biome particulièrement adapté pour essayer tout cela. Et voici une seed adaptée pour que vous puissiez apparaitre en plein désert : “-4099434556615951558”).
La première chose à faire est de créer une classe capable d’écouter un événement. Pour l’instant nous allons la nommer “Explosion”. Mais si vous voulez en savoir plus sur comment bien structurer vos fichiers au sein d’un programme, vous pourrez en savoir plus dans le chapitre correspondant.
Dans cette classe nous allons écrire un code Java qui sera exécutée à chaque fois qu’un joueur saute. Il existe de nombreux événements auxquels on peut réagir mais, dans notre cas, nous allons utiliser le PlayerMoveEvent. Cet événement est déclenché à chaque fois qu’un joueur se déplace (Étant donné que cela arrive très souvent, nous évitons généralement de faire appel à cet événement mais, dans le cadre de ce tutoriel, c’est tout à fait acceptable).
// Comme auparavant, nous importons plusieurs fichiers qui nous seront utiles par la suite.import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.util.Vector;
/* De la même manière qu’au sein des exercices précédents nous avons utilisé “extends” pour
hériter du fonctionnement d’autres classes, nous utilisons ici “implements” pour implémenter
le code de l’interface Listener. Nous expliquerons cela plus en détail par la suite. Pour
l’instant, sachez simplement que cela est indispensable pour pouvoir réagir à un événement
dans le jeu. */
public class Explosion implements Listener {
/* L’inscription “@EventHandler” ci-dessous permet à la méthode suivante, “onMove”,
d’être appelée à chaque fois que l’événement qu’elle écoute (“PlayerMoveEvent”)
sera déclenché.
Sans cette inscription cette méthode ne sera donc jamais déclenchée. */
@EventHandler
/* Nous créons ensuite cette méthode.
D’ordinaire une méthode peut prendre une ou plusieurs informations en paramètre (en les
inscrivant, séparées par des virgules, entre les parenthèses qui suivent son nom) et
retourner une et une seule information en retour (grâce à l’instruction return).
Comme expliqué précédemment ici la méthode attend un paramètre (“PlayerMoveEvent”) et
par contre elle ne renvoie aucune information (nous définissons cela en inscrivant
“void” avant le nom de la fonction). */
public void onMove(PlayerMoveEvent event){
/* Attention aux allergiques à la trigonométrie, nous allons parler un peu de repère
orthonormé : dans Minecraft, les blocs, les joueurs et toutes les autres entitées
sont situées à une position (“localisation” en anglais).
Cette position contient 3 informations :
- La composante X, c’est-à-dire la position sur l’axe Est-Ouest.
- La composante Y, c’est-à-dire la position sur l’axe Haut-Bas (l’altitude).
- La composante Z, c’est-à-dire la position sur l’axe Nord-Sud.
Vous pouvez voir cela à tout moment en jeu simplement en appuyant sur la touche “F3”
de votre clavier.
Ici nous allons donc vérifier que le déplacement du joueur est un déplacement vers
le haut.
Pour cela nous comparons donc la composante Y (l’altitude) entre le point de départ
(event.getFrom()) et l’emplacement d'arrivée du joueur (event.getTo()) au cours du
déplacement du joueur. Nous vérifions que le joueur est plus haut à la fin du
déplacement qu’au début.
Cela donne “event.getFrom().getBlockY() < event.getTo().getBlockY()”. Si cette
condition est vraie alors le joueur est en train de monter.
Mais il reste quelques problèmes. Et c’est en tentant de les régler que nous allons
mettre les mains dans le cambouis. C’est-à-dire nous attaquer à des complexités
innommables propres à Minecraft.
En effet avec cette seule condition l’explosion aura lieu à des moments où nous
souhaiterions que ce ne soit pas le cas :
- Si un joueur monte alors qu’il vole.
- Si un joueur nage vers le haut.
- Si un joueur monte un escalier.
- Si un joueur monte une échelle.
Nous avons donc besoin de rajouter quelques autres vérifications qui vont éviter que
des joueurs qui se trouvent dans ces situations n’aient à subir ces explosions :
- Pour régler le problème des nageurs nous allons vérifier que les joueurs ne sont
pas dans l’eau avec la condition :
!event.getFrom().getBlock().isLiquid()
Le “!” permet d’inverser le test et donc de vérifier que le joueur ne part pas
d’un emplacement empli de liquide.
- De la même manière, pour les personnes qui volent, nous allons utiliser la
condition suivante :
!event.getPlayer().isFlying()
- Et pour régler le problème concernant ceux qui montent sur une échelle ou un
escalier, nous allons employer une solution particulièrement étonnante, mais,
indispensable au vu de la manière dont Minecraft a été codé. Mais d’abord un petit
retour sur de la trigonométrie : Est-ce que vous vous souvenez de la notion de
vecteur ? On peut s’imaginer un vecteur comme une flèche. Plus elle est importante
plus la force est grande. Elle a aussi une direction et un sens (qu’est ce qu’elle
vise ?). Mais il existe une autre manière de définir les caractéristiques d’un
vecteur. C’est de le définir (dans le cadre d’un monde en 3 dimensions), par 3
composantes : X, Y et Z.
Dans Minecraft, la vélocité (la vitesse à un instant t) est un vecteur.
Même si à première vue cela peut sembler étonnant (aux yeux de la physique), un
joueur, dans Minecraft, n’a pas de vitesse verticale lorsqu’il monte un escalier
ou une échelle. Ainsi nous allons tester que la composante Y de sa vitesse pour
vérifier qu’elle est supérieure à 0 et ainsi nous assurer qu’il est en train de
sauter. Nous ferons cela grâce à la condition suivante :
event.getPlayer().getVelocity().getY() > 0
Nous allons ensuite lier chacune de ces conditions ensemble. Pour cela nous allons
utiliser “&&”. Ainsi il faudra que toutes ces conditions soient vraies toutes à la
fois pour que la suite de notre code soit exécutée. */
if (event.getPlayer().getVelocity().getY() > 0 &&
!event.getFrom().getBlock().isLiquid() &&
!event.getPlayer().isFlying() &&
event.getFrom().getBlockY() < event.getTo().getBlockY()) {
//Nous envoyons un petit message au joueur
event.getPlayer().sendMessage("Bisous, je m'envole !");
/* Et créons la fameuse explosion.
Elle se situe à l’emplacement des pieds du joueur.
La valeur 15 correspond à la puissance de l’explosion. Le “f” signifie que ce
nombre est un flottant. Il existe différents types de nombres (entiers, à
virgule, à virgule mais capable de stocker des valeurs plus grandes ou plus
précises). En somme, un flottant est un nombre à virgule. */
event.getPlayer().getWorld().createExplosion(event.getPlayer().getLocation(), 15f);
}
}
}
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
public final class SkyTuto extends JavaPlugin {
@Override
public void onEnable() {
/* Nous avons besoin d’inscrire une instance de notre classe Explosion comme étant à
l’écoute des événements qu’enverra Bukkit. Ce n’est qu’en faisant cela que notre
méthode onMove sera appelée au bon moment. */
Bukkit.getPluginManager().registerEvents(new Explosion(),this);
}
}
Et voilà, compilez votre code et vous pourrez désormais concurrencer SpaceX.
Le code source correspondant à cet exercice est disponible ici.
Une fois que vous aurez testé son bon fonctionnement (dans un nouveau monde), vous pourrez là encore sauvegarder votre code grâce à GIT.
N’hésitez pas à nous envoyer sur Twitter vos plus belles envolées (@SkytaleFr @minecraft_fra).
La programmation en Java
Nous vous avons montré un exemple précis. Le problème c’est que comprendre cet exemple ne vous permettra pas de coder en autonomie. C’est une première approche qui vous permettra de tâtonner pour aller un peu plus loin. Malheureusement cela ne vous amènera pas à créer des programmes complexes car ces derniers nécessitent une véritable maîtrise de la programmation.
Ainsi, pour aller plus loin, vous allez être obligés de véritablement maîtriser le langage de programmation Java. C’est long. C’est complexe. Mais de nombreuses personnes y sont arrivées avant vous. Nous sommes convaincus que si vous avez lu cet article jusqu’ici, vous avez les capacités nécessaires pour le faire. Il va simplement falloir vous accrocher.
Outre la connaissance des instructions que l’on peut utiliser au sein de ce langage ou des fonctionnalités fournies par l’API Spigot, il y a une autre connaissance dont vous aurez besoin. Nous vous avons parlé de-ci de-là de classes, d’instances, d’héritage ou d’interfaces. Tous ces mots sont propres à la programmation orientée objet. La programmation orientée objet est un paradigme de développement. Il est essentiel de bien la comprendre et de la maîtriser.
Voici quelques explications et tutoriels qui vous permettront d’aborder mieux le langage de programmation Java et la programmation orientée objet. Grâce à ces derniers, vous aurez enfin les moyens d’aller bien plus loin dans vos développements. Si vous souhaitez véritablement progresser, suivez les attentivement. N’hésitez pas à en voir plusieurs pour mieux retenir et compléter vos connaissances.
À l’écrit :
Et en vidéo:
Améliorer la structure de notre code Java
La connaissance de la programmation orientée objet vous apportera de très bonnes bases pour mieux structurer votre code. À côté de cela il existe des conventions et des bonnes pratiques.
En voici une liste (non exhaustive).
Outre sa lecture, nous vous invitons à consulter le code d’autres projets. GitHub est rempli de projets de plugins ou d’autres types de logiciels codés en Java. N’hésitez pas à en parcourir le code. Réaliser un gros projet oblige généralement à structurer son code. Ainsi cela vous fournira des exemples de structures réfléchies. C’est également un moyen un peu laborieux mais extrêmement efficace de découvrir des problématiques ainsi que des manières de les résoudre.
Trouver des solutions à vos problèmes
Chercher sur internet
Quand on cherche quelque chose sur internet on peut très facilement se perdre.
Pour la programmation, chercher en anglais est une bonne stratégie car la majorité des ressources disponibles sont dans cette langue. Si vous débutez, relire des tutoriels ou des explications génériques est souvent une bonne solution car les problématiques sur lesquelles vous tomberez résultent souvent d’une incompréhension de certaines bases. Par contre si vous êtes confronté à des problématiques techniques avancées, il existe de nombreux sites dont vous pouvez privilégier les résultats comme StackOverflow ou Spigot (si vous travaillez sur des plugins Minecraft).
En programmation (Java ou non) il existe aussi des mots clés importants. Il est donc pertinent de faire une première recherche pour découvrir les mots clés qui correspondent à votre cas d’usage puis faire une seconde recherche plus précise en utilisant ces mots clés.
Par exemple, si vous souhaitez découper du texte vous pouvez commencer par chercher “java array from string”. En parcourant les résultats de cette recherche vous trouverez le mot-clé “split” qui correspond au nom de la fonction que vous aurez besoin d’utiliser.
Dans un second temps vous pourrez rechercher “java split usage” pour trouver des exemples d’utilisation adaptés à vos besoins.
Bien sûr, vous pouvez aussi poser vos questions sur des forums, sur des discords ou ailleurs. Mais globalement les ressources les plus adaptées pour progresser et répondre à des problématiques avancées sont le plus souvent disponibles sur internet.
Faire une pause
Cette suggestion semble être une blague et elle est pourtant très sérieuse. Quand nous développons on a tendance à être très concentré. Ainsi lorsque l’on trouve une erreur on peut être tellement obnubilé par une manière de faire qu’il devient impossible de trouver la solution. Ainsi, lorsque l’on s’arrête pendant quelque temps, on peut revenir avec un regard frais. Cela permet d’aborder les choses différemment pour trouver la solution qui nous avait échappée.
Bref, quand vous bloquez, prenez l’air, marchez un peu, allez vous faire un thé ou profitez-en pour faire les quelques corvées que vous aviez procrastiné.
Lire la documentation
La documentation officielle du langage de programmation ou de Spigot / Bukkit / Paper peut être riche en informations utiles :
Même si elle n’est pas toujours très digeste, cela peut parfois être un bon moyen de trouver l’information qu’il vous manque.
Débugger
Personne ne réussit tout au premier essai et le code source d’un programme peut être gigantesque est extrêmement complexe. Dans ce genre de cas, il ne suffit pas toujours de relire son code (Java ou non) pour pouvoir trouver une solution.
Une fois que l’on a lancé son serveur avec notre plugin et que l’on a constaté que cela ne fonctionnait pas comme on le souhaitait, on peut le relancer en ayant activé le debugger.
Cet outil permet de dérouler pas à pas l’exécution de votre plugin et de suivre l’état des données. C’est un moyen complexe mais extrêmement efficace pour comprendre exactement sur quelle ligne de code et sous quelles conditions l’erreur que vous constatez apparaît.
Pour faire cela je vous invite à consulter les liens suivants :
Comment utiliser un debugger et à quoi sert-il :
[1] https://www.jetbrains.com/help/idea/debugging-your-first-java-application.html#setting-breakpoints [2] https://www.youtube.com/watch?v=1bCgzjatcr4 [3] https://www.youtube.com/watch?v=9gAjIQc4bPUComment configurer le debugger sur Spigot :
- Avec IntelliJ :
- Avec Eclipse, à l’écrit : https://www.spigotmc.org/wiki/eclipse-debug-your-plugin/
Lire dans la console ou dans les logs une stacktrace d’exception
Il s’agit de regarder la console lors de l’exécution, ou les logs (les journaux) après l’exécution, à la recherche de potentielles erreurs. Il est essentiel de savoir lire de telles erreurs.
Vous pourrez repérer une erreur en cherchant le texte “ERROR” (ou Exception plus généralement). Ces erreurs peuvent être courtes mais, le plus souvent, on obtient des textes gigantesques comme celui là :
Toutes ces lignes correspondent à la pile des appels successifs (chacune des méthodes de chaque classe) par lesquels le programme est passé avant d’exécuter la ligne de code qui a provoqué l’erreur.
Il peut y avoir, comme ici, une succession de causes sous-jacentes. La dernière cause correspond donc à la nature première de l’erreur et permet de connaître l’endroit où elle a eu lieu.
En lisant ces lignes, on peut être attentif au nom de package. C’est-à-dire à la succession de dossiers dans lesquels se trouve chaque fichier par lequel est passé le code (attention un package n’est pas un simple dossier, c’est une simplification. Vous trouverez ici des explications sur ce sujet). En effet, si vous voyez une ligne qui possède un package correspondant à Spigot, c’est sans doute moins intéressant pour comprendre votre erreur que des lignes qui concernent votre propre plugin.
Si l’on prend l’erreur ci-dessus, on peut constater que l’erreur provient de l’absence d’un fichier (“NoSuchFileException”). Le fichier concerné est res\img\menu_bg.png.
Il est donc possible, après en avoir pris l’habitude, de lire très rapidement ce type de rapport d’erreur pour régler rapidement les problèmes que vous rencontrez.
Mieux comprendre son propre code en suivant l’exécution
En lisant un code, n’importe qui se demande ce qu’il fait, étape par étape. Mais ce qui peut être un peu plus dur à se représenter lors d’une simple lecture, c’est l’évolution de l’état des variables.
Nous avons déjà parlé du debuggeur qui permet de voir cela étape par étape mais, plus vous serez expérimentés en développement Java et plus vous apprendrez à le faire de tête. C’est essentiel, mais cela ne deviendra possible qu’à force d’entraînement.
Un petit exercice
Et en bonus, nous allons vous donner un petit exercice en Java à réaliser dont nous donnerons la solution dans le prochain article.
L’objectif est d’obtenir les fonctionnalités suivantes :
– Lorsqu’un administrateur inscrit la commande “/firework NomDuJoueur” le joueur concerné est enregistré au sein d’une liste tandis que l’administrateur obtient un message de confirmation. Si l’administrateur réinscrit cette même commande, le joueur est supprimé de la liste et, là encore, un message de confirmation est envoyé à l’administrateur.
– Lorsqu’un joueur saute alors qu’il fait partie de cette liste, il s’envole dans les cieux, propulsé par des explosions sous ses pieds. Au cours de son ascension, une traînée de particules apparaît derrière lui.
On entend également le chuintement caractéristique d’une fusée d’artifice
Enfin, lorsqu’il atteint une certaine altitude, le joueur meurt et l’on peut voir une explosion de feux d’artifices (Whooo, une belle bleu !).
Pour cet exercice nous publierons de temps à autre, sur notre Twitter, quelques informations pour vous aider et vous accompagner ! N’hésitez pas à nous contacter sur discord si vous êtes vraiment bloqués, mais essayez tout d’abord de trouver par vous-même. Car c’est en cherchant et en persévérant que vous deviendrez autonomes.
À vos claviers !
Bonjour,
Je vous remercie pour cette remarque, nous corrigeons cela au plus vite !
Petite erreur de copier-coller au niveau des commandes git
git config -global user.name [email protected]
Celui ci devrait etre user.email et non user.name. Aussi il faut penser à indiquer le double tiré « dash » devant global « –global » et non « -global ».