These are the steps I go through to add a new block in Minecraft using Forge. Some blocks are more complicated than others, so there will usually be some variation depending on what exactly you want the block to be able to do.
Checklist
- Create the Block
- Implement Never methods
- Register the Block
- Register the Block Item
- Create the Block State
- Create the Block’s Model
- Create the Block’s Textures
- Add Localisation String(s)
- Add a Loot Table
Block
First, you need a block class for your block. Depending on what you want your block to do you might be able to use a vanilla block class, but any custom behaviour will need its own class. This class must extend Block
or one of its subclasses in order to work.
As an example, the flower crop block extends Minecraft’s CropBlock
to support growing flowers on grass:
/** * Represents a crop that grows into a flower. */ public class FlowerCropBlock extends CropBlock { // The shape of the flower. private static final VoxelShape[] SHAPE_BY_AGE; // The flower block the crop will grow into. private final Block flowerBlock; /** * Construction - copy the properties from the flower it will grow into. * @param block The flower block. */ public FlowerCropBlock(Block block) { super(BlockBehaviour.Properties.copy(block)); this.flowerBlock = block; } /** * Flower crops cannot be cloned or placed, even in creative mode. * @param blockGetter The block getter. * @param blockPos The block position. * @param blockState The block state. * @return An empty item stack. */ @NotNull @Override public ItemStack getCloneItemStack(@NotNull BlockGetter blockGetter, @NotNull BlockPos blockPos, @NotNull BlockState blockState) { return ItemStack.EMPTY; } /** * Get the shape of the flower crop. * @param blockState The block state. * @param blockGetter The block getter. * @param blockPos The block position. * @param collisionContext The collision context. * @return The shape of the crop based on its age. */ @NotNull @Override public VoxelShape getShape(@NotNull BlockState blockState, @NotNull BlockGetter blockGetter, @NotNull BlockPos blockPos, @NotNull CollisionContext collisionContext) { Vec3 offset = blockState.getOffset(blockGetter, blockPos); return SHAPE_BY_AGE[this.getAge(blockState)].move(offset.x, offset.y, offset.z); } /** * Gets the block state based on the age of the crop. Will convert to a * flower when it is fully grown. * @param age The age of the crop. * @return The updated block state. */ @NotNull @Override public BlockState getStateForAge(int age) { return age == MAX_AGE ? this.flowerBlock.defaultBlockState() : super.getStateForAge(age); } /** * FLower buds can be placed on grass blocks. * @param blockState The block state. * @param blockGetter The block getter. * @param blockPos The block position. * @return TRUE if the crop is on a grass block. */ @Override protected boolean mayPlaceOn(BlockState blockState, @NotNull BlockGetter blockGetter, @NotNull BlockPos blockPos) { return blockState.is(Blocks.GRASS_BLOCK); } // Defines the shape of the crop for each age. static { SHAPE_BY_AGE = new VoxelShape[] { Block.box(5.0, 0.0, 5.0, 11.0, 2.0, 11.0), Block.box(5.0, 0.0, 5.0, 11.0, 4.0, 11.0), Block.box(5.0, 0.0, 5.0, 11.0, 6.0, 11.0), Block.box(5.0, 0.0, 5.0, 11.0, 8.0, 11.0), Block.box(5.0, 0.0, 5.0, 11.0, 10.0, 11.0), Block.box(5.0, 0.0, 5.0, 11.0, 10.0, 11.0), Block.box(5.0, 0.0, 5.0, 11.0, 10.0, 11.0), Block.box(5.0, 0.0, 5.0, 11.0, 10.0, 11.0) }; } }
Never
Some blocks need to pass in a method to certain block properties. Often you just want to say never
, but these methods are private in Forge/Minecraft. Luckily, they are simple enough to recreate:
/** * Helper method for the "never" attribute. Used in block properties during * block construction. * @param ignoredBlockState The current block state. * @param ignoredBlockGetter Access to the block. * @param ignoredBlockPos The block's position. * @param ignoredEntityType The entity type trying to spawn. * @return Always FALSE. */ public static boolean never(BlockState ignoredBlockState, BlockGetter ignoredBlockGetter, BlockPos ignoredBlockPos, EntityType<?> ignoredEntityType) { return false; } /** * Helper method for the "never" attribute. Used in block properties during * block construction. * @param ignoredBlockState The current block state. * @param ignoredBlockGetter Access to the block. * @param ignoredBlockPos The block's position. * @return Always FALSE. */ public static boolean never(BlockState ignoredBlockState, BlockGetter ignoredBlockGetter, BlockPos ignoredBlockPos) { return false; }
These are just the ones I’ve come across so far – there may be more depending on the type of block properties you want to use.
Registry
The block needs to be registered with Forge’s registries, as with everything else. A basic registry for blocks that supports some flower crops might look like this:
/** * Registers the blocks used by the mod. */ public class BlockRegistry { // An instance of a deferred registry we use to register items. private final DeferredRegister<Block> deferredRegister; // Flower Buds private RegistryObject<Block> alliumBud; private RegistryObject<Block> azureBluetBud; /** * Construction * @param modEventBus The event bus to register with. */ public BlockRegistry(IEventBus modEventBus) { this.deferredRegister = DeferredRegister.create(ForgeRegistries.BLOCKS, ButterfliesMod.MOD_ID); this.deferredRegister.register(modEventBus); } /** * Register the blocks. * @param blockEntityTypeRegistry The block entity registry. * @param menuTypeRegistry The menu type registry. */ public void initialise(BlockEntityTypeRegistry blockEntityTypeRegistry, MenuTypeRegistry menuTypeRegistry) { this.alliumBud = deferredRegister.register( "bud_allium", () -> new FlowerCropBlock(Blocks.ALLIUM) ); this.azureBluetBud = deferredRegister.register( "bud_azure_bluet", () -> new FlowerCropBlock(Blocks.AZURE_BLUET) ); } /** * Allium bud accessor. * @return The registry object. */ public RegistryObject<Block> getAlliumBud() { return alliumBud; } /** * Azure bluet bud accessor. * @return The registry object. */ public RegistryObject<Block> getAzureBluetBud() { return azureBluetBud; } }
Block Item
If you want players to be able to collect your block, you need an item to represent it. The easiest way to do this is to use the BlockItem
class when registering your item. Of course, if you don’t want players to be able to pick up the block, this step can be skipped.
Bok’s Banging Butterflies uses the BlockItem
to register the butterfly feeder:
/** * This class registers items with Forge's Item Registry */ public class ItemRegistry { // An instance of a deferred registry we use to register items. private final DeferredRegister<Item> deferredRegister; // Other registry references private BlockRegistry blockRegistry; private EntityTypeRegistry entityTypeRegistry; // Registry Items private RegistryObject<Item> butterflyFeeder; /** * Construction * @param modEventBus The event bus to register with. */ public ItemRegistry(IEventBus modEventBus) { this.deferredRegister = DeferredRegister.create(ForgeRegistries.ITEMS, ButterfliesMod.MOD_ID); this.deferredRegister.register(modEventBus); } /** * Register the items. * @param blockRegistry The block registry. * @param entityTypeRegistry The entity type registry. */ public void initialise(BlockRegistry blockRegistry, EntityTypeRegistry entityTypeRegistry) { this.butterflyFeeder =deferredRegister.register("butterfly_feeder", () -> new BlockItem(blockRegistry.getButterflyFeeder().get(), new Item.Properties())); } /** * Accessor for the butterfly feeder. * @return The registry object. */ public RegistryObject<Item> getButterflyFeeder() { return butterflyFeeder; } }
Creative Tab
If you want the block to be available in Creative Mode, don’t forget to add it’s item to the creative menu by listening for a BuildCreativeModeTabContentsEvent
.
/** * Registers items with the relevant creative tab * @param event The event information */ public void onBuildCreativeModeTabContents(BuildCreativeModeTabContentsEvent event) { if (event.getTabKey() == CreativeModeTabs.FUNCTIONAL_BLOCKS) { event.accept(itemRegistry.getButterflyFeeder()); } }
Block States
Every model has a set of block states that tell it what model to use. These are placed under /resources/assets/<MOD_ID>/blockstates/
. The simplest block state simply references a model for the block to use:
{ "variants": { "": { "model": "butterflies:block/bottle" } } }
A more complex block state may take into account (e.g.) the age of a crop to decide which model to use:
{ "variants": { "age=0": { "model": "butterflies:block/flower_buds/allium_stage0" }, "age=1": { "model": "butterflies:block/flower_buds/allium_stage1" }, "age=2": { "model": "butterflies:block/flower_buds/allium_stage2" }, "age=3": { "model": "butterflies:block/flower_buds/allium_stage3" }, "age=4": { "model": "butterflies:block/flower_buds/allium_stage4" }, "age=5": { "model": "butterflies:block/flower_buds/allium_stage5" }, "age=6": { "model": "butterflies:block/flower_buds/allium_stage6" } } }
Model
A block’s model tells it how to render in game. You will find these under /resources/assets/<MOD_ID>/models/blocks/
. The easiest way to create a model is to inherit from a vanilla model and update it to use a custom texture:
{ "render_type": "minecraft:cutout", "parent": "minecraft:block/cross", "textures": { "cross": "butterflies:block/flower_bud/allium_stage0" } }
If you want a more complex model, Blockbench can help you create the model and export it in a JSON file that you can drop into the models folder and use right away. Collision detection and rendering may need to be updated in the block’s implementation to support this. Make sure that getRenderShape()
is overridden and returns RenderShape.MODEL
, and that getShape()
is overridden and returns the correct shape for the block.
These are the methods used for the bottled butterflies and caterpillars:
// The bottle's "model". private static final VoxelShape SHAPE = Shapes.or( Block.box(5.0, 0.0, 5.0, 10.0, 1.0, 10.0), Block.box(4.0, 1.0, 4.D, 11.0, 2.0, 11.0), Block.box(3.0, 2.0, 3.0, 12.0, 6.0, 12.0), Block.box(4.0, 6.0, 4.D, 11.0, 7.0, 11.0), Block.box(5.0, 7.0, 5.0, 10.0, 8.0, 10.0), Block.box(6.0, 8.0, 6.0, 9.0, 10.0, 9.0), Block.box(5.0, 10.0, 5.0, 10.0, 12.0, 10.0), Block.box(6.0, 12.0, 6.0, 9.0, 13.0, 9.0)); /** * Get the shape of the block. * @param blockState The current block state. * @param blockGetter Access to the block. * @param position The block's position. * @param collisionContext The collision context we are fetching for. * @return The block's bounding box. */ @NotNull @Override @SuppressWarnings("deprecation") public VoxelShape getShape(@NotNull BlockState blockState, @NotNull BlockGetter blockGetter, @NotNull BlockPos position, @NotNull CollisionContext collisionContext) { return SHAPE; } /** * Tell this block to render as a normal block. * @param blockState The current block state. * @return Always MODEL. */ @Override @NotNull @SuppressWarnings("deprecation") public RenderShape getRenderShape(@NotNull BlockState blockState) { return RenderShape.MODEL; }
Textures
Textures go under /resources/assets/<MOD_ID>/textures/
. If you want to reference any new textures in your block model, they go here.
Localisation
The block’s name will use a new localisation string: block.<MOD_ID>.<BLOCK_ID>
. For example, the butterfly feeder’s entry in en_us.json
is as follows:
"block.butterflies.butterfly_feeder": "Butterfly Feeder",
Loot Table
If you want players to be able to pick up the block after they have placed it, you will need a loot table. These go under /resources/<MOD_ID>/data/loot_tables/blocks/
, and define what is dropped by a block when it is destroyed. The name of the JSON file must match the Registry ID of the block you want to create a loot table for.
To create a simple loot table that drops the block being destroyed you can use a JSON file that looks something like this:
{ "pools": [ { "bonus_rolls": 0.0, "conditions": [ { "condition": "minecraft:survives_explosion" } ], "entries": [ { "name": "butterflies:bottled_butterfly_atlas", "type": "minecraft:item" } ], "rolls": 1 } ], "random_sequence": "butterflies:blocks/bottled_butterfly_atlas", "type": "minecraft:block" }
This just gives a 100% chance that a Bottled Butterfly (Atlas Moth) is dropped when the block is destroyed.