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.
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); |
| |
| 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é
| |
| |
| |
| |
| public int getRenderType() { |
| return -1; |
| } |
| |
| |
| |
| |
| |
| 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
| |
| |
| |
| |
| public void breakBlock(World worldIn, BlockPos pos, IBlockState state) { |
| super.breakBlock(worldIn, pos, state); |
| worldIn.removeTileEntity(pos); |
| } |
| |
| |
| |
| |
| 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); |
| |
| this.setBlockBounds(0.0625F, 0.0F, 0.0625F, 0.9375F, 0.875F, 0.9375F); |
| } |
| |
| |
| |
| |
| |
| public int getRenderType() { |
| return -1; |
| } |
| |
| |
| |
| |
| |
| public boolean isOpaqueCube() { |
| return false; |
| } |
| |
| |
| |
| |
| @Override |
| public TileEntity createNewTileEntity(World worldIn, int meta) { |
| return new TileEntityTutoriel(); |
| } |
| |
| |
| |
| |
| |
| public void breakBlock(World worldIn, BlockPos pos, IBlockState state) { |
| super.breakBlock(worldIn, pos, state); |
| worldIn.removeTileEntity(pos); |
| } |
| |
| |
| |
| |
| 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); |
| } |
| } |
:::
TileEntityTutoriel:
Pour la classe de notre TileEntity rien de plus simple, créez la et étendez la de la classe TileEntity
| import net.minecraft.tileentity.TileEntity; |
| |
| public class TileEntityTutoriel extends TileEntity { |
| |
| } |
ModelTutoriel :
Pour le modèle 3D de notre bloc je vais utiliser celui du “Pré-requis”, mais c’est exactement la même chose qu’avec vos modèles custom à une petit différence…
remplacez:
public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5)
par
public void renderAll()
dans les lignes
morceauDeModele.render(f5)
remplacez f5 par 0.0625F
:::
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
| |
| GlStateManager.pushMatrix(); |
| |
| GlStateManager.translate(posX + 0.5F, posY + 1.5F, posZ + 0.5F); |
| |
| GlStateManager.rotate(180F, 1.0F, 0.0F, 0.0F); |
| |
| this.bindTexture(texture); |
| |
| modelBlock.renderAll(); |
| |
| 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.
Vous pouvez translater sur 3 en même temps dans la même fonction.*
- GL11.glRotatef(90F, 1.0F, 0.0F, 0.0F); effectue une rotation de 90° sur l’axe x.
- GL11.glRotatef(90F, 0.0F, 1.0F, 0.0F); effectue une rotation de 90° sur l’axe y.
- GL11.glRotatef(90F, 0.0F, 0.0F, 1.0F); effectue une rotation de 90° sur l’axe z.
Tous ces codes sont à mettre avant “model.renderAll();”
Lancez le jeu en debug (cliquez sur le scarabée à côté de la flèche pour lancer normalement) cela vous permettra de voir les changements en direct (pas besoin de relancer le jeu à chaque fois)
Votre classe devrait ressembler à ça au final:
:::
| import fr.minecraftforgefrance.tutoriel.client.model.ModelBlockTutoriel; |
| import fr.minecraftforgefrance.tutoriel.common.core.TutorielCore; |
| import fr.minecraftforgefrance.tutoriel.common.tileEntity.TileEntityTutoriel; |
| import net.minecraft.client.renderer.GlStateManager; |
| import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer; |
| import net.minecraft.tileentity.TileEntity; |
| import net.minecraft.util.ResourceLocation; |
| |
| public class TileEntityTutorielSpecialRenderer extends TileEntitySpecialRenderer { |
| |
| private static ModelBlockTutoriel modelBlock = new ModelBlockTutoriel(); |
| public static ResourceLocation texture = new ResourceLocation(TutorielCore.MODID, "textures/models/blocks/model_block_tutoriel.png"); |
| |
| public void renderTileEntityTutorielAt(TileEntityTutoriel tileEntity, double posX, double posY, double posZ, float partialTicks, int damageCount) { |
| GlStateManager.pushMatrix(); |
| GlStateManager.translate(posX + 0.5F, posY + 1.5F, posZ + 0.5F); |
| GlStateManager.rotate(180F, 1.0F, 0.0F, 0.0F); |
| GlStateManager.rotate(90F * tileEntity.getDirection(), 0.0F, 1.0F, 0.0F); |
| this.bindTexture(texture); |
| modelBlock.renderAll(); |
| GlStateManager.popMatrix(); |
| } |
| |
| @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); |
| } |
| } |
:::
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:
:::
| @EventHandler |
| public void init(FMLInitializationEvent event) { |
| proxy.init(); |
| |
| GameRegistry.registerTileEntity(TileEntityTutoriel.class, "tileEntityTutoriel"); |
| ClientRegistry.bindTileEntitySpecialRenderer(TileEntityTutoriel.class, new TileEntityTutorielSpecialRenderer()); |
| |
| |
| proxy.registerBlockTexture(this.blockTutoriel, "blockTutoriel"); |
| } |
:::
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
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.
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); |
| |
| GlStateManager.rotate(90F * tileEntity.getDirection(), 0.0F, 1.0F, 0.0F); |
| this.bindTexture(texture); |
| this.modelBlock.renderAll(); |
| GlStateManager.popMatrix(); |
par ceci:
| |
| if (damageCount >= 0) { |
| |
| this.bindTexture(DESTROY_STAGES[damageCount]); |
| GlStateManager.matrixMode(5890); |
| GlStateManager.pushMatrix(); |
| |
| GlStateManager.scale(4.0F, 4.0F, 1.0F); |
| |
| GlStateManager.translate(0.0625F, 0.0625F, 0.0625F); |
| GlStateManager.matrixMode(5888); |
| } |
| |
| 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); |
| |
| |
| GlStateManager.rotate(90F * tileEntity.getDirection(), 0.0F, 1.0F, 0.0F); |
| |
| |
| this.modelBlock.renderAll(); |
| GlStateManager.disableRescaleNormal(); |
| GlStateManager.popMatrix(); |
| GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); |
| |
| |
| 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 !
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

Rédaction :
Correction :

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
Retour vers le sommaire des tutoriels