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à !
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 :
:::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);
Vous ne vous attendiez pas à ça non ? xD
:::
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 :
Et voilààààààà !!!
Tutoriel terminé !
J’espère qu’il vous aura plus et à bientôt !!!Questions/Réponses
Rien pour l’instant !
-
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 :). -
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 !
-
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é -
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 !
-
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?
-
Envoie le code complet de ton ClientProxy.