Ajouter un rendu via TESR à votre bloc



  • Sommaire

    Introduction

    Bonjour à tous, au menu de ce tutoriel la création d'un bloc avec le rendu d'un modèle 3D fait avec Techne ou CraftStudio par exemple, commençons sans plus tarder.

    Pré-requis

    Code

    BlockTutoriel :

    Sans perdre de temps nous allons créer une classe (si ce n'est pas déjà fait) pour notre block custom, implémentons dans cette dernière le ITileEntityProvider
    Dans le constructeur veillez à bien définir un nom non-localisé et, si votre futur modèle 3D est plus petit qu'un bloc normal, à redéfinir la hitbox de ce dernier.

    Voilà à quoi ressemble le début de votre classe:

    public class BlockTutoriel extends Block implements ITileEntityProvider {
    
        public BlockTutoriel() {
            super(Material.rock);
            this.setUnlocalizedName("blockTutoriel");
            this.setCreativeTab(CreativeTabs.tabMisc);
            // Ici on adapte la hitbox du bloc pour notre modèle 3D
            this.setBlockBounds(0.0625F, 0.0F, 0.0625F, 0.9375F, 0.875F, 0.9375F);
        }
    }
    

    Dans la continuité de notre classe nous allons définir le type de rendu pour notre bloc et son opacité

        /**
        * On appelle le type de rendu pour notre bloc ici -1 pour pouvoir faire le
        * rendu à part d'un JSON basique, donc via une TESR
        */
        public int getRenderType() {
            return -1;
        }
    
        /**
        * On fait en sorte que notre bloc ne soit pas opaque pour avoir la
        * transparence de notre modèle 3D
        */
        public boolean isOpaqueCube() {
            return false;
        }
    

    Ajoutons ensuite l'entité à notre bloc:

        @Override
        public TileEntity createNewTileEntity(World worldIn, int meta) {
            return new TileEntityTutoriel();
        }
    

    Pour en finir avec la classe de notre bloc nous allons ajouter deux méthodes qui sont "géréniques" pour les blocs avec une "tile entity" donc à vous de juger si vous comptez faire plusieurs blocs avec un rendu TESR de faire une classe mère ou non

        /**
        * Lorsque le bloc est cassé on s'assure de supprimer l'entité, de ce
        * dernier, du monde.
        */
        public void breakBlock(World worldIn, BlockPos pos, IBlockState state) {
            super.breakBlock(worldIn, pos, state);
            worldIn.removeTileEntity(pos);
        }
    
        /**
        * Appelée côté Client et Server lorsque le bloc est ajouté dans le monde
        */
        public boolean onBlockEventReceived(World worldIn, BlockPos pos, IBlockState state, int eventID, int eventParam) {
            super.onBlockEventReceived(worldIn, pos, state, eventID, eventParam);
            TileEntity tileentity = worldIn.getTileEntity(pos);
            return tileentity == null ? false : tileentity.receiveClientEvent(eventID, eventParam);
        }
    

    Votre classe devrait ressembler à ça au final:

    
    import fr.minecraftforgefrance.tutoriel.common.tileEntity.TileEntityTutoriel;
    import net.minecraft.block.Block;
    import net.minecraft.block.ITileEntityProvider;
    import net.minecraft.block.material.Material;
    import net.minecraft.block.state.IBlockState;
    import net.minecraft.creativetab.CreativeTabs;
    import net.minecraft.entity.EntityLivingBase;
    import net.minecraft.item.ItemStack;
    import net.minecraft.tileentity.TileEntity;
    import net.minecraft.util.BlockPos;
    import net.minecraft.util.MathHelper;
    import net.minecraft.world.World;
    
    public class BlockTutoriel extends Block implements ITileEntityProvider {
    
        public BlockTutoriel() {
            super(Material.rock);
            this.setUnlocalizedName("blockTutoriel");
            this.setCreativeTab(CreativeTabs.tabMisc);
            // Ici on adapte la hitbox du bloc pour notre modèle 3D
            this.setBlockBounds(0.0625F, 0.0F, 0.0625F, 0.9375F, 0.875F, 0.9375F);
        }
    
        /**
        * On appelle le type de rendu pour notre bloc ici -1 pour pouvoir faire le
        * rendu à part d'un JSON basique, donc via un modele.java et une TESR
        */
        public int getRenderType() {
            return -1;
        }
    
        /**
        * On fait en sorte que notre bloc ne soit pas opaque pour avoir la
        * transparence de notre modèle 3D
        */
        public boolean isOpaqueCube() {
            return false;
        }
    
        /**
        * On ajoute notre entité à notre bloc (TileEntity)
        */
        @Override
        public TileEntity createNewTileEntity(World worldIn, int meta) {
            return new TileEntityTutoriel();
        }
    
        /**
        * Lorsque le bloc est cassé on s'assure de supprimer l'entité, de ce
        * dernier, du monde.
        */
        public void breakBlock(World worldIn, BlockPos pos, IBlockState state) {
            super.breakBlock(worldIn, pos, state);
            worldIn.removeTileEntity(pos);
        }
    
        /**
        * Called on both Client and Server when World#addBlockEvent is called
        */
        public boolean onBlockEventReceived(World worldIn, BlockPos pos, IBlockState state, int eventID, int eventParam) {
            super.onBlockEventReceived(worldIn, pos, state, eventID, eventParam);
            TileEntity tileentity = worldIn.getTileEntity(pos);
            return tileentity == null ? false : tileentity.receiveClientEvent(eventID, eventParam);
        }
    }
    

    TileEntityTutorielSpecialRenderer :

    Cette classe va se charger du rendu de notre modèle 3D sur notre bloc, pour ce faire, une fois créée, étendez votre classe par TileEntitySpecialRenderer

    import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
    
    public class TileEntityTutorielSpecialRenderer extends TileEntitySpecialRenderer {
    
    }
    

    Nous allons ensuite appeler la classe de notre Modèle 3D et un ResourceLocation pour la texture.

    private static ModelBlockTutoriel modelBlock = new ModelBlockTutoriel();
    public static ResourceLocation texture = new ResourceLocation("moid", "chemin/vers/la/texture/de/votre/modele.png");
    

    Créons ensuite une fonction avec comme arguments notre TileEntity, des double pour la position X,Y,Z de notre modèle 3D, un float pour les ticks et un integer pour l'animation jouée lors du cassage du bloc

    public void renderTileEntityTutorielAt(TileEntityTutoriel tileEntity, double posX, double posY, double posZ, float partialTicks, int damageCount) {
    
    }
    

    Ajoutez ensuite la fonction à override pour le rendu de notre modèle 3D dans notre TESR

    @Override
    public void renderTileEntityAt(TileEntity tileEntity, double posX, double posY, double posZ, float partialTicks, int damageCount) {
    
        this.renderTileEntityTutorielAt(((TileEntityTutoriel) tileEntity), posX, posY, posZ, partialTicks, damageCount);
    }
    

    Retournez ensuite dans la fonction renderTileEntityTutorielAt() pour que nous affichions le modèle 3D à l'emplacement du bloc

        //On ouvre la matrice de rendu
        GlStateManager.pushMatrix();
        //On place le modèle 3D aux position X,Y,Z du blocs
        GlStateManager.translate(posX + 0.5F, posY + 1.5F, posZ + 0.5F);
        //On retourne le bloc pour le mettre à l'endroit
        GlStateManager.rotate(180F, 1.0F, 0.0F, 0.0F);
        //On affiche la texture
        this.bindTexture(texture);
        //On fait le rendu du modèle 3D
        modelBlock.renderAll();
        //On ferme la matrice de rendu
        GlStateManager.popMatrix();
    

    Si jamais vous avez besoin d'ajuster, voici un repère :]

    (image trouver sur google provenant de ce site, vous pouvez faire un tour dessus, ça parle de 3D avec java)

    • GL11.glTranslatef(1.0F, 0.0F, 0.0F); déplace de 1 sur l'axe x.
    • GL11.glTranslatef(0.0F, 1.0F, 0.0F); déplace de 1 sur l'axe y.
    • GL11.glTranslatef(0.0F, 0.0F, 1.0F); déplace de 1 sur l'axe z.

    TileEntityInventoryRenderer:

    Passons maintenant au rendu dans l'inventaire, car c'est sympa les TESR dans le monde mais si le rendu dans l'inventaire ne fonctionne pas, c'est tout de suite moins chouette.

    Je vous fournis la classe qui vous permettre d'avoir votre rendu TESR dans l'inventaire, je l'expliquerai ensuite.

    package fr.minecraftforgefrance.tutoriel.client.render;
    
    import fr.minecraftforgefrance.tutoriel.common.core.TutorielCore;
    import fr.minecraftforgefrance.tutoriel.common.tileEntity.TileEntityTutoriel;
    import net.minecraft.block.Block;
    import net.minecraft.client.renderer.tileentity.TileEntityItemStackRenderer;
    import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
    import net.minecraft.item.ItemStack;
    
    public class TileEntityInventoryRenderHelper extends TileEntityItemStackRenderer {
    
        private TileEntityTutoriel tileEntityTutoriel = new TileEntityTutoriel();
    
        @Override
        public void renderByItem(ItemStack itemStack) {
            Block block = Block.getBlockFromItem(itemStack.getItem());
    
            if (block == TutorielCore.blockTutoriel) {
                TileEntityRendererDispatcher.instance.renderTileEntityAt(this.tileEntityTutoriel, 0.0D, 0.0D, 0.0D, 0.0F);
            } else {
                super.renderByItem(itemStack);
            }
        }
    }
    

    Explication du fonctionnement de la classe:

    Nous faisons une classe qui s'étend de la classe **TileEntityItemStackRenderer **qui sert au final à enregistrer les TileEntity et leurs Rendus pour l'avoir sous forme d'item et dans l'inventaire. (Cela ne s'applique bien évidemment qu'aux blocs)

    Pour cela on Override la fonction renderItem() pour que nous puissions définir notre liste de blocs avec un rendu TESR à rendre sous la forme d'item avec un modèle 3D dans l'inventaire (ou quand l'item est droppé au sol, une entité donc)

    Grâce au

    if (block == TutorielCore.blockTutoriel) {}
    

    On définit ce bloc et pas un autre avec son propre rendu et sa propre Tile Entity, d'où la déclaration de;

    TileEntityRendererDispatcher.instance.renderTileEntityAt(this.tileEntityTutoriel, 0.0D, 0.0D, 0.0D, 0.0F);
    

    NB: Il est clair que le gros du travail pour le rendu dans l'inventaire n'est pas dans cette classe, mais dans **TileEntityItemStackRenderer **

    Pour ajouter le rendu dans l'inventaire de plusieurs blsoc, faites une série de else if() comme par exemple:

        private TileEntityTutoriel tileEntityTutoriel = new TileEntityTutoriel();
        private TileEntityTutoriel2 tileEntityTutoriel2 = new TileEntityTutoriel2();
        private TileEntityTutoriel3 tileEntityTutoriel3 = new TileEntityTutoriel3();
    
        @Override
        public void renderByItem(ItemStack itemStack) {
            Block block = Block.getBlockFromItem(itemStack.getItem());
    
            if (block == TutorielCore.blockTutoriel) {
                TileEntityRendererDispatcher.instance.renderTileEntityAt(this.tileEntityTutoriel, 0.0D, 0.0D, 0.0D, 0.0F);
            }
            else if (block == TutorielCore.blockTutoriel2) {
                TileEntityRendererDispatcher.instance.renderTileEntityAt(this.tileEntityTutoriel2, 0.0D, 0.0D, 0.0D, 0.0F);
            }
            else if (block == TutorielCore.blockTutoriel3) {
                TileEntityRendererDispatcher.instance.renderTileEntityAt(this.tileEntityTutoriel3, 0.0D, 0.0D, 0.0D, 0.0F);
            }
            else {
                super.renderByItem(itemStack);
            }
        }
    

    Bien maintenant que nous avons créer les classes "principales" pour le rendu de notre bloc avec une TESR (TileEntitySpecialRenderer)
    Passons à la classe principale, les proxys et les JSON

    La Classe Principale :

    Maintenant que nous sommes dans notre classe pricipale, vérifiez bien que vous ayez enregistré votre bloc, car c'est exactement la même méthode, nous allons juste enregistrer notre TileEntity et son Rendu Spécial (TileEntitySpecialRenderer)

    Dans la fonction init() après l'appel du proxy:

    On enregistre la TileEntity

        GameRegistry.registerTileEntity(TileEntityTutoriel.class, "tileEntityTutoriel");
    

    Et on déclare le rendu spécial à notre TileEntity (et donc à notre bloc)

        ClientRegistry.bindTileEntitySpecialRenderer(TileEntityTutoriel.class, new TileEntityTutorielSpecialRenderer());
    

    Votre fonction init() devrait ressembler à ça:

    ClientProxy :

    Maintenant que notre travail dans la classe principale est terminé nous allons déclarer notre classe **TileEntityInventoryRenderHelper **dans la fonction init() (ou registerRender() peu importe comment vous l'avez appelée) en utilisant une nouvelle instance de TileEntityItemStackRenderer sans cette ligne de code presque insignifiante votre rendu dans l'inventaire ne fonctionnera pas.

        @Override
        public void init() {
            TileEntityItemStackRenderer.instance = new TileEntityInventoryRenderHelper();
        }
    

    Nous en avons terminé avec le ClientProxy

    Les fichiers JSON:

    Et nous voilà dans la partie la plus importante pour un rendu correct dans notre monde cubique de votre bloc, mais là où ça peut paraître étrange c'est le fait de devoir utiliser des fichiers json alors que nous utilisons un rendu TESR, et bien c'est pour dans un premier temps éviter d'avoir des messages dans la console du type:

    Model definition for location tutorielmff:blockTutoriel#normal not found
    

    et pour bien entendu avoir une position correcte dans la main, de notre modèle 3D et pour définir une texture de particule à notre bloc (par exemple quand on le casse à la main, quand on sprinte dessus, etc...)

    Le fichier JSON des blockstates (assets/<votre_modid>/blockstates)

    {
        "variants": {
            "normal": { "model": "tutorielmff:blockTutoriel" }
        }
    }
    

    tutorielmff est l'ID de votre mod (modid)
    blockTutoriel est le nom du fichier json qui se trouve dans models/block

    Le fichier JSON du block (assets/<votre_modid>/models/block)

    {
        "parent": "builtin/entity",
        "textures": {
            "particle": "tutorielmff:models/blocks/model_block_tutoriel"
        }
    }
    

    tutorielmff est l'ID de votre mod (modid)
    models/blocks/model_block_tutoriel est le chemin dans le dossier des textures (assets/<votre_modid>/textures) de la texture pour les particules du bloc

    Le fichier JSON de l'item(assets/<votre_modid>/models/item)

    {
        "parent": "builtin/entity",
        "display": {
            "thirdperson": {
                "rotation": [ 10, -45, 170 ],
                "translation": [ 0, 1.5, -2.75 ],
                "scale": [ 0.375, 0.375, 0.375 ]
            }
        }
    }
    
    • builtin/entity est le type de rendu de notre bloc, ici une entité de bloc donc Tile Entity et donc TESR
    • "display": {} est la fonction qui sert à faire tourner, agrandir/rétrécir, et déplacer le rendu du modèle 3D pour qu'il soit dans la main
    :::

    Nous en avons désormais terminé avec la création de notre bloc avec un rendu TESR !

    En bonus de ce tutoriel vous pourrez trouver la rotation du modèle en fonction de l'orientation du joueur, et l'affichage des dégâts sur le bloc.

    Bonus

    Faire pivoter le bloc en fonction de l'orientation du joueur :

    Pour se faire nous allons devoir modifier la classe de notre TileEntity

    Pour y ajouter quelques paramètres pour la sauvegarde de la rotation du bloc:

    (J'ai dû faire quelques légères modifications de certains fields ou noms de méthodes, parce-que la 1.8)

        private byte direction;
    
        @Override
        public void readFromNBT(NBTTagCompound compound) {
            super.readFromNBT(compound);
            this.direction = compound.getByte("Direction");
        }
    
        @Override
        public void writeToNBT(NBTTagCompound compound) {
            super.writeToNBT(compound);
            compound.setByte("Direction", this.direction);
        }
    
        public byte getDirection() {
            return direction;
        }
    
        public void setDirection(byte direction) {
            this.direction = direction;
            this.worldObj.markBlockForUpdate(this.pos);
        }
    

    Dans l'ordre :

    • on déclare la variable direction, qui contiendra la direction du bloc.
    • readFromNBT permet de lire dans la sauvegarde du monde la valeur de la direction (mais ça vous devrez déjà le savoir si vous avez bien suivi les pré-requis
    • writeToNBT permet d'écrire dans la sauvegarde du monde la valeur de la direction (idem que le commentaire d'en haut)
    • getDirection permet d'obtenir la valeur de la direction
    • setDirection permet de mettre la valeur de la direction, on fait un worldObj.markBlockForUpdate juste en dessous pour indiquer que le bloc a été modifié.

    Mais il va y avoir un problème, en effet la valeur direction est bien sauvegardée, mais si vous avez bien retenu ce qui est dit dans le tutoriel sur les entités de bloc, elle est sauvegardée du côté du serveur, or c'est le client qui en a besoin pour faire le rendu. Il faut donc synchroniser ces deux valeurs. Ajoutez donc ces deux fonctions dans la classe de l'entité de bloc :

    public Packet getDescriptionPacket() {
        NBTTagCompound nbttagcompound = new NBTTagCompound();
        this.writeToNBT(nbttagcompound);
        return new S35PacketUpdateTileEntity(this.pos, 0, nbttagcompound);
    }
    
    public void onDataPacket(NetworkManager net, S35PacketUpdateTileEntity pkt) {
        this.readFromNBT(pkt.getNbtCompound());
        this.worldObj.markBlockRangeForRenderUpdate(this.pos, this.pos);
    }
    

    La première permet de mettre la valeur dans le paquet, la deuxième lit dans le paquet la valeur à l'arrivée.

    Rendons nous ensuite dans la classe de notre bloc

    public void onBlockPlacedBy(World worldIn, BlockPos pos, IBlockState state, EntityLivingBase placer, ItemStack stack) {
        TileEntity tile = worldIn.getTileEntity(pos);
        if (tile instanceof TileEntityTutoriel) {
            int direction = MathHelper.floor_double((double) (placer.rotationYaw * 4.0F / 360.0F) + 2.5D) & 3;
            ((TileEntityTutoriel) tile).setDirection((byte) direction);
        }
    }
    

    On obtient l'entité de bloc, on vérifie qu'elle est bien d'instance TileEntityTutoriel et si c'est le cas on calcule la direction.
    Cette méthode nous permet de poser le bloc dans la direction opposée au regard du joueur (donc la face de notre meuble sera vers nous) et ainsi d'avoir les rotations dans les axes Nord, Sud, Est, Ouest. Puis on met cette valeur dans l'entité de bloc.

    Ensuite on quitte la classe de notre bloc pour se rendre dans la classe du rendu donc TileEntityTutorielSpecialRenderer et dans la fonction renderTileEntityTutorielAt()

    juste avant :

        this.bindTexture(texture);
        this.modelBlock.renderAll();
    

    ajoutez

        GlStateManager.rotate(90F * tileEntity.getDirection(), 0.0F, 1.0F, 0.0F);
    

    Cela va effectuer une rotation de 90 degrés multipliée par la direction sur l'axe Y.

    Et voilà votre bloc va pouvoir pivoter dans les axes Nord, Sud, Est et Ouest !

    Afficher le rendu des dégâts sur le bloc :

    Nous allons nous rendre dans la classe de notre TileEntity et y ajouter un boolean pour permettre un rendu des dégâts sur le bloc
    Car avec un rendu TESR le fait d'afficher les dégâts sur le bloc, comme n'importe quel bloc ne fonctionne pas de base.

    public boolean canRenderBreaking() {
        return true;
    }
    

    Et c'est tout pour la classe TileEntity allons ensuite dans la classe TESR (TileEntitySpecialRenderer) donc ici TileEntityTutorielSpecialRenderer
    Dans la fonction renderTileEntityTutorielAt()

    remplacez:

        GlStateManager.pushMatrix();
        GlStateManager.translate(posX + 0.5F, posY + 1.5F, posZ + 0.5F);
        GlStateManager.rotate(180F, 1.0F, 0.0F, 0.0F);
        //Utilisé si vous avez suivi le précédent bonus
        GlStateManager.rotate(90F * tileEntity.getDirection(), 0.0F, 1.0F, 0.0F);
        this.bindTexture(texture);
        this.modelBlock.renderAll();
        GlStateManager.popMatrix();
    

    par ceci:

        //On check si le joueur tape le bloc et si les dégâts sont supérieurs ou égaux à 0
        if (damageCount >= 0) {
            //On affiche les textures des dégâts sur le bloc
            this.bindTexture(DESTROY_STAGES[damageCount]);
            GlStateManager.matrixMode(5890);
            GlStateManager.pushMatrix();
            //On modifie la taille des textures pour les adapter sur le bloc
            GlStateManager.scale(4.0F, 4.0F, 1.0F);
            //On modifie la position X, Y, Z des textures pour les adapter sur le bloc
            GlStateManager.translate(0.0625F, 0.0625F, 0.0625F);
            GlStateManager.matrixMode(5888);
        }
        //Si les dégâts ne sont pas > ou = à 0 on met la texture de notre modèle
        else {
            this.bindTexture(texture);
        }
    
        GlStateManager.pushMatrix();
        GlStateManager.enableRescaleNormal();
        GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
        GlStateManager.translate(posX + 0.5F, posY + 1.5F, posZ + 0.5F);
        GlStateManager.scale(1.0F, -1.0F, -1.0F);
    
        //Utilisé si vous avez suivi le précédent bonus
        GlStateManager.rotate(90F * tileEntity.getDirection(), 0.0F, 1.0F, 0.0F);
    
        //On affiche le rendu de notre modèle 3D
        this.modelBlock.renderAll();
        GlStateManager.disableRescaleNormal();
        GlStateManager.popMatrix();
        GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
    
        //On ferme notre matrice de rendu pour les dégâts
        if (damageCount >= 0) {
            GlStateManager.matrixMode(5890);
            GlStateManager.popMatrix();
            GlStateManager.matrixMode(5888);
        }
    

    Et voilà le travail ! Quand vous détruirez votre bloc vous aurez une belle animation de ces dégâts ! En plus de la rotation du bloc !

    Résultat

    Rendu dans l'inventaire

    Rendu dans le monde (entité, item frame, dans la main)

    Rendu avec les orientations

    Rendu des dégâts sur le bloc

    Crédits

    Rédaction :

    • ZeAmateis

    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



  • J'ai un petit problème en 1.11.2, je ne sais pas pourquoi mais le rendu dans le monde est très noir comparé à la véritable texture 😕 De plus j'ai l'impression que les jsons ne fonctionne, sauriez vous m'aider pour la 1.11.2 ?

    Voici dans l'inventaire et dans la main alors que mon json pour l'item block

    Code :

    {
      "parent": "builtin/entity",
      "display": {
          "thirdperson": {
               "rotation": [ 10, -45, 170 ],
               "translation": [ 0, 1.5, -2.75 ],
               "scale": [ 0.375, 0.375, 0.375 ]
          }
      }
    }
    

    le json du block :

    {
      "parent": "builtin/entity",
      "textures": {
          "particle": "economy:blocks/block_atm"
      }
    }
    

    et mon blockstate

       "variants": {
           "normal": {
               "model": "economy:block_atm" }
           }
       }
    

    Pour ce qui est du rendu dans le monde je ne sais pas trop /:

    Des idées ?