PREREQUISITES
Make sure you've completed the datagen setup process first.
Setup
First, we will need to create our ModelProvider. Create a class that extends FabricModelProvider. Implement both abstract methods: generateBlockStateModels and generateItemModels. Lastly, create a constructor matching super.
java
public class FabricDocsReferenceModelProvider extends FabricModelProvider {
public FabricDocsReferenceModelProvider(FabricDataOutput output) {
super(output);
}
@Override
public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) {
}
@Override
public void generateItemModels(ItemModelGenerator itemModelGenerator) {
}
@Override
public String getName() {
return "FabricDocsReference Model Provider";
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Register this class in your DataGeneratorEntrypoint within the onInitializeDataGenerator method.
Blockstates and Block Models
java
@Override
public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) {
}1
2
3
2
3
For block models, we will primarily be focusing on the generateBlockStateModels method. Notice the parameter BlockStateModelGenerator blockStateModelGenerator - this object will be responsible for generating all the JSON files. Here are some handy examples you can use to generate your desired models:
Simple Cube All
java
blockStateModelGenerator.registerSimpleCubeAll(ModBlocks.STEEL_BLOCK);1
This is the most commonly used function. It generates a JSON model file for a normal cube_all block model. One texture is used for all six sides, in this case we use steel_block.
json
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "fabric-docs-reference:block/steel_block"
}
}1
2
3
4
5
6
2
3
4
5
6
It also generates a blockstate JSON file. Since we have no blockstate properties (e.g. Axis, Facing, ...), one variant is sufficient, and is used every time the block is placed.
json
{
"variants": {
"": {
"model": "fabric-docs-reference:block/steel_block"
}
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
Singletons
The registerSingleton method provides JSON model files based on the TexturedModel you pass in and a single blockstate variant.
java
blockStateModelGenerator.registerSingleton(ModBlocks.PIPE_BLOCK, TexturedModel.END_FOR_TOP_CUBE_COLUMN);1
This method will generate models for a normal cube, that uses the texture file pipe_block for the sides and the texture file pipe_block_top for the top and bottom sides.
json
{
"parent": "minecraft:block/cube_column",
"textures": {
"end": "fabric-docs-reference:block/pipe_block_top",
"side": "fabric-docs-reference:block/pipe_block"
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
TIP
If you're stuck choosing which TextureModel you should use, open the TexturedModel class and look at the TextureMaps!
Block Texture Pool
java
blockStateModelGenerator.registerCubeAllModelTexturePool(ModBlocks.RUBY_BLOCK)
.stairs(ModBlocks.RUBY_STAIRS)
.slab(ModBlocks.RUBY_SLAB)
.fence(ModBlocks.RUBY_FENCE);1
2
3
4
2
3
4
Another useful method is registerCubeAllModelTexturePool: define the textures by passing in the "base block", and then append the "children", which will have the same textures. In this case, we passed in the RUBY_BLOCK, so the stairs, slab and fence will use the RUBY_BLOCK texture.
WARNING
It will also generate a simple cube all JSON model for the "base block" to ensure that it has a block model.
Be aware of this, if you're changing block model of this particular block, as it will result in en error.
You can also append a BlockFamily, which will generate models for all of its "children".
java
public static final BlockFamily RUBY_FAMILY =
new BlockFamily.Builder(ModBlocks.RUBY_BLOCK)
.stairs(ModBlocks.RUBY_STAIRS)
.slab(ModBlocks.RUBY_SLAB)
.fence(ModBlocks.RUBY_FENCE)
.build();1
2
3
4
5
6
2
3
4
5
6
java
blockStateModelGenerator.registerCubeAllModelTexturePool(ModBlocks.RUBY_BLOCK).family(ModBlocks.RUBY_FAMILY);1
Doors and Trapdoors
java
blockStateModelGenerator.registerDoor(ModBlocks.RUBY_DOOR);
blockStateModelGenerator.registerTrapdoor(ModBlocks.RUBY_TRAPDOOR);
// blockStateModelGenerator.registerOrientableTrapdoor(ModBlocks.RUBY_TRAPDOOR);1
2
3
2
3
Doors and trapdoors are a little different. Here, you have to make three new textures - two for the door, and one for the trapdoor.
- The door:
- It has two parts - the upper half and the lower half. Each needs its own texture: in this case
ruby_door_topfor the upper half andruby_door_bottomfor the lower. - The
registerDoor()method will create models for all orientations of the door, both open and closed. - You also need an item texture! Put it in
assets/mod_id/textures/item/folder.
- It has two parts - the upper half and the lower half. Each needs its own texture: in this case
- The trapdoor:
- Here, you need only one texture, in this case named
ruby_trapdoor. It will be used for all sides. - Since
TrapdoorBlockhas aFACINGproperty, you can use the commented out method to generate model files with rotated textures = the trapdoor will be "orientable". Otherwise, it will look the same no matter the direction it's facing.
- Here, you need only one texture, in this case named
Custom Block Models
In this section, we'll create the models for a Vertical Oak Log Slab, with Oak Log textures.
Points 2. - 6. are declared in an inner static helper class called CustomBlockStateModelGenerator.
Custom Block Class
Create a VerticalSlab block with a FACING property and a SINGLE boolean property, like in the Block States tutorial. SINGLE will indicate if there are both slabs. Then you should override getOutlineShape and getCollisionShape, so that the outline is rendered correctly, and the block has the correct collision shape.
java
public static final VoxelShape NORTH_SHAPE = Block.createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 8.0);
public static final VoxelShape SOUTH_SHAPE = Block.createCuboidShape(0.0, 0.0, 8.0, 16.0, 16.0, 16.0);
public static final VoxelShape WEST_SHAPE = Block.createCuboidShape(0.0, 0.0, 0.0, 8.0, 16.0, 16.0);
public static final VoxelShape EAST_SHAPE = Block.createCuboidShape(8.0, 0.0, 0.0, 16.0, 16.0, 16.0);1
2
3
4
2
3
4
java
@Override
protected VoxelShape getSidesShape(BlockState state, BlockView world, BlockPos pos) {
boolean type = state.get(SINGLE);
Direction direction = state.get(FACING);
VoxelShape voxelShape;
if (type) {
switch (direction) {
case WEST -> voxelShape = WEST_SHAPE.asCuboid();
case EAST -> voxelShape = EAST_SHAPE.asCuboid();
case SOUTH -> voxelShape = SOUTH_SHAPE.asCuboid();
case NORTH -> voxelShape = NORTH_SHAPE.asCuboid();
default -> throw new MatchException(null, null);
}
return voxelShape;
} else {
return VoxelShapes.fullCube();
}
}
@Override
protected VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return this.getSidesShape(state, world, pos);
}
@Override
protected VoxelShape getCollisionShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return this.getSidesShape(state, world, pos);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Also override the canReplace() method, otherwise you couldn't make the slab a full block.
java
@Override
protected boolean canReplace(BlockState state, ItemPlacementContext context) {
Direction direction = state.get(FACING);
if (context.getStack().isOf(this.asItem()) && state.get(SINGLE)) {
if (context.canReplaceExisting()) {
return context.getSide().getOpposite() == direction;
}
}
return false;
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
And you're done! You can now test the block out and place it in game.
Parent Block Model
Now, let's create a parent block model. It will determine the size, position in hand or other slots and the x and y coordinates of the texture. It's recommended to use an editor such as Blockbench for this, as making it manually is a really tedious process. It should look something like this:
json
{
"parent": "minecraft:block/block",
"textures": {
"particle": "#side"
},
"display": {
"gui": {
"rotation": [
30,
-135,
0
],
"translation": [
-1.5,
0.75,
0
],
"scale": [
0.625,
0.625,
0.625
]
},
"firstperson_righthand": {
"rotation": [
0,
-45,
0
],
"translation": [
0,
2,
0
],
"scale": [
0.375,
0.375,
0.375
]
},
"firstperson_lefthand": {
"rotation": [
0,
315,
0
],
"translation": [
0,
2,
0
],
"scale": [
0.375,
0.375,
0.375
]
},
"thirdperson_righthand": {
"rotation": [
75,
-45,
0
],
"translation": [
0,
0,
2
],
"scale": [
0.375,
0.375,
0.375
]
},
"thirdperson_lefthand": {
"rotation": [
75,
315,
0
],
"translation": [
0,
0,
2
],
"scale": [
0.375,
0.375,
0.375
]
}
},
"elements": [
{
"from": [
0,
0,
0
],
"to": [
16,
16,
8
],
"faces": {
"down": {
"uv": [
0,
8,
16,
16
],
"texture": "#bottom",
"cullface": "down",
"tintindex": 0
},
"up": {
"uv": [
0,
0,
16,
8
],
"texture": "#top",
"cullface": "up",
"tintindex": 0
},
"north": {
"uv": [
0,
0,
16,
16
],
"texture": "#side",
"cullface": "north",
"tintindex": 0
},
"south": {
"uv": [
0,
0,
16,
16
],
"texture": "#side",
"tintindex": 0
},
"west": {
"uv": [
0,
0,
8,
16
],
"texture": "#side",
"cullface": "west",
"tintindex": 0
},
"east": {
"uv": [
8,
0,
16,
16
],
"texture": "#side",
"cullface": "east",
"tintindex": 0
}
}
}
]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
See how blockstates are formatted for more information. Notice the #bottom, #top, #side keywords. They act as variables that can be set by models that have this one as a parent:
json
{
"parent": "minecraft:block/cube_bottom_top",
"textures": {
"bottom": "minecraft:block/sandstone_bottom",
"side": "minecraft:block/sandstone",
"top": "minecraft:block/sandstone_top"
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
The bottom value will replace the #bottom placeholder and so on. Put it in the resources/assets/mod_id/models/block/ folder.
Custom Model
Another thing we will need is an instance of the Model class. It will represent the actual parent block model inside our mod.
java
public static final Model VERTICAL_SLAB = block("vertical_slab", TextureKey.BOTTOM, TextureKey.TOP, TextureKey.SIDE);
//helper method for creating Models
private static Model block(String parent, TextureKey... requiredTextureKeys) {
return new Model(Optional.of(Identifier.of(FabricDocsReference.MOD_ID, "block/" + parent)), Optional.empty(), requiredTextureKeys);
}
//helper method for creating Models with variants
private static Model block(String parent, String variant, TextureKey... requiredTextureKeys) {
return new Model(Optional.of(Identifier.of(FabricDocsReference.MOD_ID, "block/" + parent)), Optional.of(variant), requiredTextureKeys);
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
The block() method creates a new Model, pointing to the vertical_slab.json file inside the resources/assets/mod_id/models/block/ folder. The TextureKeys represent the "placeholders" (#bottom, #top, ...) as an Object.
Using Texture Map
What does TextureMap do? It actually provides the Identifiers that point to the textures. It technically behaves like a normal map - you associate a TextureKey (Key) with an Identifier (Value).
You can either use the vanilla ones, like TextureMap.all()(which associates all TextureKeys with the same Identifier), or create a new one, by creating a new instance and then using .put() to associate keys with values.
TIP
TextureMap.all() associates all TextureKeys with the same Identifier, no matter how many of them there are!
Since we want to use the Oak Log textures, but have the BOTTOM, TOP and SIDE TextureKeys, we need to create a new one.
java
public static TextureMap blockAndTopForEnds(Block block) {
return new TextureMap()
.put(TextureKey.TOP, ModelIds.getBlockSubModelId(block, "_top"))
.put(TextureKey.BOTTOM, ModelIds.getBlockSubModelId(block, "_top"))
.put(TextureKey.SIDE, ModelIds.getBlockModelId(block));
}1
2
3
4
5
6
7
2
3
4
5
6
7
The bottom and top faces will use oak_log_top.png, the sides will use oak_log.png.
WARNING
All TextureKeys in the TextureMap have to match all TextureKeys in your parent block model!
Custom BlockStateSupplier Method
The BlockStateSupplier contains all blockstate variants, their rotation, and other options like uvlock.
java
private static BlockStateSupplier createVerticalSlabBlockStates(Block vertSlabBlock, Identifier vertSlabId, Identifier fullBlockId) {
VariantSetting<Boolean> uvlock = VariantSettings.UVLOCK;
VariantSetting<VariantSettings.Rotation> yRot = VariantSettings.Y;
return VariantsBlockStateSupplier.create(vertSlabBlock).coordinate(BlockStateVariantMap.create(VerticalSlabBlock.FACING, VerticalSlabBlock.SINGLE)
.register(Direction.NORTH, true, BlockStateVariant.create().put(VariantSettings.MODEL, vertSlabId).put(uvlock, true))
.register(Direction.EAST, true, BlockStateVariant.create().put(VariantSettings.MODEL, vertSlabId).put(uvlock, true).put(yRot, VariantSettings.Rotation.R90))
.register(Direction.SOUTH, true, BlockStateVariant.create().put(VariantSettings.MODEL, vertSlabId).put(uvlock, true).put(yRot, VariantSettings.Rotation.R180))
.register(Direction.WEST, true, BlockStateVariant.create().put(VariantSettings.MODEL, vertSlabId).put(uvlock, true).put(yRot, VariantSettings.Rotation.R270))
.register(Direction.NORTH, false, BlockStateVariant.create().put(VariantSettings.MODEL, fullBlockId).put(uvlock, true))
.register(Direction.EAST, false, BlockStateVariant.create().put(VariantSettings.MODEL, fullBlockId).put(uvlock, true))
.register(Direction.SOUTH, false, BlockStateVariant.create().put(VariantSettings.MODEL, fullBlockId).put(uvlock, true))
.register(Direction.WEST, false, BlockStateVariant.create().put(VariantSettings.MODEL, fullBlockId).put(uvlock, true)));
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
First, we create a new VariantsBlockStateSupplier using VariantsBlockStateSupplier.create(). Then we create a new BlockStateVariantMap that contains parameters for all variants of the block, in this case FACING and SINGLE, and pass it into the VariantsBlockStateSupplier. Specify which model and which transformations (uvlock, rotation) is used when using .register(). For example:
- On the first line, the block is facing north, and is single => we use the model with no rotation.
- On the fourth line, the block is facing west, and is single => we rotate the model on the Y axis by 270°.
- On the sixth line, the block is facing east, but isn't single => it looks like a normal oak log => we don't have to rotate it.
Custom Datagen Method
The last step - creating an actual method you can call and that will generate the JSONs. But what are the parameters for?
BlockStateModelGenerator generator, the same one that got passed intogenerateBlockStateModels.Block vertSlabBlockis the block to which we will generate the JSONs.Block fullBlock- is the model used when theSINGLEproperty is false = the slab block looks like a full block.TextureMap texturesdefines the actual textures the model uses. See the Using Texture Map chapter.
java
public static void registerVerticalSlab(BlockStateModelGenerator generator, Block vertSlabBlock, Block fullBlock, TextureMap textures) {
Identifier slabModel = VERTICAL_SLAB.upload(vertSlabBlock, textures, generator.modelCollector);
Identifier fullBlockModel = ModelIds.getBlockModelId(fullBlock);
generator.blockStateCollector.accept(createVerticalSlabBlockStates(vertSlabBlock, slabModel, fullBlockModel));
generator.registerParentedItemModel(vertSlabBlock, slabModel);
}1
2
3
4
5
6
7
2
3
4
5
6
7
First, we get the Identifier of the single slab model with VERTICAL_SLAB.upload(). Then we get the Identifier of the full block model with ModelIds.getBlockModelId(), and pass those two models into createVerticalSlabBlockStates. The BlockStateSupplier gets passed into the blockStateCollector, so that the JSON files are actually generated. Also, we create a model for the vertical slab item with BlockStateModelGenerator.registerParentedItemModel().
And that is all! Now all that's left to do is to call our method in our ModelProvider:
java
CustomBlockStateModelGenerator.registerVerticalSlab(
blockStateModelGenerator,
ModBlocks.VERTICAL_OAK_LOG_SLAB,
Blocks.OAK_LOG,
CustomBlockStateModelGenerator.blockAndTopForEnds(Blocks.OAK_LOG)
);1
2
3
4
5
6
2
3
4
5
6
Sources and Links
You can view the example tests in Fabric API and this documentation's Reference Mod for more information.
You can also find more examples of using custom datagen methods by browsing mods' open-source code, for example Vanilla+ Blocks and Vanilla+ Verticals by Fellteros.








