1.8 Les BlockStates



  • Sommaire

    Introduction

    Depuis la version de minecraft 1.8 les blocs ont subi un petit changement, il s'agit des BlockStates. Les BlockStates permettent de définir les états du bloc par des properties (propriétés). À savoir, il existe plusieurs properties. Avec ces propriétés on pourra aussi faire ce qui était, avant, les metadata mais pas que. Avec les BlockStates on a des possibilités presque infinies, dans ce tutoriel je vous donnerai les bases qui vous permettront de réaliser autant de possibilités que vous voulez.

    Pré-requis

    Créer un bloc basique

    Code

    Avant de démarrer, il faut savoir qu'il existe trois principales catégories de properties (propriétés), les Enums, les Booleans, et les Integers.

    Les PropertiesEnum :

    La classe de l’énumération :
    Dans un premier temps, nous allons créer notre enum. Donc, dans la classe de votre bloc (ou une autre classe), créez une énumération statique et implémentez l'interface IStringSerializable comme ceci :

        public static enum EnumType implements IStringSerializable
        {
    
        }
    

    Pour mon cas, je l'appelle EnumType, vous pouvez l'appeler comme vous voulez, mais gardez un nom logique.

            private static final EnumType[] METADATA = new EnumType[values().length];
            private final String name;
            private final int metadata;
    

    Ajoutez trois variables: "METADATA" avec le modifieur "final", qui est utile pour les metadata. Le String ("name") vous permettra de récupérer le nom, tous comme "METADATA" permet d'obtenir les metadata. N'oubliez pas de les mettre en private.

            private EnumType(int metadata, String name)
            {
                this.metadata = metadata;
                this.name = name;
            }
    

    Le constructeur permettra de définir le metadata et le nom de nos énumérations.

            @Override
            public String getName()
            {
                return name;
            }
    
            public int getMetadata()
            {
                return metadata;
            }
    
            @Override
            public String toString()
            {
                return name;
            }
    

    Les fonctions sont simples à comprendre (getter), je ne pense pas avoir besoin de vous les expliquer.

            public static EnumType getStateFromMeta(int metadata)
            {
                if(metadata < 0 || metadata >= METADATA.length)
                {
                    metadata = 0;
                }
    
                return METADATA[metadata];
            }
    

    Cela permettra que si un metadata n'existe pas pour notre bloc, il donnera la metadata 0. Par exemple, si l'on tente de donner un metadata avec une valeur de -1, il donnera la metadata 0. Si notre bloc a quatre metadata et qu'on donne le bloc avec un metadata de 8, il recevra une metadata de 0. Enfin, ajoutez un bloc statique (allez ici si vous ne savez pas ce que c'est) :

            static
            {
                EnumType[] var0 = values();
                int var1 = var0.length;
    
                for(int var2 = 0; var2 < var1; var2++)
                {
                    EnumType var3 = var0[var2];
                    METADATA[var3.getMetadata()] = var3;
                }
            }
    

    Maintenant, il ne nous reste plus qu'à ajouter les valeurs :

    VARIANT_1(0, "variant_1"), VARIANT_2(1, "variant_2");
    

    Les valeurs se mettent avant tous les autres contenus de votre enum. Pour ce tuto je créerai deux variantes, nommez-les de manière logique. Par exemple, pour du bois donnez les noms de vos types de bois... Le premier argument correspond au metadata (commence à 0 et pas 1), le deuxième correspond au nom de la variante qui sera utilisé notamment dans le fichier lang. Vous pouvez créer autant de variantes que vous voulez, mais les metadata sont limités à 16 valeurs (0; 1; 2... 15). Nous en avons fini avec notre enum.

    La classe du bloc :

    public static final PropertyEnum VARIANT = PropertyEnum.create("variant", EnumType.class);
    

    Nous commençons par déclarer et initialiser la variable de notre property. Les properties devront avoir les mots-clés "final" et "static" (si vous ne savez pas ce que c'est, allez ici et/ou ici). Ensuite, pour l'initialiser vous devrez utiliser la fonction "create" de la classe de votre property (son contenu varie en fonction de la propriété). Le premier paramètre correspond au nom de votre propriété et le second à la classe de votre property. Cette classe peut être dans une autre classe ou être indépendante. La classe enum peut être utilisée pour plusieurs blocs (les bûches de bois, les feuilles, les planches, etc.).

        @Override
        public IBlockState getStateFromMeta(int meta)
        {
            return this.getDefaultState().withProperty(VARIANT, EnumType.getStateFromMeta(meta));
        }
    
        @Override
        public int getMetaFromState(IBlockState state)
        {
            return ((EnumType)state.getValue(VARIANT)).getMetadata();
        }
    

    La première permet de récupérer le BlockState à partir des metadata du bloc, la seconde permet d'obtenir les metadata à partir du BlockState.

        @Override
        protected BlockState createBlockState()
        {
            return new BlockState(this, new IProperty[] {VARIANT});
        }
    

    Cette fonction permet d'enregistrer nos properties, c'est important pour les utiliser dans les textures/models ou même les récupérer. Si vous ne la mettez pas votre jeu aura un crash. Continuons sur quelques fonctions supplémentaires (non obligatoires).

        @Override
        public int damageDropped(IBlockState state)
        {
            return ((EnumType)state.getValue(VARIANT)).getMetadata();
        }
    

    Cela permet de récupérer la metadata droppé.

        @Override
        public Item getItemDropped(IBlockState state, Random rand, int fortune)
        {
            if(state.getValue(VARIANT) == EnumType.VARIANT_1)
            {
                return Items.diamond;
            }
            else
            {
                return Item.getItemFromBlock(this);
            }
        }
    

    Cela est un simple exemple, cela permet de dropper un certain item si on casse la variant1 et sinon on drop le bloc. Ceci est important, car la condition pour identifier les states d'un bloc est la même (sauf dans certains cas).

        @SideOnly(Side.CLIENT)
        public void getSubBlocks(Item itemIn, CreativeTabs tab, List list)
        {
            list.add(new ItemStack(this, 1, EnumType.VARIANT_1.getMetadata()));
            list.add(new ItemStack(this, 1, EnumType.VARIANT_2.getMetadata()));
        }
    

    Cette fonction permettra d'ajouter nos items (blocs) dans les creatives tabs de manière à ne pas devoir se donner (/give) le bloc (avec la metadata voulue). Cela fonctionne facilement, on ajoute un ItemStack, avec en premier argument (this) notre bloc, en second le nombre et en troisième la metadata. Pour vos metadata vous pouvez faire comme ceci au lieu d'écrire le chiffre, cela est pratique car vous ne vous embrouillez pas.

        public String getUnlocalizedName(int metadata)
        {
            return super.getUnlocalizedName() + "." + EnumType.getStateFromMeta(metadata).getName();
        }
    

    Ceci nous permettra de récupérer le nom de notre bloc en fonction de sa metadata, elle sera aussi utilisée dans notre ItemBlock. Maintenant placez vous dans le constructeur et ajoutez :

    this.setDefaultState(getDefaultState().withProperty(VARIANT, EnumType.VARIANT_1));
    

    Ceci est obligatoire et permet de dire quelles sont les propriétés par défaut. Notez que quand vous voulez donner une propriété particulière il faudra passer par le même principe (avec le withProperty(IProperty, valeur)).Ainsi, pour mettre des harvest différents en fonction des variants :

            this.setHarvestLevel("pickaxe", 1, getDefaultState().withProperty(VARIANT, EnumType.VARIANT_1));
            this.setHarvestLevel("pickaxe", 2, getDefaultState().withProperty(VARIANT, EnumType.VARIANT_2));
    

    Pour notre variant 1 le bloc se cassera à la pioche avec une pioche en pierre au minimum, contrairement au 2 où il faudra une pioche en fer.

    Les PropertiesBool :

    Cette propriété de bloc est rarement utilisée seule, souvent on l'utilise avec d'autres propriétés. Nous verrons comment faire ceci plus tard dans le tutoriel. Les property bool sont différentes des énumérations, car vous devriez l'avoir compris ce sont des propriétés booléen elles auront deux valeurs possibles: vrai ou faux. Elles peuvent aussi avoir (selon moi) deux utilisations que nous allons découvrir de suite.
    La première classe :
    La première sera de définir une propriété, savoir s'il y a un bloc de ce type à côté, que le bloc voit le ciel... Cela fonctionne très facilement.

    public static final PropertyBool TUTO_BOOL = PropertyBool.create("tuto_bool");
    

    Comme pour la classe précédente, commençons par déclarer et initialiser notre variable de la property. Nous retrouvons la même structure que pour notre premièr bloc. La différence vient de la classe qui change et de la fonction create qui change au niveau du nombre d'argument, il n'y a plus que le nom de la propriété (vu qu'ensuite il n'y a que deux valeurs possibles).

        @Override
        protected BlockState createBlockState()
        {
            return new BlockState(this, new IProperty[] {TUTO_BOOL});
        }
    

    Normalement vous devriez la connaître, nous l'avons vu dans l'autre classe. Comparés à la première classe nous n'avons pas les fonctions pour récupérer la metadata et la state, en effet on ne l'utilisera pas pour créer différente metadata. Notez que ce que nous allons faire peut être fait avec tout type de property.

        @Override
        public IBlockState getActualState(IBlockState state, IBlockAccess world, BlockPos pos)
        {
            Block block = world.getBlockState(pos.down()).getBlock();
            boolean stone = Boolean.valueOf(block == Blocks.stone);
    
            return state.withProperty(TUTO_BOOL, stone);
        }
    

    Cette fonction permet de retourner le blockstate avec sa/ses propriété/s ainsi que leur valeur. Notez qu'avec cette fonction on utilise notre state de manière dynamique. Vous l'aurez compris, on utilise cela surtout pour les textures en fonction du décor. Par exemple avec les blocs de terre ou d'herbe quand il y a de la neige dessus. Pour notre cas, on crée une variable de bloc qui est le bloc en dessous du nôtre, dans le boolean on regarde si ce même bloc est de pierre, si oui cela retourne true sinon false (il est important de mettre le "Boolean.valueOf(...), enfin on retourne notre state avec pour propriété "TUTO_BOOL" auquel on met la valeur du boolean stone. Donc quand il y aura un bloc de pierre ou pas en dessous notre bloc changera de propriété. Vous pouvez tester à votre manière, changer, mettre d'autres blocs, mettre plusieurs blocs, changer l'emplacement du bloc... Ceci est un exemple. Enfin, dans le constructeur on va ajouter :

    this.setDefaultState(getDefaultState().withProperty(TUTO_BOOL, true);
    

    La seconde classe :
    Dans notre seconde classe, on utilisera les property d'une manière différente, on va utiliser le même type de property mais on va l'utiliser pour créer des metadatas. Notez que cela peut être très utile avec d'autres propriétés notamment les enums, je vous propose donc de voir comment s'organise cette classe ainsi que quelques exemples. Remarquez que cette manière est utilisée dans les droppers et les dispensers par exemple .

    public static final PropertyBool TUTO_BOOL = PropertyBool.create("tuto_bool");
    

    Comme toujours on crée notre propriété de la même manière que pour l'autre classe.

        @Override
        public IBlockState getStateFromMeta(int meta)
        {
            boolean tuto_bool = (meta & 8) > 0;
    
            return this.getDefaultState().withProperty(TUTO_BOOL, Boolean.valueOf(tuto_bool));
        }
    
        @Override
        public int getMetaFromState(IBlockState state)
        {
            boolean tuto_bool = (Boolean)state.getValue(TUTO_BOOL);
            return tuto_bool ? 0 | 8 : 0;
        }
    

    Dans la première fonction on retourne la valeur de notre propriété. Dans la seconde on retourne la valeur de notre metadata. Si vous avez été observateur, vous avez dû vous rendre compte que pour la première fonction on définit la valeur par : "(meta&8) > 0", Cela permet de retourner true si la valeur est 8 au dessus de la metadata et false si elle est de la metadata. Dans la seconde, vous voyez dans le retourne ceci : "tuto_bool ? 0 | 8 : 0", dans cette condition on peut voir que si la variable tuto_bool est en true alors on renvoie 8 au-dessus de 0 et sinon on renvoie 0. En soit vous l'auraez compris on pourrait noter: "tuto_bool ? 8 : 0", mais si nous faisons ça c'est si nous ajoutions d'autres propriétés. Je vous invite à jeter un coup d'œil dans la partie du tutoriel à ce sujet.

        @Override
        public int damageDropped(IBlockState state)
        {
            boolean tuto_bool = (Boolean)state.getValue(TUTO_BOOL);
            return tuto_bool ? 0 | 8 : 0;
        }
    

    Cette fonction retourne la metadata à dropper, cette fonction est comme le "getMetaFromState" juste au-dessus.

        @Override
        protected BlockState createBlockState()
        {
            return new BlockState(this, new IProperty[] {TUTO_BOOL});
        }
    

    Comme toujours, sans cela votre jeu aura un crash.

        @Override
        @SideOnly(Side.CLIENT)
        public void getSubBlocks(Item item, CreativeTabs tab, List list)
        {
            list.add(new ItemStack(this, 1, 0));
            list.add(new ItemStack(this, 1, 8));
        }
    

    Cette fonction est utile dans le cas où vous voulez voir toutes vos metadata dans les creatives tabs, comme nous l'avons vu au-dessus, pour notre cas, l'une aura la valeur 0 et l'autre 8. Dans votre constructeur n'oubliez pas d'ajouter :

    this.setDefaultState(getDefaultState().withProperty(TUTO_BOOL, false));
    

    Si vous voulez que chaque state ait un nom différent :

        public String getUnlocalizedName(int metadata)
        {
            IBlockState state = this.getStateFromMeta(metadata);
            boolean tutoBool = (Boolean)state.getValue(TUTO_BOOL);
            return super.getUnlocalizedName() + "." + tutoBool;
        }
    

    Donc, avec c'est deux state vous pouvez en faire ce que vous voulez, dire que true c'est un bloc de stone et false de cobble... Toutefois, je vous propose un petit exemple d'application.

        @Override
        public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumFacing side, float hitX, float hitY, float hitZ)
        {
            if(state.getBlock() != this)
            {
                return false;
            }
            else
            {
                boolean flag = ((Boolean)state.getValue(TUTO_BOOL)).booleanValue();
                if(flag)
                {
                    world.setBlockState(pos, state.withProperty(TUTO_BOOL, Boolean.valueOf(false)));
                    return true;
                }
                else if(!flag)
                {
                    world.setBlockState(pos, state.withProperty(TUTO_BOOL, Boolean.valueOf(true)));
                    return true;
                }
                return false;
            }
        }
    

    Normalement vous devriez comprendre, mais si vous ne l'avez pas encore compris, quand on cliquera sur le bloc il changera de state, true ==> false et false ==> true. Je vous propose de voir quelques fonctions pratiques, notez que ce que je vais vous montrer peut être adapté à toute propriété et adapté à d'autres fonctions desquelles vous pourriez avoir besoin.

        @Override
        public float getBlockHardness(World world, BlockPos pos)
        {
            IBlockState state = world.getBlockState(pos);
    
            if(state.getBlock() == this)
            {
                boolean tuto_bool = (Boolean)state.getValue(TUTO_BOOL);
    
                if(tuto_bool)
                    return 2.5F;
                else
                    return 2.0F;
            }
    
            return super.getBlockHardness(world, pos);
        }
    
        @Override
        public float getExplosionResistance(World world, BlockPos pos, Entity exploder, Explosion explosion)
        {
    
            IBlockState state = world.getBlockState(pos);
    
            if(state.getBlock() == this)
            {
                boolean tuto_bool = (Boolean)state.getValue(TUTO_BOOL);
    
                if(tuto_bool)
                    return 5.0F;
                else
                    return 3.0F;
            }
    
            return super.getBlockHardness(world, pos);
    
        }
    

    Le fonctionnement est simple et en plus est très pratique ! Dans les deux cas, nous regardons que le bloc est bien celui là, ensuite un fait un boolean et s'il est true on retourne une valeur et si false une autre. Attention pour l'explosion resistance, ce n'est pas comme quand vous utilisez le setter setBlockResistance(floa), donc, la valeur que vous auriez entré dans le setter devra être multipliéé par 3 et divisée par 5. Donc, si dans votre setter vous auriez dû mettre 10, vous retournez 6 (6 = 10*3/5).

    Les PropertiesInteger :

    Nous allons découvrir la troisième forme de propriété existante. Cette propriété intégrera des valeurs "int". Je vous propose de voir comment se construit cette propriété. Pour commencer, notre propriété sera de la classe PropertyInteger.

    public static final PropertyInteger TUTO_INTEGER = PropertyInteger.create("tuto_integer", 0, 3);
    

    Donc, cette fois s'il s'agit d'une variable de la classe PropertyInteger. Comme toujours on utilise la fonction create qui se trouve dans la classe de notre propriété. Le premier paramètre est toujours le nom de notre propriété. Le changement est ce qui rend utilisable et différente cette propriété qui s'applique sur le second et troisième paramètre (int), le premier des deux paramètres correspondent à la valeur minimum (au-dessus ou égale à 0) et le second à la valeur maximum (plus grande que la valeur minimume). Cette propriété, moins utilisée, l'est par exemple dans les gâteaux (chaque valeur de la propriété correspond à une quantité de gâteau, à chaque fois qu'on clique on augmente la valeur et on donne une texture et un model pour chaque valeur). Dans le constructeur de votre classe, ajoutez :

    this.setDefaultState(getDefaultState().withProperty(TUTO_INTEGER, 0));
    

    Comme toujours nous définissons la state par défaut, pour ce bloc on met avec la propriété TUTO_INTEGER avec 0 en valeur.

        @Override
        public IBlockState getStateFromMeta(int meta)
        {
            return this.getDefaultState().withProperty(TUTO_INTEGER, Integer.valueOf(meta));
        }
    

    Grâce à cela, que nous avons vu dans les classes précédentes va nous permettre de récupérer la/les bonne/s state en fonction de la metadata.

        @Override
        public int getMetaFromState(IBlockState state)
        {
            return ((Integer)state.getValue(TUTO_INTEGER)).intValue();
        }
    

    Nous récupérons la metadata en fonction de la valeur de notre propriété. Ensuite :

        @Override
        protected BlockState createBlockState()
        {
            return new BlockState(this, new IProperty[] {TUTO_INTEGER});
        }
    

    Nous connaissons bien cette fonction, elle nous permet d'ajouter les propriétés de notre bloc, comme nous l'avons déjà vu, sans, le jeu aura un crash. Maintenant, nous allons voir un exemple d'application. Nous ferons en sorte qu'en cliquant sur le bloc quatre fois il explosera et que, s'il est détruit par une explosion, il explose aussi.

        @Override
        public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumFacing side, float hitX, float hitY, float hitZ)
        {   
            state = world.getBlockState(pos);
            //on met la valeur de notre propriété à la variable i
            int i = ((Integer)state.getValue(TUTO_INTEGER)).intValue();
            //on récupère les coordonées x, y, z
            int x = pos.getX();
            int y = pos.getY();
            int z = pos.getZ();
            //cette condition tant que i (valeur de notre propriété) est inférieure à 3,
            //vous pouvez y mettre une autre valeur (1, 2 mais pas au dessus)
            if(i < 3)
            {
                //on met notre bloc avec
                world.setBlockState(pos, state.withProperty(TUTO_INTEGER, Integer.valueOf(i + 1)), 3);
            }
            //sinon on crée une explosion
            else
            {
                //on met un bloc d'air
                world.setBlockToAir(pos);
                //créer l'explosion (entité, coordX, coordY, coordZ, puissance, fumée)
                world.createExplosion(null, x, y, z, 4.0F, true);
            }
            return true;
        }
    

    Le code est suffisamment expliqué (cette fonction se déclenche quand on active notre bloc). Maintenant que nous avons fait ça, on va ajouter la fonction qui intervient quand le bloc est détruit par une explosion :

        @Override
        public void onBlockDestroyedByExplosion(World world, BlockPos pos, Explosion explosion)
        {
            //on récupère les coordonées x, y, z du bloc
            int x = pos.getX();
            int y = pos.getY();
            int z = pos.getZ();
    
            //on met un bloc
            world.setBlockToAir(pos);
            //on crée l'explosion (entité, coordX, coordY, coordZ, puissance, fumée) 
            world.createExplosion(null, x, y, z, 4.0F, true);
        }
    

    Encore une fois le code est expliqué (cette fonction se déclenche quand le bloc est détruit par une explosion). Comme vous l'avez vu dans les deux fonctions, on utilise "createExplosion", il faut savoir que les paramètres double correspondent aux coordonnées, le premier à l'entité qui explose, le float à la puissance de l'explosion et le boolean si on veut faire apparaître des particules de fumée. Vous pouvez aussi utiliser la fonction : "newExplosion", cette fonction change sur un boolean (le premier), qui permet de mettre des flammes à l'endroit de l'explosions (world.newExplosion(null, x, y, z, 4.0F, true (ou false, pour les flammes), true);

        public String getUnlocalizedName(int metadata)
        {
            return super.getUnlocalizedName() + "." + ((Integer)this.getStateFromMeta(metadata).getValue(TUTO_INTEGER)).intValue();
        }
    

    Si vous voulez que chaque state ait une metadata identifiable par le nom.

        @SideOnly(Side.CLIENT)
        public void getSubBlocks(Item itemIn, CreativeTabs tab, List list)
        {
            list.add(new ItemStack(this, 1, 0/*correspond à la metadata*/));
            list.add(new ItemStack(this, 1, 1));
            list.add(new ItemStack(this, 1, 2));
            list.add(new ItemStack(this, 1, 3));
        }
    

    Nous avons déjà vu cette fonction, elle nous permet d'ajouter les metadata de notre bloc dans des creatives tabs.

    Les Axis et les Directions :

    Les properties : "Axis et Directions" sont des variantes de la PropertyEnum (que nous avons vu au début). Les directions seront utilisées pour donner une direction (nort, sud, est, ouest), les axis, eux, seront utilisées pour dire axes du bloc (X,Y,Z ou aucun). Voyons comment créer ces properties. Commençons par les directions.
    Les directions :
    Dans la classe de votre bloc, commencez par créer la variable de votre property.

    public static final PropertyDirection TUTO_DIRECTION = PropertyDirection.create("direction", EnumFacing.Plane.HORIZONTAL);
    

    Comme toujours le premier paramètre correspond au nom de votre property, le second correspond au type de votre direction ("HORIZONTAL" ou "VERTICAL") dans horizontal on pourra mettre les valeurs nord, sud, ouest, est et dans vertical dessus et dessous. Pour le tutoriel j'utiliserai le premier type: horizontal. Je vous laisse le loisir de tenter à la verticale (qui est semblable à l'horizontale).

        @Override
        public void onBlockAdded(World world, BlockPos pos, IBlockState state)
        {
            this.setDefaultFacing(world, pos, state);
        }
    

    Avec cette fonction qui sera appelée quand on ajoute le bloc on appelle la fonction setDefaultFacing.

        private void setDefaultFacing(World world, BlockPos pos, IBlockState state)
        {
            if(!world.isRemote)
            {
                Block blockN = world.getBlockState(pos.north()).getBlock();
                Block blockS = world.getBlockState(pos.south()).getBlock();
                Block blockW = world.getBlockState(pos.west()).getBlock();
                Block blockE = world.getBlockState(pos.east()).getBlock();
    
                EnumFacing facing = (EnumFacing)state.getValue(TUTO_DIRECTION);
    
                if(facing == EnumFacing.NORTH && blockN.isFullBlock() && !blockS.isFullBlock())
                {
                    facing = EnumFacing.SOUTH;
                }
                else if(facing == EnumFacing.SOUTH && blockS.isFullBlock() && !blockN.isFullCube())
                {
                    facing = EnumFacing.NORTH;
                }
                else if(facing == EnumFacing.EAST && blockE.isFullBlock() && !blockW.isFullCube())
                {
                    facing = EnumFacing.WEST;
                }
                else if(facing == EnumFacing.WEST && blockW.isFullBlock() && !blockE.isFullBlock())
                {
                    facing = EnumFacing.EAST;
                }
            }
        }
    

    Dans cette fonction, on regarde si le monde est serveur, ensuite on utilise quatre variables block, qui ont chacune une coordonnée relative attribuée. Ensuite, la variable facing nous permet de connaitre la valeur de notre property, les conditions qui suivent permettent de savoir quelle valeur attribuer à notre property lorsque l'on place le bloc.

        @Override
        public IBlockState onBlockPlaced(World world, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ, int meta, EntityLivingBase placer)
        {
            return this.getDefaultState().withProperty(TUTO_DIRECTION, placer.getHorizontalFacing().getOpposite());
        }
    
    

    Cette fonction permet de placer le bloc par rapport à l'entité qui le place.

        @Override
        @SideOnly(Side.CLIENT)
        public IBlockState getStateForEntityRender(IBlockState state)
        {
            return this.getDefaultState().withProperty(TUTO_DIRECTION, EnumFacing.SOUTH);
        }
    

    Récupère la state à rendre pour les entités :

        @Override
        public IBlockState getStateFromMeta(int meta)
        {
            EnumFacing enumfacing = EnumFacing.getFront(meta);
    
            if(enumfacing.getAxis() == EnumFacing.Axis.Y)
            {
                enumfacing = EnumFacing.NORTH;
            }
    
            return this.getDefaultState().withProperty(TUTO_DIRECTION, enumfacing);
        }
    

    Cela nous permet de retourner la state en fonction de la metadata.

        @Override
        public int getMetaFromState(IBlockState state)
        {
            return ((EnumFacing)state.getValue(TUTO_DIRECTION)).getIndex();
        }
    
    

    Grâce à cette fonction que vous connaissez bien nous allons pouvoir récupérer les metadata du bloc en fonction de la state.

        @Override
        protected BlockState createBlockState()
        {
            return new BlockState(this, new IProperty[] {TUTO_DIRECTION});
        }
    

    Normalement vous devez connaitre cette fonction. Sans elle notre property ne s'utilise pas et notre jeu crash. Dans votre constructeur nous allons définir la state par défaut :

    this.setDefaultState(getDefaultState().withProperty(TUTO_DIRECTION, EnumFacing.NORTH));
    

    Les axis :
    Pour nos property axis, on utilisera une PropertyEnum. Nous allons donc devoir créer une énumération, notez que nous n'utiliserons pas cette énumération en temps que classe interne. Commencez par créer votre énumération qui devra être implémentée de l'interface IStringSerializable. Ensuite, nous allons ajouter quatre valeurs à notre énumération :

    X("x"), Y("y"), Z("z"), NONE("none");
    

    Vous devriez avoir des erreurs car nous n'avons pas encore défini de constructeur. Ajoutez une variable String privée avec un modifieur final :

    private final String name;
    

    Maintenant nous allons ajouter un constructeur :

        private EnumAxis(String name)
        {
            this.name = name;
        }
    

    Grâce à ceci on stockera le nom dans la variable name.

        @Override
        public String getName()
        {
            return name;
        }
    

    En ajoutant cette fonction vous devriez avoir une erreur qui disparaît. Avec cette fonction ainsi que avec la suivante on retourne le nom.

        @Override
        public String toString()
        {
            return name;
        }
    

    Après ces deux fonctions.

        public static EnumAxis fromFacingAxis(EnumFacing.Axis axis)
        {
            switch(SwitchEnumAxis.AXIS_LOOKUP[axis.ordinal()])
            {
                case 1:
                    return X;
                case 2:
                    return Y;
                case 3:
                    return Z;
                default:
                    return NONE;
            }
        }
    

    Avec cette fonction on pourra récupérer le bon axe. Vous avez une erreur, c'est normal, il vous manque encore une petite chose. Créez donc une classe interne static et final :

        static final class SwitchEnumAxis
        {
            static final int[] AXIS_LOOKUP = new int[EnumFacing.Axis.values().length];
    
            static
            {
                try
                {
                    AXIS_LOOKUP[EnumFacing.Axis.X.ordinal()] = 1;
                }
                catch(NoSuchFieldError e)
                {
                    ;
                }
    
                try
                {
                    AXIS_LOOKUP[EnumFacing.Axis.Y.ordinal()] = 2;
                }
                catch(NoSuchFieldError e)
                {
                    ;
                }
    
                try
                {
                    AXIS_LOOKUP[EnumFacing.Axis.Z.ordinal()] = 3;
                }
                catch(NoSuchFieldError e)
                {
                    ;
                }
            }
        }
    

    Ceci permet de stocker une valeur int en fonction de l'axis, cela nous permet de récupérer le bon axe. Nous en avons fini avec la classe EnumAxis, passons à la classe de notre Bloc.

    public static final PropertyEnum TUTO_AXIS = PropertyEnum.create("axis", EnumAxis.class);
    

    On crée la variable de notre property. C'est une EnumProperty normal.

        @Override
        public IBlockState onBlockPlaced(World world, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ, int meta, EntityLivingBase placer)
        {
            return super.onBlockPlaced(world, pos, facing, hitX, hitY, hitZ, meta, placer).withProperty(TUTO_AXIS, EnumAxis.fromFacingAxis(facing.getAxis()));
        }
    

    Cette fonction nous permet de placer le bloc avec le bon axe.

        @Override
        public IBlockState getStateFromMeta(int meta)
        {
            IBlockState state = this.getDefaultState();
    
            switch(meta)
            {
                case 0:
                    state = state.withProperty(TUTO_AXIS, EnumAxis.Y);
                    break;
                case 1:
                    state = state.withProperty(TUTO_AXIS, EnumAxis.X);
                    break;
                case 2:
                    state = state.withProperty(TUTO_AXIS, EnumAxis.Z);
                    break;
                default:
                    state = state.withProperty(TUTO_AXIS, EnumAxis.NONE);
            }
            return state;
    
        }
    

    Avec cette fonction que nous avons souvent vu nous allons retourner la bonne state par rapport à la metadata.

        @Override
        public int getMetaFromState(IBlockState state)
        {
            int meta = 0;
    
            switch(SwitchEnumAxis.AXIS_LOOKUP[((EnumAxis)state.getValue(TUTO_AXIS)).ordinal()])
            {
                case 1:
                    meta = 1;
                    break;
                case 2:
                    meta = 2;
                    break;
                case 3:
                    meta = 3;
                    break;
            }
    
            return meta;
        }
    

    Avec cette fonction (que vous avez aussi beaucoup vu) nous faisons l'inverse de la précédente, nous récupérons la metadata avec la state. Notez que vous devriez avoir une erreur (que nous allons corriger).

        @Override
        protected BlockState createBlockState()
        {
            return new BlockState(this, new IProperty[] {TUTO_AXIS});
        }
    

    Je ne pense pas devoir expliquer quelque chose. Nous allons ensuite ajoutez une classe interne :

        static final class SwitchEnumAxis
        {
    
            static final int[] AXIS_LOOKUP = new int[EnumAxis.values().length];
    
            static
            {
                try
                {
                    AXIS_LOOKUP[BlockLog.EnumAxis.X.ordinal()] = 1;
                }
                catch(NoSuchFieldError var3)
                {
                    ;
                }
    
                try
                {
                    AXIS_LOOKUP[BlockLog.EnumAxis.Z.ordinal()] = 2;
                }
                catch(NoSuchFieldError var2)
                {
                    ;
                }
    
                try
                {
                    AXIS_LOOKUP[BlockLog.EnumAxis.NONE.ordinal()] = 3;
                }
                catch(NoSuchFieldError var1)
                {
                    ;
                }
            }
    
        }
    

    La classe est différente, mais le fonctionnement est le même que pour notre autre classe (du même nom) dans la classe EnumAxis. Il vous manque plus qu'à donner la state par défaut (dans le constructeur):

    this.setDefaultState(getDefaultState().withProperty(TUTO_AXIS, EnumAxis.Y));
    

    Plusieurs properties :

    Dans cette partie je vous donnerai des exemples d'utilisation de plusieurs property en même temps. Avec les quelques exemples vous devriez pourvoir utiliser et combiner n'importe quelle property. Nous allons démarrer par une combinaison de PropertyBool et PropertyEnum.

        public static final PropertyBool TUTO_BOOL = PropertyBool.create("tuto_bool");
        public static final PropertyEnum VARIANT = PropertyEnum.create("variant", TutoBlockStateEnum.EnumType.class);
    

    Vous remarquez que nous utilisons énumération de l'autre classe, mais bien évidemment vous pouvez créer une autre énumération.

        @Override
        public IBlockState getStateFromMeta(int meta)
        {
            boolean tuto_bool = (meta & 8) > 0;
            int type = meta & 1;
    
            return this.getDefaultState().withProperty(VARIANT, EnumType.values()[type]).withProperty(TUTO_BOOL, Boolean.valueOf(tuto_bool));
        }
    

    Vous remarquez que cette fonction pour récupérer les states est un peu particulière. Si vous regardez ensuite d'un autre œil vous vous rendez compte qu'elle est totalement basique. En effet, on crée une variable boolean, cette variable boolean sera true si (meta & 😎 sont supérieurs à 0, sinon elle sera false.

        @Override
        public int getMetaFromState(IBlockState state)
        {
            int type = ((EnumType)state.getValue(VARIANT)).getMetadata();
            boolean tuto_bool = (Boolean)state.getValue(TUTO_BOOL);
    
            return tuto_bool ? type | 8 : type;
        }
    

    Là aussi on remarque une architecture différente de ce que vous aviez pu voir, on crée une variable qui permet de récupérer la valeur de la metadata, ensuite on crée une variable boolean pour récupérer la valeur de notre property TUTO_BOOL. Enfin on retourne : Si la valeur de notre property TUTO_BOOL est true on retourne la valeur avec l'opérateur "|" (si la metadata est 1, en binnaire 0001 avec 8 en binnaire 1000 ça donne 9 soit 1001, pour plus d'information regardez les opérateurs de manipulation binaire) cela aura pour notre cas d'ajouter 8 à notre metadata. Dans l'autre cas on retourne la valeur de notre metadata d'origine.

        @Override
        public int damageDropped(IBlockState state)
        {
            int type = ((EnumType)state.getValue(VARIANT)).getMetadata();
            boolean tuto_bool = (Boolean)state.getValue(TUTO_BOOL);
    
            return tuto_bool ? type | 8 : type;
        }
    

    On fait comme au-dessus.

        @Override
        protected BlockState createBlockState()
        {
            return new BlockState(this, new IProperty[] {TUTO_BOOL, VARIANT});
        }
    

    Dans le constructeur définissez votre state par défaut.

    this.setDefaultState(getDefaultState().withProperty(TUTO_BOOL, false).withProperty(VARIANT, TutoBlockStateEnum.EnumType.VARIANT_1));
    

    Après cela nous allons ajouter quelques fonctions pratiques.

        @Override
        @SideOnly(Side.CLIENT)
        public void getSubBlocks(Item item, CreativeTabs tab, List list)
        {
            list.add(new ItemStack(this, 1, 0));
            list.add(new ItemStack(this, 1, 1));   
    
            list.add(new ItemStack(this, 1, 8));
            list.add(new ItemStack(this, 1, 9));
        }
    

    Ceci n'est pas obligatoire, mais si vous voulez que votre bloc soit dans un onglet créative. Le premier paramètre correspond au bloc, le second au nombre de stack et enfin le troisième à la metadata.

        public String getUnlocalizedName(int metadata)
        {
            IBlockState state = this.getStateFromMeta(metadata);
            boolean tutoBool = (Boolean)state.getValue(TUTO_BOOL);
    
            return super.getUnlocalizedName() + "." + ((EnumType)state.getValue(VARIANT)).getName() + "." + tutoBool;
        }
    

    Si vous voulez que votre bloc ait un nom. Je ne pense pas devoir expliquer grand chose. Maintenant nous allons continuer avec les fonctions utiles avec deux que nous avons déjà vues qui vous seront vraiment pratiques.

        @Override
        public float getBlockHardness(World world, BlockPos pos)
        {
            IBlockState state = world.getBlockState(pos);
    
            if(state.getBlock() == this)
            {
                boolean tuto_bool = (Boolean)state.getValue(TUTO_BOOL);
                int variant = state.getValue(VARIANT) == TutoBlockStateEnum.EnumType.VARIANT_1 ? 1 : 0;
    
                if(tuto_bool)
                    return 2.5F + variant;
                else
                    return 2.0F + variant;
            }
    
            return super.getBlockHardness(world, pos);
        }
    

    Pour notre cas, je retourne des valeurs différentes, mais vous pouvez faire des tas de combinaisons (ceci est un exemple). La partie intéréssante est la fin, on crée un boolean avec la valeur de notre property bool, ensuite un int à qui on donnera une valeur soit de 1 ou de 0 en fonction de la variante. Enfin on regarde si le boolean retourne true dans le cas on retourne une valeur de 2.5F où on additionne notre int. Dans le cas contraire on retourne une valeur de 2.0F additionnée à notre int.

        @Override
        public float getExplosionResistance(World world, BlockPos pos, Entity exploder, Explosion explosion)
        {
    
            IBlockState state = world.getBlockState(pos);
    
            if(state.getBlock() == this)
            {
                boolean tuto_bool = (Boolean)state.getValue(TUTO_BOOL);
                int variant = state.getValue(VARIANT) == TutoBlockStateEnum.EnumType.VARIANT_1 ? 1 : 0;
    
                variant = variant + (tuto_bool ? 2 : 0);
    
                int resistance = (3 + variant) * 3 / 5;
    
                return resistance;
            }
    
            return super.getBlockHardness(world, pos);
    
        }
    

    Cette fonction nous est aussi très utile, elle ressemble à la précédente en fonction des propriétés. On donne une valeur différente. La valeur entre parenthèses dans le int resistance correspond à la valeur à mettre dans le setResistance.

        public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumFacing side, float hitX, float hitY, float hitZ)
        {
            if(state.getBlock() != this)
            {
                return false;
            }
            else
            {
                boolean flag = ((Boolean)state.getValue(TUTO_BOOL)).booleanValue();
    
                if(flag)
                {
                    world.setBlockState(pos, state.withProperty(TUTO_BOOL, Boolean.valueOf(false)));
                    return true;
                }
                else if(!flag)
                {
                    world.setBlockState(pos, state.withProperty(TUTO_BOOL, Boolean.valueOf(true)));
                    return true;
                }
                return false;
            }
        }
    

    Ceci est un petit exemple d'utilisation ça permet de passer de true à false et de false à true notre property. Nous allons voir un deuxième exemple utilisant deux property. Ce coup-là on utilisera une property Integer et Bool. Dans cet exemple nous n'utiliserons pas la property bool pour créer d'autres metadata. En revanche, on l'utilisera pour révéler un état.

        public static final PropertyInteger TUTO_INTEGER = PropertyInteger.create("tuto_integer", 0, 3);
        public static final PropertyBool TUTO_BOOL = PropertyBool.create("tuto_bool");
    

    Après avoir ajouté la base, ajoutez :

        @Override
        public IBlockState getStateFromMeta(int meta)
        {
            boolean tuto_bool = (meta & 8) > 0;
            int type = meta;
    
            return this.getDefaultState().withProperty(TUTO_INTEGER, Integer.valueOf(type)).withProperty(TUTO_BOOL, Boolean.valueOf(tuto_bool));
        }
    
        @Override
        public int getMetaFromState(IBlockState state)
        {
            boolean tuto_bool = ((Boolean)state.getValue(TUTO_BOOL)).booleanValue();
            int tuto_int = ((Integer)state.getValue(TUTO_INTEGER)).intValue();
    
            return tuto_bool ? tuto_int | 8 : tuto_int;
        }
    
        @Override
        public int damageDropped(IBlockState state)
        {
            boolean tuto_bool = ((Boolean)state.getValue(TUTO_BOOL)).booleanValue();
            int tuto_int = ((Integer)state.getValue(TUTO_INTEGER)).intValue();
            return tuto_bool ? tuto_int | 8 : tuto_int;
        }
    
        @Override
        protected BlockState createBlockState()
        {
            return new BlockState(this, new IProperty[] {TUTO_INTEGER, TUTO_BOOL});
        }
    

    Je n'ai rien à expliquer, nous avons déjà vu ceci avant.

        public String getUnlocalizedName(int metadata)
        {
            return super.getUnlocalizedName() + "." + ((Integer)this.getStateFromMeta(metadata).getValue(TUTO_INTEGER)).intValue() + "." + ((Boolean)this.getStateFromMeta(metadata).getValue(TUTO_BOOL)).booleanValue();
        }
    
        @SideOnly(Side.CLIENT)
        public void getSubBlocks(Item itemIn, CreativeTabs tab, List list)
        {
            list.add(new ItemStack(this, 1, 0));
            list.add(new ItemStack(this, 1, 1));
            list.add(new ItemStack(this, 1, 2));
            list.add(new ItemStack(this, 1, 3));
        }
    

    De la même manière, si vous voulez voir le nom de votre bloc en fonction des états (integer) ou simplement le faire apparaître dans un onglet créative. Notez que je ne fais pas apparaître les metadata en fonction de la valeur de TUTO_BOOL. Après cela ajoutez de la même manière que dans votre classe sur les integer :

        @Override
        public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumFacing side, float hitX, float hitY, float hitZ)
        {
            state = world.getBlockState(pos);
            int i = ((Integer)state.getValue(TUTO_INTEGER)).intValue();
            boolean tuto_bool = world.isBlockPowered(pos) ? true : false;
    
            int x = pos.getX();
            int y = pos.getY();
            int z = pos.getZ();
    
            world.setBlockState(pos, state.withProperty(TUTO_BOOL, Boolean.valueOf(tuto_bool)));
    
            if(tuto_bool)
            {
                if(i < 3)
                {
                    world.setBlockState(pos, state.withProperty(TUTO_INTEGER, Integer.valueOf(i + 1)).withProperty(TUTO_BOOL, Boolean.valueOf(tuto_bool)), 3);
                }
    
                else
                {
                    world.setBlockToAir(pos);
                    world.createExplosion(null, x, y, z, 4.0F, true);
                }
                return true;
            }
            return false;
        }
    

    Cette fonction est presque identique à celle de l'autre classe, à la différence on regarde si notre valeur boolean tuto_bool, qui correspond à "si le bloc reçoit du courant si elle est true alors on crée l'explosion" sinon on ne fait rien, on set aussi la state qui correspond à la bonne valeur.

        @Override
        public void onBlockDestroyedByExplosion(World world, BlockPos pos, Explosion explosion)
        {
            int x = pos.getX();
            int y = pos.getY();
            int z = pos.getZ();
    
            world.setBlockToAir(pos);
            world.createExplosion(null, x, y, z, 4.0F, true);
        }
    
    

    Oubliez pas cette fonction pour quand le bloc est détruit. Je vous laisse vous amuser à tester par vous-même comment faire exploser le bloc s'il a sa valeur de la PropertyBool en true. Nous en avons fini avec ce court exemple.

        @Override
        public void onNeighborBlockChange(World world, BlockPos pos, IBlockState state, Block neighborBlock)
        {
            boolean tuto_bool = world.isBlockPowered(pos) ? true : false;
            world.setBlockState(pos, state.withProperty(TUTO_BOOL, Boolean.valueOf(tuto_bool)));
        }
    

    Je met cette fonction de manière à update le bloc pour éviter que ce soit buggé. Nous passerons maintenant à un exemple beaucoup plus intéressant. Nous utiliserons trois states différents: une énumération, un boolean et un integer.

        public static final PropertyEnum DARK = PropertyEnum.create("dark", EnumType.class);
        public static final PropertyBool POWERED = PropertyBool.create("powered");
        public static final PropertyInteger COLOR = PropertyInteger.create("color", 0, 3);
    

    Nous déclarons et initialisons nos variables de la même manière que pour toutes nos classe. Chose importante que vous comprendrez juste après, notre Enum ne doit pas contenir plus de deux valeurs.

    this.setDefaultState(getDefaultState().withProperty(POWERED, Boolean.valueOf(false)).withProperty(COLOR, 0).withProperty(DARK, EnumType.BLACK));
    

    Ajouter dans le constructeur votre state par défaut, que nous le fassions maintenant ou après ne change rien.

        @Override
        public int getMetaFromState(IBlockState state)
        {
            byte b0 = 0;
            int i;
    
            i = b0 | ((Integer)state.getValue(COLOR)).intValue();
    
            if(state.getValue(DARK) == EnumType.WHITE)
                i |= 8;
            i |= (((Boolean)state.getValue(POWERED)).booleanValue() == true) ? 4 : 0;
    
            return i;
        }
    

    On passe aux choses un peu plus intéressantes. Si vous ne connaissez pas et ne comprenez pas les opérateurs de bit-to-bit je vous conseille fortement de vous documenter dessus avant. Ce sont des choses simples mais essentielles. Nous utilisons déjà deux variables, un int et un byte. Ceci nous permettra pour le int de garder le résultat de nos calculs et pour le byte de faire un calcul. On commence par décaler la valeur de b0 (notre byte) par la valeur de notre int de la property integer. Ceci nous permet donc de récupérer notre valeur, i aura donc pour valeur maximum 3. Ensuite, on regarde si notre state a pour valeur "EnumType.WHITE" dans la property enum. Si oui alors on fait un petit calcul, notre valeur sera décalée de 8 dans tous les cas. Enfin, on regarde si la valeur de notre property POWERED vaut true, si oui on décale la valeur de 4. Soyez prudent dans vos BlockState car souvenez vous que la valeur maximale des metadata est 16. Avec nos trois property on arrive à 16 valeurs différentes.

        @Override
        public IBlockState getStateFromMeta(int meta)
        {
            IBlockState state = this.getDefaultState().withProperty(COLOR, (meta & 3) % 4);
    
            switch(meta & 8)
            {
                case 0:
                    state = state.withProperty(DARK, EnumType.BLACK);
                    break;
                case 8:
                    state = state.withProperty(DARK, EnumType.WHITE);
            }
    
            switch(meta & 4)
            {
                case 4:
                    state = state.withProperty(POWERED, Boolean.valueOf(false));
                case 12:
                    state = state.withProperty(POWERED, Boolean.valueOf(true));
            }
    
            return state;
        }
    

    Vous allez encore avoir besoin des opérateurs de bit-to-bit. On démarre par créer une variable state à laquelle on ajoute la valeur de la property COLOR, on utilise le (meta&3) pour obtenir la bonne valeur pour nos 16 metadata, on utilise ensuite l'opérateur modulo pour récupérer une valeur entière. Après cela on utilise un switch pour savoir quelle est la valeur de notre property DARK. On utilise encore un switch pour savoir si notre bloc a pour valeur true ou false à notre property POWERED. Normalement en comprenant le fonctionnement de l'opérateur vous devriez comprendre pourquoi j'utilise 4 et 12. Remarquez qu'il est important de pouvoir y modifier pour d'autres blocs que vous pourriez créer où nous utiliserons plus les mêmes valeurs.

        @Override
        protected BlockState createBlockState()
        {
            return new BlockState(this, new IProperty[] {DARK, POWERED, COLOR});
        }
    

    Enfin une fonction où il ne faut pas réfléchir :).

        @Override
        public void getSubBlocks(Item item, CreativeTabs tab, List list)
        {
            list.add(new ItemStack(this, 1, 0));
            list.add(new ItemStack(this, 1, 8));
        }
    

    Ajoutez le getSubBlocks pour le mettre dans une créative tab, 0 et 8 sont les metadata de nos variantes, mais vous pouvez en mettre d'autres. Par exemple on aurait pu ajouter les autres metadata de la state COLOR.

        public String getUnlocalizedName(int metadata)
        {
            String varName = metadata < 8 ? "black" : "white";
    
            return super.getUnlocalizedName() + "." + varName;
        }
    

    Si vous le souhaitez, vous pouvez aussi ajouter à la suite les autres states. Pour mon cas je n'en ai aucune utilité. Par exemple vous pouvez ajouter la valeur de la state COLOR à la suite... Nous allons conclure ce bloc avec un petit exemple d'utilisation.

        @Override
        public void onNeighborBlockChange(World world, BlockPos pos, IBlockState state, Block neighborBlock)
        {
            boolean flag = world.isBlockPowered(pos);
            state = world.getBlockState(pos);
    
            world.setBlockState(pos, state.withProperty(POWERED, Boolean.valueOf(flag)));
        }
    

    Avec cette fonction appelée quand un bloc voisin changera, on regardera si notre bloc reçoit un courant de redstone (on le met dans un variable), si notre variable est true alors notre state aura la valeur true (ce qui signifie que le bloc reçoit du courant), sinon elle sera false.

        @Override
        public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumFacing side, float hitX, float hitY, float hitZ)
        {
            state = world.getBlockState(pos);
            int i = ((Integer)state.getValue(COLOR)).intValue();
    
            if(((Boolean)state.getValue(POWERED)).booleanValue() == true)
            {
                if(i < 3)
                {
                    world.setBlockState(pos, state.withProperty(COLOR, i + 1));
                }
                else if(i == 3)
                {
                    world.setBlockState(pos, state.withProperty(COLOR, 0));
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
    
            return true;
    
        }
    

    Nous retrouvons une fonction simple, on définit une variable i avec pour valeur la valeur de notre COLOR, ensuite on regarde si notre bloc est alimenté, si oui on augmente la valeur de i de 1, si i vos 3 alors on le remet à 0. Normalement avec les exemples que je vous ai donné vous devriez être capable de faire tout bloc state, comme je l'ai dit au début il y a une possibilité infinie de state. Donc grâce aus exemples vous devriez arriver à les combiner sans problème.

    La classe de l'ItemBlock :

    Comme vous l'avez probablement remarqué, tous les blocs ont un item associé. Vous allez donc créer l'ItemBlock. Créer une classe hérité de la classe ItemBlock. Créez le constructeur et ajoutez dedans :

            this.setMaxDamage(0);
            this.setHasSubtypes(true);
    

    Ensuite, ajoutez une variable de votre classe du bloc :

    private VotreClasseBlockState blockState
    

    Après cela :

        @Override
        public int getMetadata(int metadata)
        {
            return metadata;
        }
    

    Sans cette fonction votre bloc ne rendra pas la bonne metadata, mais une metadata de 0, c'est une erreur bête mais qui peut arriver, donc si ça vous arrive vous savez où chercher en premier.

        public String getUnlocalizedName(ItemStack stack)
        {
            blockState = (VotreClasseBlockState)this.block;
            return blockState.getUnlocalizedName(stack.getMetadata());
        }
    

    Enfin on ajoute la fonction qui nous permet de retourner le nom de chaque metadata, pour notre cas il vous faut la fonction getUnlocalizedName dans votre bloc. Vous pouvez faire cela autrement, mais c'est la méthode que je vous recommande.

    La classe principale :

    Dans votre classe principale, déclarez vos blocs:

    public static Block tutoStateEnum, tutoStateBool, tutoStateBool1, tutoStateInteger, tutoStateBoolEnum, tutoStateBoolInteger, tutoStateDirection, tutoStateAxis, tutoStateMulti;
    

    Ensuite, dans la fonction preInit initialiser les :

            tutoStateEnum = new TutoBlockStateEnum(Material.rock).setUnlocalizedName("tuto_block_enum");
            GameRegistry.registerBlock(tutoStateEnum, ItemBlockTutoState1.class, "tuto_block_enum");
    
            tutoStateBool1 = new TutoBlockStateBool1(Material.rock).setUnlocalizedName("tuto_block_bool1");
            GameRegistry.registerBlock(tutoStateBool1, ItemBlockTutoState2.class, "tuto_block_bool1");
    
            tutoStateBool = new TutoBlockStateBool(Material.rock).setUnlocalizedName("tuto_block_bool");
            GameRegistry.registerBlock(tutoStateBool, "tuto_block_bool");
    
            tutoStateInteger = new TutoBlockStateInteger(Material.rock).setUnlocalizedName("tuto_block_integer");
            GameRegistry.registerBlock(tutoStateInteger, ItemBlockTutoState3.class, "tuto_block_integer");
    
            tutoStateBoolEnum = new TutoBlockStateBoolEnum(Material.rock).setUnlocalizedName("tuto_block_boolenum");
            GameRegistry.registerBlock(tutoStateBoolEnum, ItemBlockTutoState4.class, "tuto_block_boolenum");
    
            tutoStateBoolInteger = new TutoBlockStateBoolInteger(Material.rock).setUnlocalizedName("tuto_block_boolinteger");
            GameRegistry.registerBlock(tutoStateBoolInteger, ItemBlockTutoState5.class, "tuto_block_boolinteger");
    
            tutoStateDirection = new TutoBlockStateDirection(Material.rock).setUnlocalizedName("tuto_block_direction");
            GameRegistry.registerBlock(tutoStateDirection, "tuto_block_direction");
    
            tutoStateAxis = new TutoBlockStateAxis(Material.rock).setUnlocalizedName("tuto_block_axis");
            GameRegistry.registerBlock(tutoStateAxis, "tuto_block_axis");
    
            tutoStateMulti = new TutoBlockStateMultiState(Material.rock).setUnlocalizedName("tuto_block_multi");
            GameRegistry.registerBlock(tutoStateMulti, ItemBlockTutoState6, "tuto_block_multi");
    

    Certains blocs ne sont pas enregistrés avec d'ItemBlock (custom, par défaut ItemBlock est utilisé) car je ne veux pas - et ne vois pas l'utilité - qu'ils droppent une autre metadata (state) que celle par défaut et que leur nom soit le même pour toutes les states. Si vous le souhaitez, vous pouvez mettre un ItemBlock.

    Utilisé les BlockStates :

    L'utilisation des BlockStates est assez simple et se fait toujours de la même manière. Pour ceux qui se poseraient la question des crafts, ils utilisent les ItemStack, donc la metadata. Quand il sagira de récupérer un BlockState, il faudra appeler la variable de votre bloc suivi de la fonction getBlockState().

    Tuto.tutoStateEnum.getDefaultState()
    

    Par exemple pour récupérer la state (par défaut) de mon bloc des enum, je devrai faire comme ça.

    Tuto.tutoStateEnum.getDefaultState().withProperty(TutoBlockStateEnum.VARIANT, TutoBlockStateEnum.EnumType.VARIANT_2)
    

    Avec ça nous pourrons récupérer la state avec la valeur VARIANT_2.
    Voici des exemples de utilisation :

            IBlockState state1;
            state1 = Tuto.tutoStateEnum.getDefaultState().withProperty(TutoBlockStateEnum.VARIANT, TutoBlockStateEnum.EnumType.VARIANT_1);
    

    Grâce à ceci on peut donner à la state1 la valeur de notre state.

    this.setHarvestLevel("pickaxe", 2, getDefaultState().withProperty(VARIANT, EnumType.VARIANT_2));
    

    Souvenez vous, nous avions vu comment mettre un harvest différent en fonction des states.

    world.setBlockState(pos, Tuto.tutoStateEnum.getDefaultState().withProperty(TutoBlockStateEnum.VARIANT, TutoBlockStateEnum.EnumType.VARIANT_2));
    

    Pour placer un bloc (pos représente la position), donc avec ceci nous placerons notre bloc tutoStateEnum avec une state spécifiée. Vous avez dû comprendre, quand nous devons utiliser une fonction avec un paramètre IBlockState on pourra spécifier la state ou simplement récupérer la state par défaut (.getDefaultState()), vous l'utiliserez aussi pour vos variables IBlockState et dans vos conditions.

    world.getBlockState(pos);
    

    Petite parenthèse, avec ceci nous pourrons récupérer la state par rapport à une position.

    state.getValue(VARIANT) == TutoBlockState.EnumType.VARIANT_1
    

    Avec les variables[...] IBlockState vous pourez utiliser la fonction getValue(IProperty) pour vérifier si un bloc possède une state. Bien évidemment j'utilise une classe avec une seule propety, mais vous pouvez le faire avec les classes en en utilisant plusieurs. Vous pouvez ajouter autant de withProperty() que vous désirez.

    getDefaultState().withProperty(POWERED, Boolean.valueOf(false)).withProperty(COLOR, 0).withProperty(DARK, EnumType.BLACK)
    


  • Les proxy :

    Déjà, il vous faudra avoir vu le tutoriel sur les blocs basiques. Ensuite, libre à vous de mettre l'enregistrement des rendus dans la phase init de votre classe principale ou dans une fonction dans la classe de votre proxy qui sera appelée au moment de l'init. Pour la classe principale :

           ModelBakery.addVariantName(Item.getItemFromBlock(Tuto.tutoStateEnum), "tuto:tuto_block_enum_variant_1", "tuto:tuto_block_enum_variant_2");
           proxy.registerBlockTexture(Tuto.tutoStateEnum, 0, "tuto_block_enum_variant_1");
           proxy.registerBlockTexture(Tuto.tutoStateEnum, 1, "tuto_block_enum_variant_2");
    
           ModelBakery.addVariantName(Item.getItemFromBlock(Tuto.tutoStateBool1), "tuto:tuto_block_bool1_false", "tuto:tuto_block_bool1_true");
           proxy.registerBlockTexture(Tuto.tutoStateBool1, 8, "tuto_block_bool1_true");
           proxy.registerBlockTexture(Tuto.tutoStateBool1, 0, "tuto_block_bool1_false");
    
           proxy.registerBlockTexture(Tuto.tutoStateBool, "tuto_block_bool");
    
           ModelBakery.addVariantName(Item.getItemFromBlock(Tuto.tutoStateInteger), "tuto:tuto_block_integer_0", "tuto:tuto_block_integer_1", "tuto:tuto_block_integer_2", "tuto:tuto_block_integer_3");
           proxy.registerBlockTexture(Tuto.tutoStateInteger, 0, "tuto_block_integer_0");
           proxy.registerBlockTexture(Tuto.tutoStateInteger, 1, "tuto_block_integer_1");
           proxy.registerBlockTexture(Tuto.tutoStateInteger, 2, "tuto_block_integer_2");
           proxy.registerBlockTexture(Tuto.tutoStateInteger, 3, "tuto_block_integer_3");
    
           ModelBakery.addVariantName(Item.getItemFromBlock(Tuto.tutoStateBoolEnum), "tuto:tuto_block_boolenum_false_var1", "tuto:tuto_block_boolenum_false_var2", "tuto:tuto_block_boolenum_true_var1", "tuto:tuto_block_boolenum_true_var2");
           proxy.registerBlockTexture(Tuto.tutoStateBoolEnum, 0, "tuto_block_boolenum_false_var1");
           proxy.registerBlockTexture(Tuto.tutoStateBoolEnum, 1, "tuto_block_boolenum_false_var2");
           proxy.registerBlockTexture(Tuto.tutoStateBoolEnum, 8, "tuto_block_boolenum_true_var1");
           proxy.registerBlockTexture(Tuto.tutoStateBoolEnum, 9, "tuto_block_boolenum_true_var2");
    
           ModelBakery.addVariantName(Item.getItemFromBlock(Tuto.tutoStateBoolInteger), "tuto:tuto_block_boolinteger_0", "tuto:tuto_block_boolinteger_1", "tuto:tuto_block_boolinteger_2", "tuto:tuto_block_boolinteger_3");
           proxy.registerBlockTexture(Tuto.tutoStateBoolInteger, 0, "tuto_block_boolinteger_0");
           proxy.registerBlockTexture(Tuto.tutoStateBoolInteger, 1, "tuto_block_boolinteger_1");
           proxy.registerBlockTexture(Tuto.tutoStateBoolInteger, 2, "tuto_block_boolinteger_2");
           proxy.registerBlockTexture(Tuto.tutoStateBoolInteger, 3, "tuto_block_boolinteger_3");
    
           ModelBakery.addVariantName(Item.getItemFromBlock(Tuto.tutoStateDirection), "tuto:tuto_block_direction_west", "tuto:tuto_block_direction_east", "tuto:tuto_block_direction_north", "tuto:tuto_block_direction_south");
           proxy.registerBlockTexture(Tuto.tutoStateDirection, 0, "tuto_block_direction_west");
           proxy.registerBlockTexture(Tuto.tutoStateDirection, 1, "tuto_block_direction_east");
           proxy.registerBlockTexture(Tuto.tutoStateDirection, 2, "tuto_block_direction_north");
           proxy.registerBlockTexture(Tuto.tutoStateDirection, 3, "tuto_block_direction_south");
    
           ModelBakery.addVariantName(Item.getItemFromBlock(Tuto.tutoStateAxis), "tuto:tuto_block_axis_x", "tuto:tuto_block_axis_y", "tuto:tuto_block_axis_z", "tuto:tuto_block_axis_none");
           proxy.registerBlockTexture(Tuto.tutoStateAxis, 0, "tuto_block_axis_x");
           proxy.registerBlockTexture(Tuto.tutoStateAxis, 1, "tuto_block_axis_y");
           proxy.registerBlockTexture(Tuto.tutoStateAxis, 2, "tuto_block_axis_z");
           proxy.registerBlockTexture(Tuto.tutoStateAxis, 3, "tuto_block_axis_none");
    
           ModelBakery.addVariantName(Item.getItemFromBlock(Tuto.tutoStateMulti), "tuto:tuto_block_multi_black_0_false", "tuto:tuto_block_multi_black_1_false", "tuto:tuto_block_multi_black_2_false", "tuto:tuto_block_multi_black_3_false", "tuto:tuto_block_multi_white_0_false", "tuto:tuto_block_multi_white_1_false", "tuto:tuto_block_multi_white_2_false", "tuto:tuto_block_multi_white_3_false", "tuto:tuto_block_multi_black_0_true", "tuto:tuto_block_multi_black_1_true", "tuto:tuto_block_multi_black_2_true", "tuto:tuto_block_multi_black_3_true", "tuto:tuto_block_multi_white_0_true", "tuto:tuto_block_multi_white_1_true", "tuto:tuto_block_multi_white_2_true", "tuto:tuto_block_multi_white_3_true");
           proxy.registerBlockTexture(Tuto.tutoStateMulti, 0, "tuto_block_multi_black_0_false");
           proxy.registerBlockTexture(Tuto.tutoStateMulti, 1, "tuto_block_multi_black_1_false");
           proxy.registerBlockTexture(Tuto.tutoStateMulti, 2, "tuto_block_multi_black_2_false");
           proxy.registerBlockTexture(Tuto.tutoStateMulti, 3, "tuto_block_multi_black_3_false");
           proxy.registerBlockTexture(Tuto.tutoStateMulti, 4, "tuto_block_multi_black_0_true");
           proxy.registerBlockTexture(Tuto.tutoStateMulti, 5, "tuto_block_multi_black_1_true");
           proxy.registerBlockTexture(Tuto.tutoStateMulti, 6, "tuto_block_multi_black_2_true");
           proxy.registerBlockTexture(Tuto.tutoStateMulti, 7, "tuto_block_multi_black_3_true");      
           proxy.registerBlockTexture(Tuto.tutoStateMulti, 8, "tuto_block_multi_white_0_false");
           proxy.registerBlockTexture(Tuto.tutoStateMulti, 9, "tuto_block_multi_white_1_false");
           proxy.registerBlockTexture(Tuto.tutoStateMulti, 10, "tuto_block_multi_white_2_false");
           proxy.registerBlockTexture(Tuto.tutoStateMulti, 11, "tuto_block_multi_white_3_false");
           proxy.registerBlockTexture(Tuto.tutoStateMulti, 12, "tuto_block_multi_white_0_true");
           proxy.registerBlockTexture(Tuto.tutoStateMulti, 13, "tuto_block_multi_white_1_true");
           proxy.registerBlockTexture(Tuto.tutoStateMulti, 14, "tuto_block_multi_white_2_true");
           proxy.registerBlockTexture(Tuto.tutoStateMulti, 15, "tuto_block_multi_white_3_true");
    

    Ou dans le proxy (client) :

           ModelBakery.addVariantName(Item.getItemFromBlock(Tuto.tutoStateEnum), "tuto:tuto_block_enum_variant_1", "tuto:tuto_block_enum_variant_2");
           registerBlockTexture(Tuto.tutoStateEnum, 0, "tuto_block_enum_variant_1");
           registerBlockTexture(Tuto.tutoStateEnum, 1, "tuto_block_enum_variant_2");
    
           ModelBakery.addVariantName(Item.getItemFromBlock(Tuto.tutoStateBool1), "tuto:tuto_block_bool1_false", "tuto:tuto_block_bool1_true");
           registerBlockTexture(Tuto.tutoStateBool1, 8, "tuto_block_bool1_true");
           registerBlockTexture(Tuto.tutoStateBool1, 0, "tuto_block_bool1_false");
    
           registerBlockTexture(Tuto.tutoStateBool, "tuto_block_bool");
    
           ModelBakery.addVariantName(Item.getItemFromBlock(Tuto.tutoStateInteger), "tuto:tuto_block_integer_0", "tuto:tuto_block_integer_1", "tuto:tuto_block_integer_2", "tuto:tuto_block_integer_3");
           registerBlockTexture(Tuto.tutoStateInteger, 0, "tuto_block_integer_0");
           registerBlockTexture(Tuto.tutoStateInteger, 1, "tuto_block_integer_1");
           registerBlockTexture(Tuto.tutoStateInteger, 2, "tuto_block_integer_2");
           registerBlockTexture(Tuto.tutoStateInteger, 3, "tuto_block_integer_3");
    
           ModelBakery.addVariantName(Item.getItemFromBlock(Tuto.tutoStateBoolEnum), "tuto:tuto_block_boolenum_false_var1", "tuto:tuto_block_boolenum_false_var2", "tuto:tuto_block_boolenum_true_var1", "tuto:tuto_block_boolenum_true_var2");
           registerBlockTexture(Tuto.tutoStateBoolEnum, 0, "tuto_block_boolenum_false_var1");
           registerBlockTexture(Tuto.tutoStateBoolEnum, 1, "tuto_block_boolenum_false_var2");
           registerBlockTexture(Tuto.tutoStateBoolEnum, 8, "tuto_block_boolenum_true_var1");
           registerBlockTexture(Tuto.tutoStateBoolEnum, 9, "tuto_block_boolenum_true_var2");
    
           ModelBakery.addVariantName(Item.getItemFromBlock(Tuto.tutoStateBoolInteger), "tuto:tuto_block_boolinteger_0", "tuto:tuto_block_boolinteger_1", "tuto:tuto_block_boolinteger_2", "tuto:tuto_block_boolinteger_3");
           registerBlockTexture(Tuto.tutoStateBoolInteger, 0, "tuto_block_boolinteger_0");
           registerBlockTexture(Tuto.tutoStateBoolInteger, 1, "tuto_block_boolinteger_1");
           registerBlockTexture(Tuto.tutoStateBoolInteger, 2, "tuto_block_boolinteger_2");
           registerBlockTexture(Tuto.tutoStateBoolInteger, 3, "tuto_block_boolinteger_3");
    
           ModelBakery.addVariantName(Item.getItemFromBlock(Tuto.tutoStateDirection), "tuto:tuto_block_direction_west", "tuto:tuto_block_direction_east", "tuto:tuto_block_direction_north", "tuto:tuto_block_direction_south");
           registerBlockTexture(Tuto.tutoStateDirection, 0, "tuto_block_direction_west");
           registerBlockTexture(Tuto.tutoStateDirection, 1, "tuto_block_direction_east");
           registerBlockTexture(Tuto.tutoStateDirection, 2, "tuto_block_direction_north");
           registerBlockTexture(Tuto.tutoStateDirection, 3, "tuto_block_direction_south");
    
           ModelBakery.addVariantName(Item.getItemFromBlock(Tuto.tutoStateAxis), "tuto:tuto_block_axis_x", "tuto:tuto_block_axis_y", "tuto:tuto_block_axis_z", "tuto:tuto_block_axis_none");
           registerBlockTexture(Tuto.tutoStateAxis, 0, "tuto_block_axis_x");
           registerBlockTexture(Tuto.tutoStateAxis, 1, "tuto_block_axis_y");
           registerBlockTexture(Tuto.tutoStateAxis, 2, "tuto_block_axis_z");
           registerBlockTexture(Tuto.tutoStateAxis, 3, "tuto_block_axis_none");
    
           ModelBakery.addVariantName(Item.getItemFromBlock(Tuto.tutoStateMulti), "tuto:tuto_block_multi_black_0_false", "tuto:tuto_block_multi_black_1_false", "tuto:tuto_block_multi_black_2_false", "tuto:tuto_block_multi_black_3_false", "tuto:tuto_block_multi_white_0_false", "tuto:tuto_block_multi_white_1_false", "tuto:tuto_block_multi_white_2_false", "tuto:tuto_block_multi_white_3_false", "tuto:tuto_block_multi_black_0_true", "tuto:tuto_block_multi_black_1_true", "tuto:tuto_block_multi_black_2_true", "tuto:tuto_block_multi_black_3_true", "tuto:tuto_block_multi_white_0_true", "tuto:tuto_block_multi_white_1_true", "tuto:tuto_block_multi_white_2_true", "tuto:tuto_block_multi_white_3_true");
           registerBlockTexture(Tuto.tutoStateMulti, 0, "tuto_block_multi_black_0_false");
           registerBlockTexture(Tuto.tutoStateMulti, 1, "tuto_block_multi_black_1_false");
           registerBlockTexture(Tuto.tutoStateMulti, 2, "tuto_block_multi_black_2_false");
           registerBlockTexture(Tuto.tutoStateMulti, 3, "tuto_block_multi_black_3_false");
           registerBlockTexture(Tuto.tutoStateMulti, 4, "tuto_block_multi_black_0_true");
           registerBlockTexture(Tuto.tutoStateMulti, 5, "tuto_block_multi_black_1_true");
           registerBlockTexture(Tuto.tutoStateMulti, 6, "tuto_block_multi_black_2_true");
           registerBlockTexture(Tuto.tutoStateMulti, 7, "tuto_block_multi_black_3_true");      
           registerBlockTexture(Tuto.tutoStateMulti, 8, "tuto_block_multi_white_0_false");
           registerBlockTexture(Tuto.tutoStateMulti, 9, "tuto_block_multi_white_1_false");
           registerBlockTexture(Tuto.tutoStateMulti, 10, "tuto_block_multi_white_2_false");
           registerBlockTexture(Tuto.tutoStateMulti, 11, "tuto_block_multi_white_3_false");
           registerBlockTexture(Tuto.tutoStateMulti, 12, "tuto_block_multi_white_0_true");
           registerBlockTexture(Tuto.tutoStateMulti, 13, "tuto_block_multi_white_1_true");
           registerBlockTexture(Tuto.tutoStateMulti, 14, "tuto_block_multi_white_2_true");
           registerBlockTexture(Tuto.tutoStateMulti, 15, "tuto_block_multi_white_3_true");
    

    Grâce à ceci, on enregistre le model de notre bloc en fonction de la metadata.

    Les noms et les textures :

    Pour les noms, c'est très simple. Les blocs où l'on voudra qu'ils aient un nom différent entre chaque state possèdent la fonction : "getUnlocalizedName(int metadata)". Dans cette fonction allez voir le return. Ensuite, dans votre fichier lang. Dans le fichier lang mettez :

    tile.

    Juste après le tile. il vous suffira de mettre le nom de chaque metadata. Pour mon bloc TutoStateEnum, le String qui est retourné par la fonction est :

    super.getUnlocalizedName() + "." + EnumType.getStateFromMeta(metadata).getName();
    

    Décomposez c'est donc le nom du bloc (par le setUnlocalizedName(name)), suivi d'un "." et du nom par raport à la metadata. Soit : "tuto_block_enum.variant_1" pour la metadata de la variant_1 et "tuto_block_enum.variant_2" pour la seconde variante. Enfin, après ceci ajoutez :

    .name=

    Et juste après le "=" le nom que vous voulez. Vous pouvez, en cas d'erreur, regarder le nom du bloc en jeu, car on a le nom brute qui vient du getUnlocalizedName() (donc vous pouvez mettre le nom que vous avez en jeu suivi de "=" puis le vrai nom). Pour mon bloc des enum :

    tile.tuto_block_enum.variant_2.name=Tuto Block Enum Var1
    tile.tuto_block_enum.variant_2.name=Tuto Block Enum Var2

    Donc en jeu mes noms seront "Tuto Block Enum Var1" et "Tuto Block Enum Var2". N'oubliez pas qu'on fait ceci uniquement pour les blocs qui auront des noms différents en fonction de leur metadata. Sinon c'est la même méthode que d'habitude (Pour savoir ceux qui sont normaux, c'est ceux qui n'ont pas la fonction getUnlocalizedName(metadata) et que nous n'avons pas enregistré avec le ItemBlock). Voici le contenu de mon .lang pour tous mes blocs (à titre d'exemple pour vous aider):
    :::

    tile.tuto_block_enum.variant_1.name=Tuto Block Enum Var1
    tile.tuto_block_enum.variant_2.name=Tuto Block Enum Var2

    tile.tuto_block_bool1.true.name=Tuto Block Bool1 True
    tile.tuto_block_bool1.false.name=Tuto Block Bool1 False

    tile.tuto_block_bool.name=Tuto Block Bool

    tile.tuto_block_integer.0.name=Tuto Block Integer 0
    tile.tuto_block_integer.1.name=Tuto Block Integer 1
    tile.tuto_block_integer.2.name=Tuto Block Integer 2
    tile.tuto_block_integer.3.name=Tuto Block Integer 3

    tile.tuto_block_boolenum.variant_1.true.name=Tuto Block BoolEnum Var1 True
    tile.tuto_block_boolenum.variant_1.false.name=Tuto Block BoolEnum Var1 False
    tile.tuto_block_boolenum.variant_2.true.name=Tuto Block BoolEnum Var2 True
    tile.tuto_block_boolenum.variant_2.false.name=Tuto Block BoolEnum Var2 False

    tile.tuto_block_boolinteger.0.true.name=Tuto Block BoolInteger 0 True
    tile.tuto_block_boolinteger.0.false.name=Tuto Block BoolInteger 0 False
    tile.tuto_block_boolinteger.1.true.name=Tuto Block BoolInteger 1 True
    tile.tuto_block_boolinteger.1.false.name=Tuto Block BoolInteger 1 False
    tile.tuto_block_boolinteger.2.true.name=Tuto Block BoolInteger 2 True
    tile.tuto_block_boolinteger.2.false.name=Tuto Block BoolInteger 2 False
    tile.tuto_block_boolinteger.3.true.name=Tuto Block BoolInteger 3 True
    tile.tuto_block_boolinteger.3.false.name=Tuto Block BoolInteger 3 False

    tile.tuto_block_direction.name=Tuto Block Direction

    tile.tuto_block_axis.name=Tuto Block Axis

    tile.tuto_block_multi.black.name=Tuto Block Multi Black
    tile.tuto_block_multi.white.name=Tuto Block Multi White

    :::
    Maintenant, nous avons nos noms qui sont enregistrés. Cependant, nos blocs ne possèdent pas de texture. Nous allons leur en donner une. Nous allons voir qu'il y a 3 .json à créer en plus de notre texture. Je vais donc vous montrer le fonctionnement de ces .json. Je ne les ferai pas tous avec vous car j'en ai environ 100 (pour ce tutoriel). Commencez par créer un dossier "blockstates" dans le dossier "assets/modid", dans le répertoire "assets/modid", créez un autre dossier "models" avec dedans deux sous-dossiers: "block" et "item". Dans le dossier des assets toujours, créez un dossier : "textures/blocks". Maintenant, pour créer les textures d'un bloc, créez un .json dans le dossier blockstates avec pour nom le paramètre String (name) du register de votre bloc (GameRegistry.registerBlock(tutoStateEnum, ItemBlockTutoState.class, "tuto_block_enum");) Dès lors que votre bloc aura des states, l'organisation du .json sera pareil pour tous.

    {
       "variants": {
           "variant=variant_1": {"model": "tuto:tuto_block_enum_variant_1" },
           "variant=variant_2": {"model": "tuto:tuto_block_enum_variant_2" }
       }
    }
    

    Donc, ceci est le contenu du .json, dans les variants, vous aurez à mettre, le nom de votre property suivi de "=" + votre valeur ici : "variant=variant_1". Ensuite dans les accolades, le "model" sert à définir quel est le model du bloc, dans les guillemets, le chemin du model. Pour l'accès du model oubliez pas le : "modid:" + leNomDeVotreJson.

    À titre d’exemple voici le json du multistate :

    {
       "variants": {
    
           "color=0,dark=black,powered=true": {"model": "tuto:tuto_block_multi_black_0_true" },
           "color=1,dark=black,powered=true": {"model": "tuto:tuto_block_multi_black_1_true" },
           "color=2,dark=black,powered=true": {"model": "tuto:tuto_block_multi_black_2_true" },
           "color=3,dark=black,powered=true": {"model": "tuto:tuto_block_multi_black_3_true" },
           "color=0,dark=black,powered=false": {"model": "tuto:tuto_block_multi_black_0_false" },
           "color=1,dark=black,powered=false": {"model": "tuto:tuto_block_multi_black_1_false" },
           "color=2,dark=black,powered=false": {"model": "tuto:tuto_block_multi_black_2_false" },
           "color=3,dark=black,powered=false": {"model": "tuto:tuto_block_multi_black_3_false" },
           "color=0,dark=white,powered=false": {"model": "tuto:tuto_block_multi_white_0_false" },
           "color=1,dark=white,powered=false": {"model": "tuto:tuto_block_multi_white_1_false" },
           "color=2,dark=white,powered=false": {"model": "tuto:tuto_block_multi_white_2_false" },
           "color=3,dark=white,powered=false": {"model": "tuto:tuto_block_multi_white_3_false" },
           "color=0,dark=white,powered=true": {"model": "tuto:tuto_block_multi_white_0_true" },
           "color=1,dark=white,powered=true": {"model": "tuto:tuto_block_multi_white_1_true" },
           "color=2,dark=white,powered=true": {"model": "tuto:tuto_block_multi_white_2_true" },
           "color=3,dark=white,powered=true": {"model": "tuto:tuto_block_multi_white_3_true" }
       }
    }
    

    Maintenant allez créer un json dans le dossier "models/block" puis créez un fichier avec pour nom, le nom d’accès dans le fichier json du dossier blockstate. (il faut le faire pour toutes les states). Pour la variant_1 de mon bloc des enum, mon fichier se nommera "tuto_block_enum_variant_1". Dans ce fichier nous allons mettre :

    {
       "parent": "block/cube_all",
       "textures": {
               "all": "tuto:blocks/block_enum_var1"
       }
    }
    

    La ligne parent sera sur quoi le model du bloc se base, pour celui-ci ce sera le model "cube_all" de minecraft (le "blocks/" car le dossier pris en compte est "models/", donc ça permet de signifier qu'on se trouve dans le dossier "blocks"). Ensuite, dans le bloc texture nous définissons la texture, la ligne "all" permet de mettre la texture pour toutes les faces. Notre texture se trouvera dans le répertoire : "textures/blocks/NomDeLaTexture", n'oubliez pas de mettre "modid:" pour dire qu'on se trouve dans vos assets. Pour ce bloc la texture sera nommée "block_enum_Dvar1", notez qu'il ne faut pas mettre les extensions ".png" dans les json. Après cela, dans le répertoire "models/item" mettez un json du même nom que le json du dossier "models/block" (regardez aussi quel est le nom que vous avez mis dans l'enregistrement des textures et modifiez le s'il doit l'être). Dans ce json ajoutez :

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

    Déjà, "parent", tout comme au-dessus, signifie la base du model. Ensuite, dans le bloc display on trouve toutes les options de l'affichage. Pour plus de détails allez voir sur des tutoriels à propos du sujet. Allez dans votre dossier des textures et créez la texture (.png) avec pour nom celui que vous avez mis dans le bloc texture du json. Sachez que pour que minecraft lise la texture il faut qu'elle soit à un format d'une base de deux (22, 44, 88, 1616, 32*32…). Maintenant, lancez votre jeu et admirez votre bloc.

    Avant de passer à la suite nous allons faire un petit bilan :

    Bonus

    Nous voilà au bonus ! Normalement si vous êtes là c'est que vous n'avez pas rencontré de problème. Venons en au sujet principal qui répondra aussi à la question : "Qu'allons-nous faire ?", j'y répondrai assez simplement, on va alléger notre code. Vous avez dû remarquer à quel point il est pénible de faire les ItemBlock (non on n'allègera pas les json, mais sachez qu'il existe des logiciels pour ça). Pour se faire, créez une interface :

    public interface ITutoBlockState
    {
    
    }
    

    ajoutez :

    public String getUnlocalizedName(int metadata);
    

    Maintenant, dans toutes les classes des blocs qui sont enregistrées avec l'ItemBlockCustom implementez cette interface. Vous ne devriez pas avoir de problème car les blocs qui utilisent un ItemBlockCustom possèdent la fonction getUnlocalizedName(meta).

    implements ITutoBlockState
    

    Nous allons maintenant créer un ItemBlock à nous et qui sera utilisé pour tous vos blocs. Je ne remontre pas les bases.

    private ITutoBlockState blockState;
    

    Une fois que vous avez déclaré votre variable, créez la fonction :

       @Override
       public String getUnlocalizedName(ItemStack stack)
       {
           blockState = (ITutoBlockState)this.block;
           return blockState.getUnlocalizedName(stack.getMetadata());
       }
    

    Maintenant, vous avez un seul ItemBlock et vous n'avez plus à vous embêter.

    Résultat



    Crédits

    Rédaction :

    Correction :

    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



  • Merci pour ce tuto. À première vue, il fait très peur de par sa taille, mais au final vu que ça détaille toutes les possibilités, ce n'est pas étonnant 🙂

    Il y a juste une chose qui est à corriger, lors des proxys (en tout cas en 1.8.9, Forge 1.8.9-11.15.1.1722):

    ModelBakery.addVariantName(Item.getItemFromBlock(Tuto.tutoStateEnum), "tuto:tuto_block_enum_variant_1", "tuto:tuto_block_enum_variant_2");
    

    addVariantName est devenu obsolète et a été remplacé par registerItemVariants, et on doit utiliser new ResourceLocation("nom") pour les noms. Ce qu donne, si je n'ai pas fait de bêtise:

    ModelBakery.registerItemVariants(Item.getItemFromBlock(Tuto.tutoStateEnum), new ResourceLocation("tuto:tuto_block_enum_variant_1"), new ResourceLocation("tuto:tuto_block_enum_variant_2"));
    

    Chez moi, ça marche!


Log in to reply