Modifier le menu principal


  • Administrateurs

    Sommaire

    Introduction

    Une question qui revient souvent sur notre forum est: «comment modifier le menu principal du jeu ». Beaucoup de personnes étant ici pour créer un mod pour un serveur, c'est en effet quelque chose qui apporte un plus pour le serveur. Dans ce tutoriel, nous allons donc voir comment modifier ce gui en y ajoutant des boutons, en changeant le fond, le titre et en y ajoutant d'autres éléments comme un texte défilant ou des informations sur le serveur.

    Pré-requis

    Code

    Remplacer GuiMainMenu par un autre GUI :

    Forge ne nous permettant pas de modifier directement les classes de Minecraft (afin d'éviter que des mods soient incompatibles entre eux ou que des mods cassent des mécanismes de Forge ou Minecraft), nous allons devoir trouver une astuce pour modifier ce menu. Dans le cas d'un GUI de Minecraft, l'astuce la plus simple à mettre en place est d'ouvrir son propre GUI lorsque le GUI qu'on cherche à modifier apparaît. Ainsi, le gui sera remplacé par le nôtre. Cette astuce fonctionne pour tous les GUIs, sauf pour GuiIngame (qui fera l'objet d'un autre tutoriel).
    Afin de faire ceci, nous allons utiliser l’événement GuiOpenEvent pour détecter l'ouverture du GuiMainMenu et ouvrir le nôtre à la place.
    Comme les GUIs ne sont que présents sur le client, il va être important de faire ceci dans votre classe client (ClientProxy / NomDuModClient) pour éviter que votre mod fasse crasher le serveur au démarrage.
    Dans la classe client, ajoutez donc :

    MinecraftForge.EVENT_BUS.register(this);
    

    dans la fonction init. Cela va permettre d'enregistrer votre classe client comme une classe qui contient des événements.
    Ajoutez ensuite la fonction suivante :

        @SubscribeEvent
        public void onOpenGui(GuiOpenEvent event)
        {
            if(event.getGui() != null && event.getGui().getClass() == GuiMainMenu.class)
            {
                event.setGui(new GuiCustomMainMenu());
            }
        }
    

    Cette fonction est celle qui sera appelée lorsque l'événement GuiOpenEvent sera déclenché (pendant qu'un gui est en train d'être ouvert, avant qu'il soit affiché).
    On vérifie que le gui qui va être affiché est le menu principal et si oui, on demande d'afficher notre propre menu à la place.

    Pour plus d'information sur les événements, rendez-vous sur le tutoriel dédié aux événements.

    Création du GUI :

    Votre IDE vous indiquera une erreur sur "GuiCustomMainMenu", passez la souris dessus et cliquez sur "Create class GuiCustomMainMenu".
    Vous aurez une classe semblable à celle-ci :

    package fr.minecraftforgefrance.tutorial.client;
    
    import net.minecraft.client.gui.GuiScreen;
    
    public class GuiCustomMainMenu extends GuiScreen
    {
    
    }
    

    Nous allons mettre dans cette classe le même contenu que celui de GuiMainMenu. Pour ouvrir cette classe, afin de copier/coller son contenu, retournez dans la classe client, appuyez sur la touche CTRL et cliquez sur "GuiMainMenu" au niveau de la condition. Alternativement, déroulez le menu "Referenced libraries" puis "forgeSrc-1.11.2-version-de-forge.jar" puis "net.minecraft.client.gui", vous trouverez GuiMainMenu dans ce package.
    Une fois la classe ouverte, sélectionnez le contenu de celle-ci à partir de la première accolade (celle en dessous de "public class GuiMainMenu extends GuiScreen") jusqu'à la fin. Copiez le contenu puis collez-le dans votre classe GuiCustomMainMenu, à la place des deux accolades qui étaient présentes.
    Il y aura quelques erreurs à corriger :

    • Commencez par faire ctrl + shift + o pour mettre à jour les importations.
    • Le constructeur sera nommé GuiMainMenu (public GuiMainMenu()) il faut remplacer par GuiCustomMainMenu.
    • Juste au dessus du constructeur, il y aura cette ligne : private net.minecraftforge.client.gui.NotificationModUpdateScreen modUpdateNotification;
      Retirez-la, c'est en rapport avec le système de vérification de mise à jour de forge, malheureusement nous n'allons pas pouvoir le conserver (car demande en constructeur un GuiMainMenu et non un GuiCustomMainMenu)
      Retirez les autres lignes :
      • modUpdateNotification = net.minecraftforge.client.gui.NotificationModUpdateScreen.init(this, modButton);
      • modUpdateNotification.drawScreen(mouseX, mouseY, partialTicks);
    • Forge demande à nouveau un GuiMainMenu pour une fonction, celle qui affiche un splash text spécifique si une ancienne version de java est présente. Retirez-la aussi. (this.splashText = net.minecraftforge.client.ForgeHooksClient.renderMainMenu(this, this.fontRenderer, this.width, this.height, this.splashText);)
    • Dernier problème, une erreur en rapport avec Realms (qui ne nous intéresse pas du tout dans notre cas). Retirez tout le contenu en rapport avec Realms.

    Voilà à quoi devrait ressembler votre classe à la fin (vous pouvez reprendre directement ce code) : https://gist.github.com/robin4002/0bb5f14f0967e78bc5aa0c795410f355

    Modifier les boutons :

    Le code gérant les boutons se trouve dans la fonction initGui et dans la fonction addSingleplayerMultiplayerButtons.
    À titre d'exemple, je vais dans ce tutoriel remplacer le bouton multiplayer par un bouton permettant de se connecter sur le serveur ayant l'adresse 127.0.0.1 et le port 25565 (donc un serveur local) et ajouter un bouton pour se connecter sur le discord de minecraft forge france au dessus du bouton langage et qui aura comme texture l'icône de discord.

    Pour ajouter un bouton, il faut utiliser ce code dans la fonction initGui d'un gui :

            this.buttonList.add(new GuiButton(id, xPos, yPos, name);
    

    • id représente un id qui doit être unique, il sera utilisé pour gérer l'action au clic (si deux boutons ont le même id ils auront la même action au clic)
    • xPos est la position x où l'angle haut gauche du bouton va être positionné. Sachant qu'un bouton fait 200px de largeur par défaut, il faudra mettre this.width / 2 - 100px pour positionner le bouton au milieu de l'écran (this.width étant la largeur de l'écran).
    • yPos est la position y où l'angle haut gauche du bouton va être positionné. Le bouton singleplayer est positionné au quart de l'écran (this.height / 4) + 48px. Les boutons sont ensuite espacés de 24px. La taille par défaut d'un bouton est de 20px de haut.
    • name est le texte qui va s'afficher. Il est possible d'utiliser I18n.format("menu.name") pour passer par les fichiers de lang (il faudra alors mettre "menu.name=Nom" dans le fichier de lang)

    Il est possible de créer un bouton d'une taille différente avec ce constructeur :

            this.buttonList.add(new GuiButton(id, xPos, yPos, width, height, name);
    

    Où width est la largeur et height la hauteur.
    Les tailles que j'ai indiqué en px ne correspondent pas aux pixels de l'écran mais au nombre de pixels de la texture. Le nombre de pixels à l'écran sera différent en fonction du paramètre graphique "gui scale" dans les options du jeu.
    Pour éviter que les boutons bougent dans tous les sens quand la fenêtre est redimensionnée / que la résolution est différente, il faut toujours positionner les boutons en fonction de la largeur de l'écran (this.width), soit en fonction du centre ( this.width / 2) ou du quart de l'écran ( this.width / 4) etc ... Idem pour la hauteur (this.height).

    Je vais donc commencer par commenter la ligne ajoutant le menu multiplayer et comme je souhaite que mon menu soit au même endroit je vais reprendre les mêmes coordonnées :

        private void addSingleplayerMultiplayerButtons(int yPos, int yInterval)
        {
            this.buttonList.add(new GuiButton(1, this.width / 2 - 100, yPos, I18n.format("menu.singleplayer")));
            //this.buttonList.add(new GuiButton(2, this.width / 2 - 100, yPos + yInterval * 1, I18n.format("menu.multiplayer", new Object[0])));
            this.buttonList.add(new GuiButton(20, this.width / 2 - 100, yPos + yInterval * 1, I18n.format("menu.localserver")));
            this.buttonList.add(modButton = new GuiButton(6, this.width / 2 - 100, yPos + yInterval * 2, I18n.format("fml.menu.mods")));
        }
    

    J'ai, au passage, renommé les deux arguments de la fonction qui avaient des noms pas très explicites et j'ai retiré les new Object[0] inutiles qui sont générés automatiquement à la décompilation de Minecraft.
    Dans mon fichier en_US.lang j'ai ajouté :
    menu.localserver=Local Server
    Et dans le fichier fr_FR.lang j'ai mis :
    menu.localserver=Serveur local
    Il est bien sûr possible de se passer des fichiers de lang en mettant directement "Local Server" au lieu de I18n.format("menu.localserver"), si vous voulez mettre le nom de votre serveur c'est ce qu'il y a de mieux à faire comme les noms propres ne se traduisent pas.
    J'aurais pu garder 2 comme id de bouton, mais dans le cas où vous souhaitez garder le bouton multijoueur il faudra absolument mettre autre chose (20 est assez élevé pour ne pas avoir de soucis avec les id des boutons de minecraft).

    On va ensuite gérer l'action de ce bouton. Pour ça il faut aller dans la fonction actionPerformed et ajouter une condition pour notre nouveau bouton :

            if(button.id == 20)
            {
                FMLClientHandler.instance().connectToServer(this, new ServerData("localserver", "127.0.0.1:25565", false));
            }
    

    à la place de 127.0.0.1 on peut aussi indiquer localhost qui est un nom de domaine qui pointe vers 127.0.0.1.
    Et une dernière chose à ajouter très importante, sinon cette ligne va faire crasher le jeu, dans le constructeur de la classe (public GuiCustomMainMenu() { ) ajoutez la ligne suivante :

    FMLClientHandler.instance().setupServerList();
    

    Qui va se charger de faire quelques initialisations concernant la classe FMLClientHandler.

    Et voilà, le bouton est fonctionnel !

    Maintenant passons au bouton discord. Pour celui-ci, je vais faire quelque chose de similaire au bouton de langage : un bouton de 20x20 qui sera placé à gauche de mon bouton serveur.
    Il aura comme texture l'icône de discord grisé et l'icône de discord en couleur (bleu) quand on passe la souris dessus.
    J'ajoute donc cette ligne en dessous de la ligne concernant le bouton de langage :

            this.buttonList.add(new GuiButtonDiscord(21, this.width / 2 - 124, j + 24));
    

    Les valeurs sont presque les mêmes que celles du bouton langage, j'ai juste mis un y plus élevé pour être à côté du bouton serveur et j'ai mis comme id 21 (j est une variable déclarée plus haut qui vaut la hauteur de l'écran divisé par 4 + 48).
    Il faut maintenant créer la classe GuiButtonDiscord :

    package fr.minecraftforgefrance.tutorial.client;
    
    import fr.minecraftforgefrance.tutorial.ModTutorial;
    import net.minecraft.client.Minecraft;
    import net.minecraft.client.gui.Gui;
    import net.minecraft.client.gui.GuiButton;
    import net.minecraft.client.renderer.GlStateManager;
    import net.minecraft.util.ResourceLocation;
    
    public class GuiButtonDiscord extends GuiButton
    {
        private static final ResourceLocation DISCORD_ICON = new ResourceLocation(ModTutorial.MODID, "textures/gui/discord.png");
        private static final ResourceLocation DISCORD_HOVER_ICON = new ResourceLocation(ModTutorial.MODID, "textures/gui/discord_hover.png");
    
        public GuiButtonDiscord(int buttonId, int x, int y)
        {
            super(buttonId, x, y, 20, 20, ""); // taille de 20x20, pas de nom
        }
    
        public void drawButton(Minecraft mc, int mouseX, int mouseY)
        {
            if(this.visible)
            {
                boolean mouseHover = mouseX >= this.xPosition && mouseY >= this.yPosition && mouseX < this.xPosition + this.width && mouseY < this.yPosition + this.height;
                if(mouseHover) // si la souris est sur le bouton
                {
                    mc.getTextureManager().bindTexture(DISCORD_HOVER_ICON);
                }
                else
                {
                    mc.getTextureManager().bindTexture(DISCORD_ICON);
                }
                GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
                Gui.drawScaledCustomSizeModalRect(this.xPosition, this.yPosition, 0, 0, 128, 128, 20, 20, 128, 128);
            }
        }
    }
    

    Cette classe hérite donc de la classe bouton, dans le constructeur on définit une taille de 20x20 et on met comme nom un string vide (je n'ai rien inventé, GuiLanguage est fait de la même façon).
    Ensuite j'ai deux ressources qui correspondent à deux fichiers de texture, nommées discord.png et discord_hover.png qui se trouvent dans un package assets.tutorial.textures.gui (tutorial car c'est mon modid) et ce package se trouve dans src/main/resources (car toutes les ressources doivent être mises dans ce dossier, comme déjà expliqué dans le tutoriel sur la base du mod).
    Ces deux textures ont une taille de 128x128.
    Ensuite dans la fonction drawButton, on va vérifier si la souris est sur le bouton ou non et en fonction de ça on bind la bonne texture. Après cela, on utilise une fonction d'opengl pour la couleur, puis on utilise une fonction de la classe Gui pour draw la texture.
    Il existe plusieurs fonctions toutes faites dans la classe Gui pour nous éviter de dessiner étape par étape notre texture.
    La fonction drawTexturedModalRect(x, y, textureX, textureY, width, height) qu'utilise GuiLanguage (et qui est utilisée à beaucoup d'autres endroits de Minecraft) s'occupe d'appliquer un ratio pour passer d'image de 256x256 (ou 512x512, 1024x1024, etc ...) vers la bonne taille. Cette fonction ne peut que être utilisée avec des images ayant comme dimension une puissance de 2. On peut placer dans la même image des textures pour plusieurs composants, c'est ce que fait Mojang avec le fichier assets/minecraft/textures/gui/widgets.png (qui contient la plupart des éléments des gui).

    • x et y sont les positions où la texture va commencer à être dessiné (donc l'angle en haut à gauche).
    • textureX et textureY et la position de l'élement à l'intérieur de l'image. Par exemple, si on a une image de 256x256 avec dedans un premier élement positionné en 0;0 et un second positionné en 0;20, il faudra mettre 0 comme valeur textureX et 20 comme valeur textureY.
    • width et height correspondent à la taille de la texture.

    Cas concret avec le code du gui langage :
    0_1529702654041_drawTexturedModalRect.PNG
    (cliquez pour voir en grand. flag vaut true quand la souris se trouve sur le bouton).

    Si vous prévoyez d'avoir beaucoup d’éléments avec une texture personnalisée, passez par un gros fichier de 256x256 regroupant toutes les textures et la fonction drawTexturedModalRect sera une bonne solution.

    Dans mon cas, j'ai simplement deux textures différentes, qui sont également déjà très grandes (128x128). Je suis donc passé par une fonction plus flexible :
    drawScaledCustomSizeModalRect(x, y, u, v, uWidth, vHeight, width, height, tileWidth, tileHeight)

    • comme avant, x et y sont les positions où la texture va commencer à être dessiné (l'angle en haut à gauche)
    • u et v sont les positions dans le fichier de texture. (idem, on parle de l'angle haut gauche).
    • uWidth et vHeight la hauteur et la largeur dans la texture.
    • width et height la largeur et la hauteur de la zone dans laquelle la texture va être dessinée.
    • tileWidth et tileHeight la largeur et la hauteur de la texture sur le dessin.
      En général tileWidth et tileHeight auront respectivement les mêmes valeurs que uWidth et vHeight.

    À nouveau je vous propose une image explicative :
    0_1529702669426_drawScaledCustomSizeModalRect.PNG
    (cliquer pour voir en grand).

    drawScaledCustomSizeModalRect a donc l'avantage de permettre dessiner l'image dans une taille différente que celle du fichier d'origine. Ici j'ai utilisé deux fichiers .png de 128x128, mais on aura très bien pu mettre les deux ensembles dans un fichier de 256x128 et du-coup mettre le paramètre u sur 128 pour dessiner la texture quand la souris est sur le bouton.
    (L'explication donné ici est une explication simplifiée avec un exemple concret. En réalité u, v, uWidth, vHeight, tileWidth et tileHeight sont des ratios. Si vous appliquez un même coefficient sur les 3 variables u, uWidth et tileWidth ou sur v, vHeight et tileHeight, vous obtiendrez exactement le même résultat.
    Gui.drawScaledCustomSizeModalRect(this.xPosition, this.yPosition, 0, 0, 64, 64, 20, 20, 64, 64); donnera le même résultat.
    Gui.drawScaledCustomSizeModalRect(this.xPosition, this.yPosition, 0, 0, 32, 64, 20, 20, 32, 64); aussi).

    Maintenant que le bouton est dessiné, il ne reste plu qu'à ajouter son action dans la fonction actionPerformed :

            if(button.id == 21)
            {
                try
                {
                    Desktop.getDesktop().browse(new URI("https://discordapp.com/invite/0uXCYNHVWGM8sAYS"));
                }
                catch(URISyntaxException e)
                {
                    e.printStackTrace();
                }
            }
    

    Et voila !

    Changer l'arrière plan :

    Nous allons dans cette partie remplacer le panorama qui sert d'arrière plan par une image fixe.
    Commencez par repérer ces deux lignes :

        /** An array of all the paths to the panorama pictures. */
        private static final ResourceLocation[] TITLE_PANORAMA_PATHS = new ResourceLocation[] {new ResourceLocation("textures/gui/title/background/panorama_0.png"), new ResourceLocation("textures/gui/title/background/panorama_1.png"), new ResourceLocation("textures/gui/title/background/panorama_2.png"), new ResourceLocation("textures/gui/title/background/panorama_3.png"), new ResourceLocation("textures/gui/title/background/panorama_4.png"), new ResourceLocation("textures/gui/title/background/panorama_5.png")};
    

    On va la remplacer par la déclaration d'une simple ressource qui va être l'image de votre arrière plan (dans mon cas une image de 1920x1080) :

        private static final ResourceLocation BACKGROUND_TEXTURE = new ResourceLocation(ModTutorial.MODID, "textures/gui/background.png");
    

    ModTutorial est ici ma classe principale et MODID a pour valeur tutorial.
    Ma texture sera donc placée dans le dossier assets/tutorial/textures/gui/ et se nommera background.png
    Comme la variable TITLE_PANORAMA_PATHS n'existe plus, vous allez avoir une erreur dans la fonction drawPanorama. Supprimez la totalité de la fonction.
    Désormais il y aura une erreur dans la fonction renderSkybox comme il y avait un appel à drawPanorama dans cette dernière. Supprimez-la également, vous pouvez aussi retirer rotateAndBlurSkybox qui ne sert plus.
    Il reste maintenant une erreur dans la fonction drawScreen, comme il y avait un appel à la fonction renderSkybox dedans :

        public void drawScreen(int mouseX, int mouseY, float partialTicks)
        {
            GlStateManager.disableAlpha();
            this.renderSkybox(mouseX, mouseY, partialTicks); // erreur ici
            GlStateManager.enableAlpha();
            int i = 274;
            int j = this.width / 2 - 137;
            int k = 30;
            this.drawGradientRect(0, 0, this.width, this.height, -2130706433, 16777215);
            this.drawGradientRect(0, 0, this.width, this.height, 0, Integer.MIN_VALUE);
            this.mc.getTextureManager().bindTexture(MINECRAFT_TITLE_TEXTURES);
            GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
            // le reste de la fonction [...]
    

    À la place des trois premières lignes, nous allons mettre celles-ci pour dessiner notre propre arrière plan :

            mc.getTextureManager().bindTexture(BACKGROUND_TEXTURE);
            GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
            Gui.drawScaledCustomSizeModalRect(0, 0, 0, 0, 1, 1, this.width, this.height, 1, 1);
    

    J'ai réutilisé ici la même fonction drawScaledCustomSizeModalRect qu'avant.
    On commence à dessiner en 0, 0 (coin haut gauche du jeu) en lisant le fichier de texture à partir de 0, 0 également (coin haut gauche également).
    L'image doit prendre la totalité de la largeur de l'écran du jeu, on indique donc this.width pour la largeur, on fait de même pour la hauteur avec this.height.
    Ensuite il reste les quatre 1. Comme dit plus haut, la fonction utilise en réalité des ratios. J'aurais pu mettre 1920, 1080 les deux fois au lieu de 1,1 comme mon image fait cette résolution, mais mettre fois 1,1 fonctionne aussi et s'adaptera à toutes les tailles d'image (enfin, à condition que vous souhaitez que la totalité de l'image soit dessinée). Si vous voulez que seulement une partie de l'image s'affiche, je vous conseille de mettre la résolution de votre image puis changer les valeurs de u et v (le troisième et quatrième 0).

    Une dernière chose, si vous lancez votre jeu vous verez que votre rendu n'est pas exactement le même que l'image, les couleurs seront un peu différentes (comme si il y avait un léger brouillard sur l'image).
    C'est dû à un dégradé qui est dessiné juste après :

            this.drawGradientRect(0, 0, this.width, this.height, -2130706433, 16777215);
            this.drawGradientRect(0, 0, this.width, this.height, 0, Integer.MIN_VALUE);
    

    Supprimer les deux lignes réglera le problème de couleur mais le titre Minecraft aura un fond noir qui s'affichera ...
    Pour éviter cela, ajoutez cette ligne :

    GlStateManager.enableAlpha();
    

    à l'endroit où étaient les deux lignes drawGradientRect.

    Et voilà à quoi ressemble le début de la fonction drawScreen après modification :

        public void drawScreen(int mouseX, int mouseY, float partialTicks)
        {
            mc.getTextureManager().bindTexture(BACKGROUND_TEXTURE);
            GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
            Gui.drawScaledCustomSizeModalRect(0, 0, 0, 0, 1, 1, this.width, this.height, 1, 1);
            GlStateManager.enableAlpha();
    
            int j = this.width / 2 - 137;
            this.mc.getTextureManager().bindTexture(MINECRAFT_TITLE_TEXTURES);
            GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
            // le reste de la fonction [...]
    

    (j'ai au passage retiré les variables i et k qui ne sont pas utilisées).

    Changer le titre :

    Maintenant nous allons voir comment changer le titre par une autre image. Dans mon cas je vais mettre à la place la bannière de MFF qui fait 320x72.
    Comme dans le cas de l'image d'arrière plan, on va commencer par déclarer notre emplacement de ressource à la place de celui de Minecraft.

        private static final ResourceLocation MINECRAFT_TITLE_TEXTURES = new ResourceLocation("textures/gui/title/minecraft.png");
    

    devient alors :

        private static final ResourceLocation MFF_TITLE = new ResourceLocation(ModTutorial.MODID, "textures/gui/mff_title.png");
    

    (adaptez comme vous le souhaitez).
    Ma texture se trouve donc dans le dossier assets/tutorial/textures/gui et se nomme mff_title.png.
    Ensuite dans la fonction drawScreen nous allons devoir adapter ceci :

            int j = this.width / 2 - 137;
            this.mc.getTextureManager().bindTexture(MINECRAFT_TITLE_TEXTURES);
            GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
    
            if ((double)this.updateCounter < 1.0E-4D)
            {
                this.drawTexturedModalRect(j + 0, 30, 0, 0, 99, 44);
                this.drawTexturedModalRect(j + 99, 30, 129, 0, 27, 44);
                this.drawTexturedModalRect(j + 99 + 26, 30, 126, 0, 3, 44);
                this.drawTexturedModalRect(j + 99 + 26 + 3, 30, 99, 0, 26, 44);
                this.drawTexturedModalRect(j + 155, 30, 0, 45, 155, 44);
            }
            else
            {
                this.drawTexturedModalRect(j + 0, 30, 0, 0, 155, 44);
                this.drawTexturedModalRect(j + 155, 30, 0, 45, 155, 44);
            }
    

    Pour afficher à la place notre image. Le code de Minecraft est assez complèxe, les deux drawTexturedModalRect après le else permettent de dessiner l'image normalement, la première inverse le e et c (et affiche alors MINCERAFT, il s'agit d'un easter egg). Il y a deux drawTexturedModalRect car le titre de Minecraft est réparti sur deux lignes dans un fichier de 256x256 (comme je vous l'ai expliqué plus haut, drawTexturedModalRect impose ce format).
    Mon image ne suivant pas ce ratio (la votre surement non plus), on va passer par drawScaledCustomSizeModalRect.
    Après avoir un peu joué avec les valeurs, voilà mon code final :

            this.mc.getTextureManager().bindTexture(MFF_TITLE);
            GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
            Gui.drawScaledCustomSizeModalRect(this.width / 2 - 150, 25, 0, 0, 1, 1, 300, 60, 1, 1);
    

    Je vous conseille de lancer votre jeu en debug (cliquant sur le scarabée) afin que les changements s'appliquent en direct.
    Pour le positionnement, prennez le centre (this.width / 2) - la moitié de la longueur du rendu de votre image pour la centrer (150 dans mon cas comme j'ai mis 300 pour la longueur du rendu) comme position x. En position y, quelque chose entre 20 et 40 devrait convenir (le texte par défaut utilise 30).

    Si votre image contient de la semi-transparence, ajoutez ceci :

            GlStateManager.enableBlend();
            GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
    

    avant la fonction draw et ceci après la fonction draw :

            GlStateManager.disableBlend();
    

    Sans cela la semi-transparence ne sera pas prise en compte et le rendu ne sera pas terrible (dans le cas de la bannière de Minecraft Forge France, après l'avoir activé le rendu est meilleur mais reste moyen car l'image n'est pas prévu pour être affichée en aussi grand. Évitez donc les images trop petites).

    Un peu plus bas il y a le code affichant le splash texte. Il peut être intéressant de changer sa position pour éviter qu'il ne couvre votre titre (voire de le retirer si vous le voulez).
    Vous pouvez également supprimer toutes les lignes faisant référence à updateCounter, qui ne sert maintenant plus (c'est la variable qui détermine s'il faut afficher MINECRAFT ou MINCERAFT).

    Afficher les informations d'un serveur :

    Un point qui devrait intéresser beaucoup de gens, comment récupérer et afficher les informations d'un serveur ? (nombre de joueur, nombre de slot, motd, etc ...).
    Ajoutez avant le constructeur (avant public GuiCustomMainMenu() et après private GuiButton modButton) les lignes suivantes :

        private final ServerPinger serverPinger = new ServerPinger();
        private ServerData server = new ServerData("localserver", "localhost:25565", false);
        private static final ThreadPoolExecutor EXECUTOR = new ScheduledThreadPoolExecutor(5, (new ThreadFactoryBuilder()).setNameFormat("Server Pinger #%d").setDaemon(true).build()); 
    

    En remplacant l'ip et le nom par ceux de votre serveur (le nom n'a pas vraiment d'importance ici).
    Ensuite dans la fonction drawScreen (peu importe où, soit au début, soit à la fin, soit après le code du dessine l'arrière plan et le titre) ajoutez ceci :

            if(!this.server.pinged)
            {
                this.server.pinged = true;
                this.server.pingToServer = -2L;
                this.server.serverMOTD = "";
                this.server.populationInfo = "";
                EXECUTOR.submit(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        try
                        {
                            GuiCustomMainMenu.this.serverPinger.ping(GuiCustomMainMenu.this.server);
                        }
                        catch(UnknownHostException unknowHostException)
                        {
                            GuiCustomMainMenu.this.server.pingToServer = -1L;
                            GuiCustomMainMenu.this.server.serverMOTD = TextFormatting.DARK_RED + "Impossible de résoudre le nom d'hôte";
                            // on peut aussi utiliser I18n pour passer par les fichiers de langage
                        }
                        catch(Exception exception)
                        {
                            GuiCustomMainMenu.this.server.pingToServer = -1L;
                            GuiCustomMainMenu.this.server.serverMOTD = TextFormatting.DARK_RED + "Impossible de se connecter au serveur";
                        }
                    }
                });
            }
    

    Cela va permettre de ping le serveur s'il n'est pas encore pinger. D'ailleurs, si vous souhaitez faire un bouton pour actualiser, il suffira de lui mettre en action this.server.pinged = false; pour que le ping soit refait.
    Comme dit en commentaire, vous pouvez passer par des fichiers de langage avec la fonction I18n.format("nom.non.localisé") si vous le souhaitez. Pour les couleurs, utilisez le caractère § puis un nombre de 0 à 9 ou une lettre de a à f. (TextFormatting.DARK_RED à pour équivalent §4).

    this.server.pingToServer sera supérieur à 0 si le ping est fini et a réussi. Autrement il faudra -1L et le message d'erreur sera indiqué dans this.server.serverMOTD.
    Il y a différentes informations disponibles :

    • this.server.populationInfo est un string ayant pour valeur le nombre de joueur / le nombre de slot disponible, formaté en couleur grise.
    • this.server.pingToServer est un double ayant pour valeur le ping en ms.
    • this.server.playerList est un string contenant tous les joueurs connectés, espace par un \n. Attention, il peut être nul ou vide. this.mc.fontRenderer.listFormattedStringToWidth(this.server.playerList, taille max) vous pemettra de récupérer une liste de String avec chaque joueur taille max représente la longueur maximum que peut avoir un String. Si un nom de joueur est trop long, il sera donc mis dans deux string différents. Il ne vous restera plus qu'à itérer sur la liste pour afficher les joueurs, sans risque de dépassement de la zone que vous avez prévu pour le texte.
    • this.server.serverMOTD est un string contenant le motd du serveur.

    Et voilà un exemple de ce qu'on peut faire :

            if(this.server.pingToServer >= 0L)
            {
                this.drawString(this.fontRenderer, this.server.populationInfo + TextFormatting.RESET + " joueurs", this.width / 2 + 110, this.height / 4 + 72, 0x245791);
                this.drawString(this.fontRenderer, this.server.pingToServer + " ms", this.width / 2 + 110, this.height / 4 + 82, 0x245791);
                if(this.server.playerList != null && !this.server.playerList.isEmpty())
                {
                    List <String>list = this.mc.fontRenderer.listFormattedStringToWidth(this.server.playerList, this.width - (this.width / 2 + 110));
                    for(int i = 0;i < list.size(); i++)
                    {
                        if(i >= 10)
                        {
                            break;
                        }
                        this.drawString(this.fontRenderer, list.get(i), this.width / 2 + 110, this.height / 4 + 92 + 10 * i, 0x245791);
                    }
                }
                this.drawCenteredString(this.fontRenderer, this.server.serverMOTD, this.width / 2, this.height / 4 + 24, 0x245791);
            }
            else
            {
                this.drawString(this.fontRenderer, this.server.serverMOTD, this.width / 2 + 110, this.height / 4 + 72, 0x245791);
            }
    

    à placer toujours dans la fonction drawScreen, en dessous du code précedent.
    La première ligne affiche le nombre de joueur, juste à droite du bouton de connexion au serveur.
    La seconde ligne affiche le ping, juste en dessous du texte précédent.
    Ensuite, si la liste des joueurs n'est ni null ni vide, j'utilise la fonction dont j'ai parlé plus haut pour avoir une liste de string. Comme je dessine le texte en x = this.width / 2 + 110, il ne faut pas que this.width / 2 + 110 + la longueur du texte soit plus grand que this.width, sinon le texte va sortir de l'écran. Je mets donc this.width - (this.width / 2 + 110) comme longueur maximum pour le texte.
    Ensuite avec une petite boucle sur la liste je dessine un par un chaque ligne de la liste (donc chaque pseudo de joueur, ou morceau de pseudo si un est trop long). Pour éviter d'avoir une liste trop longue, si la boucle arrive à 10 je break (= sortir de la boucle). Ainsi s'il y a plus de 10 joueurs, seul le pseudo des 10 premiers est affichés.
    Pour finir, j'affiche au dessus du bouton single player le motd du serveur.
    Dans le cas où le ping a échoué, j'affiche à gauche du bouton serveur l'erreur.

    Voila, c'est un exemple simple pour vous montrer comment afficher les informations du serveur. Le rendu n'est pas super, pour avoir quelque chose de plus présentable il faudrait afficher les informations sur un font (il suffit de dessiner une image avant le texte avec les fonctions expliquées précédemment).

    Ajouter un texte défilant :

    Afin d'afficher un peu mieux le motd du serveur, je vous propose de l'afficher tout en haut du menu et de façon défilante. Nous allons aussi voir comment afficher le texte d'une page web dans un second temps.
    Avant le constructeur, on va commencer par déclarer une classe qui va représenter la position x du texte :

        private int scrollingTextPosX;
    

    Ensuite dans la fonction drawScreen, ajoutez ceci :

            this.scrollingTextPosX --;
            if(this.scrollingTextPosX < -this.fontRenderer.getStringWidth(this.server.serverMOTD))
            {
                this.scrollingTextPosX = this.width;
            }
            this.drawRect(0, 0, this.width, 12, 0x77FFFFFF);
            this.fontRenderer.drawString(this.server.serverMOTD, this.scrollingTextPosX, 2, 0x245791);
    

    La première ligne (this.scrollingTextPosX --; ) change la position du texte.
    Ensuite, si le texte sort complètement de l'écran sur la gauche, on remet x ) la largeur de l'écran pour le replacer à gauche.
    drawRect dessine un rectange de 0,0 à la largeur de l'écran, 12 (donc un petit rectangle en haut du l'écran). La couleur est au format argb (alpha, red, green, blue). Mettre 00 comme valeur pour la couche alpha rendra le rectangle totalement transparent et FF le rendra totalement opaque.
    Enfin la fonction drawString dessine le texte aux positions indiquées avec la couleur RGB 0x245791 (code hexa)

    Si vous souhaitez plutôt récupérer le texte d'un fichier texte sur le web au lieu d'afficher le motd, nous allons voir maintenant comment adapter le code précedent pour faire ceci.
    Créez une nouvelle variable nommée scrollingText qui aura comme valeur par défaut un texte vide :

        private String scrollingText;
    

    Dans le code que nous avons mis précédemment dans la fonction drawScreen, remplacez this.server.serverMOTD par this.scrollingText les deux fois où il est présent.
    Enfin, dans le constructeur de la classe (dans public GuiCustomMainMenu() { ) ajoutez ceci :

            new Thread()
            {
                @Override
                public void run()
                {
                    try
                    {
                        URL url = new URL("http://dl.mcnanotech.fr/mff/tutoriels/mainmenutext.txt");
                        InputStream is = url.openStream();
                        GuiCustomMainMenu.this.scrollingText = CharStreams.toString(new InputStreamReader(is, Charsets.UTF_8));
                    }
                    catch(Exception e)
                    {
                        GuiCustomMainMenu.this.scrollingText = "Impossible de lire le texte";
                    }
                }
            }.start();
    

    en remplaçant l'url par ce qu'il faut.
    Ce code va ouvrir l'url et lire le contenu du fichier texte. Je profite du fait que Mojang utilise la bibliothèque guava pour passer par la classe CharStreams de cette dernière et rendre le code plus compact.
    Le texte est lu en UTF-8, il faudra donc bien enregistrer en UTF-8 dans votre éditeur de texte pour que les accents passent.
    Si le texte n'arrive pas être récupérer (s'il n'y a pas de connexion), il affichera impossible de lire le texte.
    Tout le code se trouve à l'intérieur d'un thread afin d'ouvrir la connexion et lire le texte en parallèle du reste, sinon cela causerait un freeze du jeu jusqu'à ce qu'il récupère le contenu.

    Résultat

    Un magnifique menu :
    0_1529702641283_resultat-menu-custom.PNG

    Voir le commit sur github
    Le commit sur github montre clairement où ont été placé les fichiers, ainsi que ce qui a été ajouté et retiré dans le fichier.

    Crédits

    Rédaction :

    Correction :

    • Toutoune1008

    Creative Commons
    Ce tutoriel de Minecraft Forge France est mis à disposition selon les termes de la licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International

    retourRetour vers le sommaire des tutoriels



  • Pourrez tu faire le meme tuto mes en 1.7.10 ? ^^



  • Ton précédent topic ne t'as pas aidé finalement ?


  • Administrateurs

    Non, je ne fais plus aucun tutoriel en 1.7.10.

    Après la méthode est sensiblement la même en 1.7.10, il y a juste quelques noms de fonctions qui changent.



  • @'robin4002':

    Non, je ne fais plus aucun tutoriel en 1.7.10.

    Après la méthode est sensiblement la même en 1.7.10, il y a juste quelques noms de fonctions qui changent.

    Les quelles ?


  • Administrateurs

    Toutes celles qui sont soulignés en rouge par ton IDE.
    Je n'ai pas le temps de tout détailler et de toute façon je n'ai même plus de workspace 1.7.10 pour le faire.



  • Juste petit point concernant la partie de la modification des boutons @robin4002 , dans la fonction drawButton, il manque (en 1.12) le paramètre partialTicks en float.

    Sinon c'est impec' !



  • gg bon tuto
    mais juste

        @SubscribeEvent
        public void onOpenGui(GuiOpenEvent event)
        {
            if(event.getGui() != null && event.getGui().getClass() == GuiMainMenu.class)
            {
                event.setGui(new GuiCustomMainMenu());
            }
        }
    

    pk ne pas avoir mit plutot event.getGui() instanceof GuiMainMenu c'est plus facile a ecrire nan ?


  • Administrateurs

    Les deux fonctionnent dans ce cas, par contre l'instanceof ne fait pas exactement la même chose puisqu'il vérifie tout l'héritable (alors que mon code ne vérifie que la classe en question).