Rendu complexe de bloc via ISBRH



  • Introduction

    Bonjour tout le monde ! Ne vous vous êtes jamais demandé comment, par exemple, les escaliers était codé ? Et non, pas de modèle, simplement un rendu.

    Qu'est-ce qu'un rendu ISBRH ?

    ISBRH signifie ISimpleBlockRenderingHandler, c'est l'interface que nous allons utiliser pour notre rendu dans l'inventaire et dans le monde

    Un rendu simple, comme son nom l'indique, est comme un rendu complexe avec modèle, mais en plus simple (pas de transformation du bloc en entité pour l'avoir). Nous allons voir dans ce tutoriel comment l'utiliser.

    Les avantages du ISBRH :

    • Il n'utilise pas de TileEntity, il est donc plus léger.
    • Il n'est que chargé une fois, il l'est pas mit à jour à chaque tick contrairement au TESR, c'est une autre chose qui le rend plus léger
    • Il y a juste la classe de votre rendu, celle de votre bloc, et quelques codes dans le proxy, il est donc aussi plus léger niveau code.

    Les désavantages du ISBRH :

    • Il ne permet de rendre un modèle techne, si vous le faite ça va crasher (sur le monde uniquement, en main c'est possible).
    • Compliqué à utiliser au début, mais on s'y habitue vite, et avec le debug ça va tout seul.

    Pre-requis

    Appliquer le rendu au bloc

    A. Rencontre des fonctions indispensables

    Normalement, vous devriez savoir comment faire un bloc simple, donc nous allons directement commencer dans la classe de votre bloc.
    Il faudra ajouter ces fonctions :

        public boolean renderAsNormalBlock()
        {
            return false;
        }
    

    Retourne faux si le bloc n'est pas normal sachant qu'un bloc normal est un cube.

        public boolean isOpaqueCube()
        {
            return false;
        }
    

    Retourne faux si le bloc est transparent sachant que opaque veux dire que le bloc prend toutes la surface et qu'il a une texture non-transparente.

        @SideOnly(Side.CLIENT)
        public int getRenderType()
        {
            return TutoClientProxy.renderTableId;
        }
    

    Retourne un int associant l'id de notre rendu au bloc.
    Vous remarquerez cependant une erreur à la déclaration de "renderTableId", c'est tout simplement parce que nous l'avons pas encore déclaré 😉
    Cette fonction charge seulement côté client car les rendus se font seulement côté client.

        @SideOnly(Side.CLIENT)
        public boolean shouldSideBeRendered(IBlockAccess blockAccess, int x, int y, int z, int side)
        {
            return true;
        }
    

    Et enfin cette fonction !
    Elle sert à… afficher les côtés du rendu ! 😄

    B. Nouvelle variable

    Maintenant, ajoutons un nouvelle variable dans le ClientProxy :

        public static int renderTableId;
    

    Cette variable correspondra à notre rendu.
    Instancions-là dans la fonction "registerRender" ou comme vous l'avez appelé :

            renderTableId = RenderingRegistry.getNextAvailableRenderId();
    

    getNextAvailableRenderId() est une fonction qui retourne le prochain id libre des rendus.
    On a plus qu'à enregistrer la classe de notre rendu :

            RenderingRegistry.registerBlockHandler(renderTableId, new RenderTable());
    

    renderTableId correspond à la variable créé précédemment et "new RenderTable()" à la classe que nous allons créer tout de suite !
    Vous pouvez aussi utiliser :

            RenderingRegistry.registerBlockHandler(new RenderTable());
    

    Dans ce cas l'id sera prit dans la fonction getRenderId() de la classe.

    #rendu-dans-le-monde(Rendu dans le monde)
    Le rendu dans le monde ? C'est le rendu qui se "rend" seulement dans le World et non dans l'inventaire/la main.

    Voilà à quoi devrait ressembler votre classe RenderCustomBlocks :

    public class RenderTable implements ISimpleBlockRenderingHandler
    {
        @Override
        public void renderInventoryBlock(Block block, int metadata, int modelID, RenderBlocks renderer)
        {
        }
    
        @Override
        public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer)
        {
            return true;
        }
    
        @Override
        public boolean shouldRender3DInInventory()
        {
            return false;
        }
    
        @Override
        public int getRenderId()
        {
            return 0;
        }
    }
    

    Remplace le 0 par l'id de votre rendu :

        @Override
        public int getRenderId()
        {
            return TutoClientProxy.renderTableId;
        }
    

    (très important si vous avez utilisez la deuxième méthode d'enregistrement, sinon ça n'a pas d'importance).

    La fonction renderWorldBlock servira à modeler vous bloc en faisant de petits morceaux :

            renderer.setRenderBounds(minX, minY, minZ, maxX, maxY, maxZ);
    

    minX : ce float déclare le minimum en X.
    minY : celui-ci déclare le minimum en Y.
    minZ : celui-là déclare le minimum en Z.
    maxX : celui-ci déclare le maximumen X.
    maxY : celui-là déclare le maximumen Y.
    maxZ : celui-ci déclare le maximum en Z.

    Exemple pour faire un cube normal ajoutez dans cette fonction avant le return :

            renderer.setRenderBounds(0F, 0F, 0F, 1F, 1F, 1F);
    

    Pour sauvegarder le "morceau", la sauvegarde ce fera comme ça à chaque déclaration de nouveaux morceaux :

            renderer.renderStandardBlock(block, x, y, z);
    

    Voici un petite exemple de table :

            renderer.setRenderBounds(0.2F, 0.0F, 0.2F, 0.8F, 0.1F, 0.8F);
            renderer.renderStandardBlock(block, x, y, z);
            renderer.setRenderBounds(0.45F, 0.1F, 0.45F, 0.55F, 0.8F, 0.55F);
            renderer.renderStandardBlock(block, x, y, z);
            renderer.setRenderBounds(0.0F, 0.8F, 0.0F, 1F, 0.9F, 1F);
            renderer.renderStandardBlock(block, x, y, z);
    

    Voilà !

    Table moddé dans Minecraft

    Il est également possible de mettre tout vos rendus dans la même classe, et d'utiliser des conditions if(modelId == TutoClientProxy.renderTableId) mais je vous conseil de créer une classe par rendu pour une question d'organisation. (surtout quand on a des rendus très complet de plusieurs lignes).

    #rendu-dans-l-inventaire(Rendu dans l'inventaire)
    Voici la dernière partie de ce tuto, le rendu dans l'inventaire et dans la main (donc le rendu en temps que ItemStack).
    Comme vous avez pu le voir dans le screen précédent, le rendu de la table dans l'inventaire n'y est pas.
    Nous allons régler ce souci. Le modelage des morceaux se fait de la même façon mais :

            renderer.renderStandardBlock(block, x, y, z);
    

    devient :


    Voilà !
    Pour ne pas répéter plusieurs fois ce même pavé, je vous conseille de créer une fonction renderInInventory :

        private void renderInInventory(Tessellator tessellator, RenderBlocks renderer, Block block, int metadata)
        {
            GL11.glTranslatef(-0.5F, -0.5F, -0.5F);
            tessellator.startDrawingQuads();
            tessellator.setNormal(0.0F, -1F, 0.0F);
            renderer.renderFaceYNeg(block, 0.0D, 0.0D, 0.0D, block.getIcon(0, metadata));
            tessellator.draw();
            tessellator.startDrawingQuads();
            tessellator.setNormal(0.0F, 1.0F, 0.0F);
            renderer.renderFaceYPos(block, 0.0D, 0.0D, 0.0D, block.getIcon(1, metadata));
            tessellator.draw();
            tessellator.startDrawingQuads();
            tessellator.setNormal(0.0F, 0.0F, -1F);
            renderer.renderFaceZNeg(block, 0.0D, 0.0D, 0.0D, block.getIcon(2, metadata));
            tessellator.draw();
            tessellator.startDrawingQuads();
            tessellator.setNormal(0.0F, 0.0F, 1.0F);
            renderer.renderFaceZPos(block, 0.0D, 0.0D, 0.0D, block.getIcon(3, metadata));
            tessellator.draw();
            tessellator.startDrawingQuads();
            tessellator.setNormal(-1F, 0.0F, 0.0F);
            renderer.renderFaceXNeg(block, 0.0D, 0.0D, 0.0D, block.getIcon(4, metadata));
            tessellator.draw();
            tessellator.startDrawingQuads();
            tessellator.setNormal(1.0F, 0.0F, 0.0F);
            renderer.renderFaceXPos(block, 0.0D, 0.0D, 0.0D, block.getIcon(5, metadata));
            tessellator.draw();
            GL11.glTranslatef(0.5F, 0.5F, 0.5F);
        }
    

    Ce qui nous donne donc :

        @Override
        public void renderInventoryBlock(Block block, int metadata, int modelID, RenderBlocks renderer)
        {
            Tessellator tessellator = Tessellator.instance;
            renderer.setRenderBounds(0.2F, 0.0F, 0.2F, 0.8F, 0.1F, 0.8F);
            this.renderInInventory(tessellator, renderer, block, metadata);
            renderer.setRenderBounds(0.45F, 0.1F, 0.45F, 0.55F, 0.8F, 0.55F);
            this.renderInInventory(tessellator, renderer, block, metadata);
            renderer.setRenderBounds(0.0F, 0.8F, 0.0F, 1F, 0.9F, 1F);
            this.renderInInventory(tessellator, renderer, block, metadata);
        }
    

    Une dernière chose, passe en vrai le rendu en 3D dans l'inventaire :

        @Override
        public boolean shouldRender3DInInventory()
        {
            return true;
        }
    

    Un dernier screen :
    Table dans l'inventaire dans Minecraft
    Et voilààààààà !!!!!
    Tutoriel terminé !
    J'espère qu'il vous aura plus et à bientôt !!! 😉

    Voir le commit sur github

    Questions/Réponses

    Rien pour l'instant !


  • Administrateurs

    Une question, on peux utiliser cette méthode de rendu pour la crop d'une plante similaire aux citrouilles ou aux melons?



  • Bonne chance pour ce tutoriel.

    @Superloup : Normalement, oui



  • OMG ! Merci ! Sa va me sortir d'une sacré m**** :D, justement pour une plante comme la citrouille.



  • Salut, ce serait bien de commencer ton tuto avec la maj la plus récente de Minecraft, en l’occurrence la 1.6.2. Fin je dis ça, je dis rien. Merci et bonne chance en tout cas 😉



  • Sauf que comme superloup10 ou moi même, on n'est toujours en 1.5.2 XD.
    Enfin je dit ça mais je dit rien ;).
    Et sinon vivement la suite :).


  • Administrateurs

    Dans le pire des cas je m'occuperai de faire le tutoriel en 1.6.2, mais j'ai d'autre priorité. De toute façon les blockbound ne change presque pas avec la version de Minecraft, ça sera très similaire voir pareil.



  • Je regarais le code de la 1.6.2 quand j'aurais envie de coder un 1.6.2 parce que je n'aime pas cette mise à jour, sinon merci !! 😄



  • Je suis devenue copain avec un creeper depuis pas longtemps la :D.



  • Vivement la suite !


  • Administrateurs

    Plus de nouvelle ?
    Le tutoriel a-t-il été abandonné ?



  • Oups, faut que je le finisse. Sur portable c'est pas très pratique :s désolé je vais le continuer ! 😄



  • Tutoriel achevé ! ;)___
    EDIT : oups double post 😞 Désolé


  • Administrateurs

    09-11-2013 19:26
    Les doubles sont autorisés si 24h est passé entre les deux, donc pas de problème.

    Je vais regarder demain en détail.


    25-11-2013 22:56
    Bon finalement j'ai été beaucoup occupé et je n'ai toujours pas regardé de prêt le tutoriel, désolé.
    Je vais faire ça pour mercredi au plus tard.


    27-11-2013 21:02
    Je vais faire quelques modifications, après vérification sur les sources de forge lui même (le rendu des liquides), il faudrait plutôt créer une classe par rendu, et la fonction getRenderId() doit return sur l'id du rendu. Ça marche aussi comme tu as fait, mais je préfère suivre ce que Forge et Buildcraft font.
    D'ailleurs ça évite tout les if(modelId == notreID), comme tu as une classe par rendu, et c'est beaucoup plus pratique pour les gros rendus



  • Ben je préfères n'en faire qu'un mais après c'est ton choix, au pire met les deux astuces ! 😄


  • Administrateurs

    Voila, j'ai modifier le tutoriel, refait l'introduction pour faire une comparaison avec le rendu via TESR et mit à jour les méthodes pour la 1.6.4.

    J'ai mit en avant le 1 rendu = 1 classe qui est plus organisé à mon gout (et que presque tout le monde utilise (Forge lui même dans les liquides, BuildCraft, etc …) tu as surement l'habitude d'avoir tout dans la même classe comme tu codais en vanilla non ?)



  • En effet, c'est comme si je codais sur RenderBlocks (plusieurs modèles dans la même classe)



  • Est-ce que pour les setRenderBounds on peut utiliser les addBox de Techne ?
    Sinon comment les convertir ?



  • j'ai un problème moi dans le Client proxy les getNextAvailableRenderId() et registerBlockHandler reste souligné en rouge. comment faire?


  • Administrateurs

    Envoie le code complet de ton ClientProxy.