Introduction
Qu’est-ce qu’un rendu TESR ?
TESR signifie TileEntitySpecialRenderer, c’est ce que nous allons utiliser pour notre rendu dans le monde. Pour l’inventaire, nous devront passer par un rendu ISBRH.
Un rendu TESR est plus complexe et va passer par un TileEntity
Les avantages du TESR :
- Permet de faire des rendus animé (exemple : le portal de l’end, les réservoirs et la raffinerie de buildcraft, etc …)
- Simple à utiliser à l’aide d’un modèle techne.
- Interaction facile avec le TileEntity associer.
Les désavantages du TESR :
- Le rendu est mit à jour à chaque tick, il est donc lourd, il en faut pas en abuser.
- Nécessite un TileEntity
- Beaucoup de classe, plus encore comme nous devrons utiliser un rendu ISBRH pour l’inventaire (le TESR ne fait que le rendu sur le monde)
Ce rendu est donc très intéressant pour faire quelque chose en fonction de votre tileEntity. Si votre bloc n’a pas de TileEntity, le rendu via ISBRH est plus intéressant, sauf si vous voulez utiliser un modèle techne.
Prérequis
- Un bloc basique
- Les entités de bloc (TileEntity)
- Direction de bloc (uniquement si vous souhaitez faire un rendu avec des directions)
- Un modèle techne (ne suivez que la partie “Création du modèle”)
Adapter le modèle techne
Une fois votre modèle exporté en java, il faut modifier quelques trucs dedans. Placez la classe du modèle dans votre package client puis changez la déclaration de package. Ensuite faite ctrl + shift + o pour mettre à jour les importations (prenez toujours net.minecraft.quelque chose)
Tout à la fin de la classe, supprimez la fonction setRotationAngles.
Ensuite modifier la méthode render, supprimer tous les paramètres sauf le dernier, nommé-le juste f. Modifier ensuite le paramètre des Shape.render en f. Exemple :
public void render(float f)
{
Shape1.render(f);
Shape2.render(f);
Shape3.render(f);
Shape4.render(f);
Shape5.render(f);
Shape6.render(f);
}
La classe du bloc
Si vous avez bien suivi les prérequis, votre classe devrait ressembler à ça :
package tutoriel.common;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
public class BlockSculpture extends Block
{
public BlockSculpture(int id)
{
super(id, Material.rock);
}
public TileEntity createTileEntity(World world, int metadata)
{
return new TileEntitySculpture();
}
public boolean hasTileEntity(int metadata)
{
return true;
}
}
Ajoutez ces trois fonctions :
public boolean renderAsNormalBlock()
{
return false;
}
public boolean isOpaqueCube()
{
return false;
}
@SideOnly(Side.CLIENT)
public int getRenderType()
{
return TutoClientProxy.renderInventoryTESRId;
}
La première fonction dit que notre bloc n’est pas un cube de 1x1. La deuxième indique de notre bloc n’est pas opaque (pour les blocs ayant une textures transparente ou un rendu ne faisant pas 1x1) et la dernière unique l’id du rendu pour le rendu en main.
ClientProxy + classe principale + interface
Dans votre proxy client, déclarez l’int renderInventoryTESRId :
public static int renderInventoryTESRId;
Dans la méthode registerRender() ajoutez :
renderInventoryTESRId = RenderingRegistry.getNextAvailableRenderId();
RenderingRegistry.registerBlockHandler(new TESRInventoryRenderer());
Créez la classe TESRInventoryRenderer, nous allons nous en occuper après.
Toujours dans le ClientProxy, créez une nouvelle méthode registerTileEntityRender :
@Override
public void registerTileEntityRender()
{
}
N’oubliez pas d’ajouter cette même méthode sans le @Override dans le CommonProxy.
Allez dans votre classe principale, dans la méthode init, après proxy.registerRender(); ajoutez :
proxy.registerTileEntityRender();
N’oubliez pas d’enregistrer votre TileEntity si ce n’est pas déjà fait :
GameRegistry.registerTileEntity(TileEntitySculpture.class, "Sculpture");
Retournez dans votre ClientProxy, et dans la méthode registerTileEntityRender que nous venons de créer ajoutez :
ClientRegistry.bindTileEntitySpecialRenderer(TileEntitySculpture.class, new TileEntitySculptureSpecialRender());
Vous pouvez déjà créez la classe TileEntitySculptureSpecialRender dans votre package client.
Dans votre package client, créez une nouvelle interface IInventoryRenderer :
package tutoriel.client;
public interface IInventoryRenderer
{
public void renderInventory(double x, double y, double z);
}
Chaque TESR devra être implémenté de cette interface.
La classe du rendu de tileEntity
Cette classe doit hériter de TileEntitySpecialRenderer pour le rendu sur le monde et être implémenté de IInventoryRenderer pour le rendu en main.
public class TileEntitySculptureSpecialRender extends TileEntitySpecialRenderer implements IInventoryRenderer
Ajoutez un constructeur à la classe, faite un petit ctrl + shift + o et un “add unimplemented methods” et on a :
package tutoriel.client;
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
import net.minecraft.tileentity.TileEntity;
public class TileEntitySculptureSpecialRender extends TileEntitySpecialRenderer implements IInventoryRenderer
{
public TileEntitySculptureSpecialRender()
{
}
@Override
public void renderInventory(double x, double y, double z)
{
}
@Override
public void renderTileEntityAt(TileEntity te, double x, double y, double z, float tick)
{
}
}
Ajoutez une nouvelle méthode :
public void renderTileEntitySculptureAt(TileEntitySculpture te, double x, double y, double z, float tick)
{
}
TileEntitySculpture est le nom du TileEntity que je veux rendre.
Dans la méthode renderTileEntityAt ajoutez : “this.renderTileEntitySculptureAt((TileEntitySculpture)te, x, y, z, tick);” et dans renderInventory ajoutez : “this.renderTileEntitySculptureAt(null, x, y, z, 0.0F);”. Cela donne :
@Override
public void renderInventory(double x, double y, double z)
{
this.renderTileEntitySculptureAt(null, x, y, z, 0.0F);
}
@Override
public void renderTileEntityAt(TileEntity te, double x, double y, double z, float tick)
{
this.renderTileEntitySculptureAt((TileEntitySculpture)te, x, y, z, tick);
}
Au dessus du constructeur déclarez un field d’objet ResourceLocation en publique, statique et final, et un d’objet de votre modèle en privé et finale et ajoutez dans le constructeur la fonction “this.setTileEntityRenderer(TileEntityRenderer.instance);”. On obtient :
private final ModelTutorial model = new ModelTutorial();
public static final ResourceLocation textureLocation = new ResourceLocation("modtutoriel", "textures/blocks/modelTutoriel.png");
public TileEntitySculptureSpecialRender()
{
this.setTileEntityRenderer(TileEntityRenderer.instance);
}
Explication : On instancie notre modèle, on déclare notre ResourceLocation (la texture sera dans assets/modtutoriel/textures/blocks/modelTutoriel.png) puis dans le constructeur on initialise le TileEntityRenderer. Très important sinon vous allez avoir des NPE.
Maintenant dans la fonction renderTileEntitySculptureAt.
GL11.glPushMatrix();
GL11.glTranslated(x + 0.5F, y + 1.5F, z + 0.5F);
this.bindTexture(textureLocation);
GL11.glRotatef(180F, 0.0F, 0.0F, 1.0F);
this.model.render(0.0625F);
GL11.glPopMatrix();
Lancez votre jeu en debug (le scarabée à gauche de run) pour tester le rendu sur le monde (il ne se fait pas encore dans l’inventaire), si vous avez fait votre modèle sur la planche en bois sur techne, le modèle devrait s’afficher correctement, si ce n’est pas le cas, modifiez la valeur y + 1.5F dans “GL11.glTranslated(x + 0.5F, y + 1.5F, z + 0.5F);” (le changement sera fait en directe lorsque vous sauvegarderez, c’est l’intérêt du debug).
Le rendu en main
Notre ISBRH est encore vide, nous allons le compléter. Le code avec interface que j’ai fait (en me basant sur celui de buildcraft) permet d’avoir un seul ISBRH pour tout vos rendus TESR.
Cela veut dire que si vous souhaitez ajouter un deuxième, puis un troisième, etc. rendu via TileEntitySpecialRenderer, il vous suffira de remettre le même id de rendu que pour les autres, et utiliser le même ISBRH. Si c’est pas clair pour vous, allez dans la parti bonus, et suivez les instructions de “ajouter plusieurs rendus via TileEntitySpecialRenderer”.
Donc dans la classe TESRInventoryRenderer, changez
@Override
public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer)
{
return false;
}
@Override
public boolean shouldRender3DInInventory()
{
return false;
}
@Override
public int getRenderId()
{
return 0;
}
en :
@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 true;
}
@Override
public int getRenderId()
{
return TutoClientProxy.renderInventoryTESRId;
}
En haut de la classe, ajoutez :
public static class TESRIndex
{
Block block;
int metadata;
public TESRIndex(Block block, int metadata)
{
this.block = block;
this.metadata = metadata;
}
@Override
public int hashCode()
{
return block.hashCode() + metadata;
}
@Override
public boolean equals(Object o)
{
if(!(o instanceof TESRIndex))
return false;
TESRIndex tesr = (TESRIndex)o;
return tesr.block == block && tesr.metadata == metadata;
}
}
public static HashMap<TesrIndex, IInventoryRenderer> blockByTESR = new HashMap<TesrIndex, IInventoryRenderer>();
TESRIndex est une classe qui va servir pour nos rendus dans l’inventaire / en main. Dans son constructeur elle prend l’objet du bloc et son metadata.
Ensuite dans la HashMap nous allons enregistrer nos rendus d’inventaire. Dans la fonction renderInventoryBlock, ajoutez :
TESRIndex index = new TESRIndex(block, metadata);
if(blockByTESR.containsKey(index))
{
blockByTESR.get(index).renderInventory(-0.5, -0.5, -0.5);
}
A chaque fois qu’un rendu dans l’inventaire d’id renderInventoryTESRId est initialisé, on créé un nouveau TESRIndex, et si ce TESRIndex existe dans la HashMap blockByTESR, on fait le rendu.
Finitions + rendu final
À nouveau dans votre ClientProxy, dans la méthode registerTileEntityRender() ajoutez :
TESRInventoryRenderer.blockByTESR.put(new TESRIndex(ModTutoriel.blockSculpture, 0), new TileEntitySculptureSpecialRender());
ModTutoriel.blockSculpture est l’objet de mon bloc et 0 le metadata.
Lance votre jeu, le rendu en main / dans l’inventaire devrait se faire.
(oui, modèle fait à l’arrache)
Bonus / Pour aller plus loin
Les particules :
Vous avez surement remarqué que les particules sont noirs et rose, comme avec un missing texture. Pour résoudre ce problème, il suffit d’attribuer un icône a votre bloc avec la méthode setTextureName que nous avons vu dans un bloc basique. Pour un bloc déjà existant, vous pouvez utiliser la fonction getIcon dans la classe de votre bloc. Par exemple avec :
public Icon getIcon(int side, int metadata)
{
return Block.blockIron.getIcon(0, 0);
}
mon bloc aura les particules du bloc de fer.
Ajoutez plusieurs rendus via TileEntitySpecialRenderer
Si vous lisez cette partie, c’est que vous n’avez pas compris mes explications plus haut. En fait il vous suffit de un autre bloc et une autre entité de bloc et une autre TileEntitySpecialRenderer. (ou alors juste une entité de bloc sur metadata avec une TileEntitySpecialRenderer).
Si vous faite un deuxième bloc, dans la fonction
@SideOnly(Side.CLIENT)
public int getRenderType()
{
return TutoClientProxy.renderInventoryTESRId;
}
Remettez la même chose, ne recréer pas un id de rendu. Comme je l’ai expliqué plus haut, on utilise un seul ISimpleBlockRenderingHandler (nommé TESRInventoryRenderer) pour tout nos rendus en main de TileEntitySpecialRenderer.
N’oubliez pas les fonctions dans le proxy client.
Par exemple, si j’ai 3 TESR sur un bloc en metadata et un quatrième sur un autre bloc, je vais avoir :
- 2 blocs
- 4 TileEntity
- 4 TileEntitySpecialRenderer
- 1 seul TileEntitySpecialRenderer
et mon client proxy ressemblera à ça :
public static int renderInventoryTESRId;
public void registerRender()
{
renderInventoryTESRId = RenderingRegistry.getNextAvailableRenderId();
RenderingRegistry.registerBlockHandler(new TESRInventoryRenderer());
}
@Override
public void registerTileEntityRender()
{
ClientRegistry.bindTileEntitySpecialRenderer(TileEntitySculpture.class, new TileEntitySculptureSpecialRender());
TESRInventoryRenderer.blockByTESR.put(new TESRIndex(ModTutoriel.blockSculpture, 0), new TileEntitySculptureSpecialRender());
ClientRegistry.bindTileEntitySpecialRenderer(TileEntitySculpture2.class, new TileEntitySculptureSpecialRender2());
TESRInventoryRenderer.blockByTESR.put(new TESRIndex(ModTutoriel.blockSculpture, 1), new TileEntitySculptureSpecialRender2());
ClientRegistry.bindTileEntitySpecialRenderer(TileEntitySculpture3.class, new TileEntitySculptureSpecialRender3());
TESRInventoryRenderer.blockByTESR.put(new TESRIndex(ModTutoriel.blockSculpture, 2), new TileEntitySculptureSpecialRender3());
ClientRegistry.bindTileEntitySpecialRenderer(TileEntityTuto.class, new TileEntityTutoSpecialRender());
TESRInventoryRenderer.blockByTESR.put(new TESRIndex(ModTutoriel.blockTuto, 0), new TileEntityTutoSpecialRender());
}
Voila, j’espère que c’est plus clair comme ça
Sinon, il y a un exemple ici
Tourner votre modèle en fonction de la direction :
Comme nous avons besoin d’un tileEntity, autant sauvegarder la direction dedans. Suivez donc à la lettre la partie “Sur un bloc avec metadata” du tutoriel Direction de bloc. Ignorez juste la méthode getBlockTexture(IBlockAccess blockaccess, int x, int y, int z, int side), elle ne servira à rien comme la texture est sur le TESR.
Ensuite allez dans votre TileEntitySpecialRenderer, et dans la méthode renderTileEntityQuelqueChoseAt, ajoutez avant " this.model.render(0.0625F);" :
if(te != null)
{
GL11.glRotatef(90F * te.getDirection(), 0.0F, 1.0F, 0.0F);
}
Ce qui donne au final :
public void renderTileEntitySculptureAt(TileEntitySculpture te, double x, double y, double z, float tick)
{
GL11.glPushMatrix();
GL11.glTranslated(x + 0.5F, y + 1.5F, z + 0.5F);
this.bindTexture(textureLocation);
GL11.glRotatef(180F, 0.0F, 0.0F, 1.0F);
if(te != null)
{
GL11.glRotatef(90F * te.getDirection(), 0.0F, 1.0F, 0.0F);
}
this.model.render(0.0625F);
GL11.glPopMatrix();
}
Et voila, maintenant en fonction de la direction du joueur lors du placement, le rendu va tourner !