Connected Block Textures
This tutorial will show you how to create a block with connected textures. This tutorial expects that you are already familiar with basic mod development and you have some experience working with block states. Creating a connected texture block is surprisingly straight forward so this should be accessible to beginners.
Creating the Block
This method of connected block textures makes use of the vanilla BlockState/Property system. This system is used by vanilla to store data about the block, things like the color of wool or the on/off state of a lever. The majority of these properties are stored in the metadata of the block, however you can also have unlisted properties that are based on conditions in the world. An example of this in Vanilla would be grass using the snowy texture when there is a snow block above it, or fences using the connected model when placed next to another fence. Both of those properties are not stored in the metadata of the block.
For the connected texture block we use six of these unlisted properties to create the effect. Each property is a boolean that represents a face of the block. When a block is connected to another one on that side, the property should return true. You can define these properties in your block class, however if you plan to have multiple connected block textures I would recommend placing them in a public class that can be accessed by all of your blocks. Bellow are the properties that I use in my block. I would advise against changing the names of the properties, as they are important later in the tutorial.
For the connected texture block we use six of these unlisted properties to create the effect. Each property is a boolean that represents a face of the block. When a block is connected to another one on that side, the property should return true. You can define these properties in your block class, however if you plan to have multiple connected block textures I would recommend placing them in a public class that can be accessed by all of your blocks. Bellow are the properties that I use in my block. I would advise against changing the names of the properties, as they are important later in the tutorial.
public static final PropertyBool CONNECTED_DOWN = PropertyBool.create("connected_down"); public static final PropertyBool CONNECTED_UP = PropertyBool.create("connected_up"); public static final PropertyBool CONNECTED_NORTH = PropertyBool.create("connected_north"); public static final PropertyBool CONNECTED_SOUTH = PropertyBool.create("connected_south"); public static final PropertyBool CONNECTED_WEST = PropertyBool.create("connected_west"); public static final PropertyBool CONNECTED_EAST = PropertyBool.create("connected_east");
To get the block to use the new properties in it's blockstate, you have to add them to the default state. This can be done in your constructor by calling Block#setDefaultState. By default the block should not be connected to any other blocks, so you can set all properties to false. Below is an example of how this would look.
this.setDefaultState(this.blockState.getBaseState().withProperty(CONNECTED_DOWN, Boolean.FALSE).withProperty(CONNECTED_EAST, Boolean.FALSE).withProperty(CONNECTED_NORTH, Boolean.FALSE).withProperty(CONNECTED_SOUTH, Boolean.FALSE).withProperty(CONNECTED_UP, Boolean.FALSE).withProperty(CONNECTED_WEST, Boolean.FALSE));
Now that your block is using state data that is not the default, the game will expect you to override several methods to provide the appropriate information. The first method that you will need to override is the Block#createBlockState method. The altered version of this method should return a new BlockStateContainer which includes all six of the properties we created earlier. You will also need to override Block#getMetaFromState to return the appropriate meta value. Given that these properties are not stored in the meta you can return 0. If your block actually uses metadata you can return the correct meta value instead. Below is an example of the overridden methods should look.
@Override protected BlockStateContainer createBlockState () { return new BlockStateContainer(this, new IProperty[] { CONNECTED_DOWN, CONNECTED_UP, CONNECTED_NORTH, CONNECTED_SOUTH, CONNECTED_WEST, CONNECTED_EAST }); } @Override public int getMetaFromState (IBlockState state) { return 0; }
The final step to creating a connected texture block is to override the Block#getActualState method. This is where magic happens, and where the boolean properties for an individual block are turned on or off. If a side is touching a block that can be connected to, then the property should be set to true. This can be quite messy so I would recommend using a method that does the check for you and returns true or false. I have already created a method that does this, which everyone is free to use.
@Override public IBlockState getActualState (IBlockState state, IBlockAccess world, BlockPos position) { return state.withProperty(CONNECTED_DOWN, this.isSideConnectable(world, position, EnumFacing.DOWN)).withProperty(CONNECTED_EAST, this.isSideConnectable(world, position, EnumFacing.EAST)).withProperty(CONNECTED_NORTH, this.isSideConnectable(world, position, EnumFacing.NORTH)).withProperty(CONNECTED_SOUTH, this.isSideConnectable(world, position, EnumFacing.SOUTH)).withProperty(CONNECTED_UP, this.isSideConnectable(world, position, EnumFacing.UP)).withProperty(CONNECTED_WEST, this.isSideConnectable(world, position, EnumFacing.WEST)); } /** * Checks if a specific side of a block can connect to this block. For this example, a side * is connectable if the block is the same block as this one. * * @param world The world to run the check in. * @param pos The position of the block to check for. * @param side The side of the block to check. * @return Whether or not the side is connectable. */ private boolean isSideConnectable (IBlockAccess world, BlockPos pos, EnumFacing side) { final IBlockState state = world.getBlockState(pos.offset(side)); return (state == null) ? false : state.getBlock() == this; }
At this point your block class should look something like this.
public class BlockConnectedTexture extends Block { public static final PropertyBool CONNECTED_DOWN = PropertyBool.create("connected_down"); public static final PropertyBool CONNECTED_UP = PropertyBool.create("connected_up"); public static final PropertyBool CONNECTED_NORTH = PropertyBool.create("connected_north"); public static final PropertyBool CONNECTED_SOUTH = PropertyBool.create("connected_south"); public static final PropertyBool CONNECTED_WEST = PropertyBool.create("connected_west"); public static final PropertyBool CONNECTED_EAST = PropertyBool.create("connected_east"); public BlockConnectedTexture() { super(Material.ROCK); this.setCreativeTab(CreativeTabs.BUILDING_BLOCKS); this.setDefaultState(this.blockState.getBaseState().withProperty(CONNECTED_DOWN, Boolean.FALSE).withProperty(CONNECTED_EAST, Boolean.FALSE).withProperty(CONNECTED_NORTH, Boolean.FALSE).withProperty(CONNECTED_SOUTH, Boolean.FALSE).withProperty(CONNECTED_UP, Boolean.FALSE).withProperty(CONNECTED_WEST, Boolean.FALSE)); } @Override public IBlockState getActualState (IBlockState state, IBlockAccess world, BlockPos position) { return state.withProperty(CONNECTED_DOWN, this.isSideConnectable(world, position, EnumFacing.DOWN)).withProperty(CONNECTED_EAST, this.isSideConnectable(world, position, EnumFacing.EAST)).withProperty(CONNECTED_NORTH, this.isSideConnectable(world, position, EnumFacing.NORTH)).withProperty(CONNECTED_SOUTH, this.isSideConnectable(world, position, EnumFacing.SOUTH)).withProperty(CONNECTED_UP, this.isSideConnectable(world, position, EnumFacing.UP)).withProperty(CONNECTED_WEST, this.isSideConnectable(world, position, EnumFacing.WEST)); } @Override protected BlockStateContainer createBlockState () { return new BlockStateContainer(this, new IProperty[] { CONNECTED_DOWN, CONNECTED_UP, CONNECTED_NORTH, CONNECTED_SOUTH, CONNECTED_WEST, CONNECTED_EAST }); } @Override public int getMetaFromState (IBlockState state) { return 0; } /** * Checks if a specific side of a block can connect to this block. For this example, a side * is connectable if the block is the same block as this one. * * @param world The world to run the check in. * @param pos The position of the block to check for. * @param side The side of the block to check. * @return Whether or not the side is connectable. */ private boolean isSideConnectable (IBlockAccess world, BlockPos pos, EnumFacing side) { final IBlockState state = world.getBlockState(pos.offset(side)); return (state == null) ? false : state.getBlock() == this; } }
Creating the Models
The second half of creating a block with connected textures is to create the models, textures and blockstates file. This system uses 64 different blockstates, 11 different textures and 10 different models. The 64 blockstates combine the different textures and models together to create the different model variants that will show up in game. Creating all of these files by hand would be absurd, so I have created a small java application that can be used to generate everything for you. The program is called Furnace, and allows you to generate all these files near instantaneously. You can download this tool from here. If you are curious about the inner workings of these files, or want to do this by hand for some insane reason, all of the default files can be found here.
The tool is very easy to use, extract the contents of the Zip file and open up your command terminal in the same directory. Once you have done that, you can run the following command and it will generate the files for you. `java -jar Furnace.jar MODID TEXTURENAME`. You want to replace MODID with the ID of your mod. This value is used to refer to your textures and models in the assets folder. For example, assets/MODID/blockstates would be generated if you ran the command with MODID. You also want to replace TEXTURENAME with the name of your block's texture. I would highly recommend using the same value here as the one used to register your block. The reason being that the blockstates file generated will use this name, and vanilla searches for the blockstates file with the same name as the registry name.
When you run the command all of the files will show up in the output folder, and if you entered the correct MODID and TEXTURENAME values you should be able to directly copy and paste the generated files into your mod's resources folder. After that, you can begin editing or replacing the default generated textures with your own. Make sure the file names for the textures are not changed, and the correct texture is being used.
If your block has a missing item texture while in the inventory, this means that you have not set an inventory model for the block. This can be done in the same way you would set the inventory model for any block. The connected texture has nothing special about it when it comes to item textures.
Good luck with your connected block textures! If this tutorial worked for you, please consider sharing some screenshots with me on Twitter.Would love to see how people are using this tutorial :)
The tool is very easy to use, extract the contents of the Zip file and open up your command terminal in the same directory. Once you have done that, you can run the following command and it will generate the files for you. `java -jar Furnace.jar MODID TEXTURENAME`. You want to replace MODID with the ID of your mod. This value is used to refer to your textures and models in the assets folder. For example, assets/MODID/blockstates would be generated if you ran the command with MODID. You also want to replace TEXTURENAME with the name of your block's texture. I would highly recommend using the same value here as the one used to register your block. The reason being that the blockstates file generated will use this name, and vanilla searches for the blockstates file with the same name as the registry name.
When you run the command all of the files will show up in the output folder, and if you entered the correct MODID and TEXTURENAME values you should be able to directly copy and paste the generated files into your mod's resources folder. After that, you can begin editing or replacing the default generated textures with your own. Make sure the file names for the textures are not changed, and the correct texture is being used.
If your block has a missing item texture while in the inventory, this means that you have not set an inventory model for the block. This can be done in the same way you would set the inventory model for any block. The connected texture has nothing special about it when it comes to item textures.
Good luck with your connected block textures! If this tutorial worked for you, please consider sharing some screenshots with me on Twitter.Would love to see how people are using this tutorial :)
Disclaimer for the above textures, I am just bad at texturing, which is why the corner texture in some places is missing a pixel. It is not due to a limitation of this method.