前置条件
请确保你已经完成数据生成器设置章节。
设置
首先,我们需要创建 ModelProvider。 创建一个继承 FabricModelProvider 的类。 实现两个抽象方法:generateBlockStateModels 和 generateItemModels。 最后,创建一个与 super 匹配的构造函数。
java
public class ExampleModModelProvider extends FabricModelProvider {
public ExampleModModelProvider(FabricDataOutput output) {
super(output);
}
@Override
public void generateBlockStateModels(BlockModelGenerators blockStateModelGenerator) {
}
@Override
public void generateItemModels(ItemModelGenerators itemModelGenerator) {
}
@Override
public String getName() {
return "ExampleModModelProvider";
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
接着在 DataGeneratorEntrypoint 入口点的 onInitializeDataGenerator 方法里注册这个类。
java
pack.addProvider(ExampleModModelProvider::new);1
方块状态和方块模型
java
@Override
public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) {
}1
2
3
2
3
对于方块模型,我们将主要关注 generateBlockStateModels 方法。 请注意参数 BlockStateModelGenerator blockStateModelGenerator——该对象将负责生成所有 JSON 文件。 以下是一些可用于生成所需模型的便捷示例:
简单 Cube All
java
blockStateModelGenerator.createTrivialCube(ModBlocks.STEEL_BLOCK);1
这是最常用的函数。 它为普通的 cube_all 方块模型生成一个 JSON 模型文件。 所有六个面都使用一个纹理,在本例中我们使用 steel_block。
json
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "example-mod:block/steel_block"
}
}1
2
3
4
5
6
2
3
4
5
6
它还生成一个方块状态 JSON 文件。 由于我们没有方块状态属性(例如轴、朝向等),因此一个变体就够了,并且每次放置方块时都会使用。
json
{
"variants": {
"": {
"model": "example-mod:block/steel_block"
}
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
单例
registerSingleton 方法根据你传入的 TexturedModel 和单个方块状态变体提供 JSON 模型文件。
java
blockStateModelGenerator.createTrivialBlock(ModBlocks.PIPE_BLOCK, TexturedModel.COLUMN_ALT);1
该方法将为一个普通立方体生成模型,该立方体使用纹理文件 pipe_block 作为侧面,使用纹理文件 pipe_block_top 作为顶部和底部。
json
{
"parent": "minecraft:block/cube_column",
"textures": {
"end": "example-mod:block/pipe_block_top",
"side": "example-mod:block/pipe_block"
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
TIP
如果您在选择应该使用哪个 TextureModel 时遇到困难,请打开 TexturedModel 类并查看 TextureMapping!
方块纹理池
java
blockStateModelGenerator.family(ModBlocks.RUBY_BLOCK)
.stairs(ModBlocks.RUBY_STAIRS)
.slab(ModBlocks.RUBY_SLAB)
.fence(ModBlocks.RUBY_FENCE);1
2
3
4
2
3
4
另一个有用的方法是 registerCubeAllModelTexturePool:通过传入“基础方块”来定义纹理,然后附加具有相同纹理的“子方块”。 在这种情况下,我们传入了 RUBY_BLOCK,因此楼梯、台阶和栅栏将使用 RUBY_BLOCK 纹理。
你还可以附加一个 BlockFamily,将为其所有“子项”生成模型。
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)
.getFamily();1
2
3
4
5
6
2
3
4
5
6
java
blockStateModelGenerator.family(ModBlocks.RUBY_BLOCK).generateFor(ModBlocks.RUBY_FAMILY);1
门与活板门
java
blockStateModelGenerator.createDoor(ModBlocks.RUBY_DOOR);
blockStateModelGenerator.createTrapdoor(ModBlocks.RUBY_TRAPDOOR);
// blockStateModelGenerator.registerOrientableTrapdoor(ModBlocks.RUBY_TRAPDOOR);1
2
3
2
3
门和活板门略有不同。 在这里,你必须制作三个新纹理——两个用于门,一个用于活板门。
- 门:
- 分为两部分——上半部分和下半部分。 **每个都需要独自的纹理:**在本例中,
ruby_door_top用于上半部分,ruby_door_bottom用于下半部分。 registerDoor()方法将为门的所有方向(打开和关闭)创建模型。- **你还需要一个物品纹理!**将其放在
assets/example-mod/textures/item/文件夹中。
- 分为两部分——上半部分和下半部分。 **每个都需要独自的纹理:**在本例中,
- 活板门:
- 在这里,只需要一个纹理,在本例中名为
ruby_trapdoor。 它将被用于所有面。 - 由于
TrapdoorBlock具有FACING属性,你可以使用注释掉的方法生成具有旋转纹理的模型文件 = 活板门将是“可定向的”。 否则,无论它面向哪个方向,看起来都会一样。
- 在这里,只需要一个纹理,在本例中名为
自定义方块模型
在本节中,我们将创建具有橡木原木纹理的竖直橡木原木台阶模型。
本教程此部分的所有字段和模型都声明在叫做 CustomBlockStateModelGenerator 的静态内部类中。
显示 CustomBlockStateModelGenerator
java
public static class CustomBlockStateModelGenerator {
// :::custom-model
public static final ModelTemplate VERTICAL_SLAB = block("vertical_slab", TextureSlot.BOTTOM, TextureSlot.TOP, TextureSlot.SIDE);
//helper method for creating Models
private static ModelTemplate block(String parent, TextureSlot... requiredTextureKeys) {
return new ModelTemplate(Optional.of(Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "block/" + parent)), Optional.empty(), requiredTextureKeys);
}
//helper method for creating Models with variants
private static ModelTemplate block(String parent, String variant, TextureSlot... requiredTextureKeys) {
return new ModelTemplate(Optional.of(Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "block/" + parent)), Optional.of(variant), requiredTextureKeys);
}
// :::custom-model
// :::custom-texture-map
public static TextureMapping blockAndTopForEnds(Block block) {
return new TextureMapping()
.put(TextureSlot.TOP, ModelLocationUtils.getModelLocation(block, "_top"))
.put(TextureSlot.BOTTOM, ModelLocationUtils.getModelLocation(block, "_top"))
.put(TextureSlot.SIDE, ModelLocationUtils.getModelLocation(block));
}
// :::custom-texture-map
// :::custom-supplier
private static BlockModelDefinitionGenerator createVerticalSlabBlockStates(Block vertSlabBlock, Identifier vertSlabId, Identifier fullBlockId) {
MultiVariant vertSlabModel = BlockModelGenerators.plainVariant(vertSlabId);
MultiVariant fullBlockModel = BlockModelGenerators.plainVariant(fullBlockId);
return MultiVariantGenerator.dispatch(vertSlabBlock)
.with(PropertyDispatch.initial(VerticalSlabBlock.FACING, VerticalSlabBlock.SINGLE)
.select(Direction.NORTH, true, vertSlabModel.with(BlockModelGenerators.UV_LOCK))
.select(Direction.EAST, true, vertSlabModel.with(BlockModelGenerators.UV_LOCK).with(BlockModelGenerators.Y_ROT_90))
.select(Direction.SOUTH, true, vertSlabModel.with(BlockModelGenerators.UV_LOCK).with(BlockModelGenerators.Y_ROT_180))
.select(Direction.WEST, true, vertSlabModel.with(BlockModelGenerators.UV_LOCK).with(BlockModelGenerators.Y_ROT_270))
.select(Direction.NORTH, false, fullBlockModel.with(BlockModelGenerators.UV_LOCK))
.select(Direction.EAST, false, fullBlockModel.with(BlockModelGenerators.UV_LOCK))
.select(Direction.SOUTH, false, fullBlockModel.with(BlockModelGenerators.UV_LOCK))
.select(Direction.WEST, false, fullBlockModel.with(BlockModelGenerators.UV_LOCK))
);
}
// :::custom-supplier
// :::custom-gen
public static void registerVerticalSlab(BlockModelGenerators generator, Block vertSlabBlock, Block fullBlock, TextureMapping textures) {
Identifier slabModel = VERTICAL_SLAB.create(vertSlabBlock, textures, generator.modelOutput);
Identifier fullBlockModel = ModelLocationUtils.getModelLocation(fullBlock);
generator.blockStateOutput.accept(createVerticalSlabBlockStates(vertSlabBlock, slabModel, fullBlockModel));
generator.registerSimpleItemModel(vertSlabBlock, slabModel);
}
// :::custom-gen
}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
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
自定义方块类
创建一个具有 FACING 属性和 SINGLE 布尔属性的 VerticalSlab 方块,类似于方块状态教程中的那样。 SINGLE 将指示是否存在两块台阶。 然后你应该重写 getOutlineShape 和 getCollisionShape,以便正确渲染轮廓,并且方块具有正确的碰撞形状。
java
public static final VoxelShape NORTH_SHAPE = Block.box(0.0, 0.0, 0.0, 16.0, 16.0, 8.0);
public static final VoxelShape SOUTH_SHAPE = Block.box(0.0, 0.0, 8.0, 16.0, 16.0, 16.0);
public static final VoxelShape WEST_SHAPE = Block.box(0.0, 0.0, 0.0, 8.0, 16.0, 16.0);
public static final VoxelShape EAST_SHAPE = Block.box(8.0, 0.0, 0.0, 16.0, 16.0, 16.0);1
2
3
4
2
3
4
java
@Override
protected VoxelShape getBlockSupportShape(BlockState state, BlockGetter level, BlockPos pos) {
boolean type = state.getValue(SINGLE);
Direction direction = state.getValue(FACING);
VoxelShape voxelShape;
if (type) {
switch (direction) {
case WEST -> voxelShape = WEST_SHAPE.singleEncompassing();
case EAST -> voxelShape = EAST_SHAPE.singleEncompassing();
case SOUTH -> voxelShape = SOUTH_SHAPE.singleEncompassing();
case NORTH -> voxelShape = NORTH_SHAPE.singleEncompassing();
default -> throw new MatchException(null, null);
}
return voxelShape;
} else {
return Shapes.block();
}
}
@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return this.getBlockSupportShape(state, level, pos);
}
@Override
protected VoxelShape getCollisionShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return this.getBlockSupportShape(state, level, 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
还要重写 canReplace() 方法,否则无法使台阶成为完整方块。
java
@Override
protected boolean canBeReplaced(BlockState state, BlockPlaceContext context) {
Direction direction = state.getValue(FACING);
if (context.getItemInHand().is(this.asItem()) && state.getValue(SINGLE)) {
if (context.replacingClickedOnBlock()) {
return context.getClickedFace().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
然后就大功告成了! 你现在可以去测试方块并将其放置在游戏中了。
父方块模型
现在,我们来创建一个父方块模型。 这将确定尺寸、在手中或其他槽位中的位置以及纹理的 x 和 y 坐标。 建议使用诸如 Blockbench 之类的编辑器来完成此操作,因为手动制作非常繁琐。 它看起来应该是这样的:
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
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
请参阅方块状态映射格式来了解更多信息。 请注意 #bottom、#top、#side 关键字, 这些关键字充当变量,可以由以此为父级的模型进行设置:
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
bottom 值将替换 #bottom 占位符,依此类推。 将其放在 resources/assets/example-mod/models/block/ 文件夹中。
自定义模型
我们还需要 Model 类的实例。 它代表我们模组内部的实际父方块模型。
java
public static final ModelTemplate VERTICAL_SLAB = block("vertical_slab", TextureSlot.BOTTOM, TextureSlot.TOP, TextureSlot.SIDE);
//helper method for creating Models
private static ModelTemplate block(String parent, TextureSlot... requiredTextureKeys) {
return new ModelTemplate(Optional.of(Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "block/" + parent)), Optional.empty(), requiredTextureKeys);
}
//helper method for creating Models with variants
private static ModelTemplate block(String parent, String variant, TextureSlot... requiredTextureKeys) {
return new ModelTemplate(Optional.of(Identifier.fromNamespaceAndPath(ExampleMod.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
block() 方法创建一个新的 Model,指向 resources/assets/example-mod/models/block/ 文件夹内的 vertical_slab.json 文件。 TextureSlot 将“占位符”(#bottom、#top……) 表示为一个对象。
使用纹理映射
TextureMapping 是干什么的? 它实际上提供了指向纹理的 ID。 从技术上讲,其行为类似于普通映射——将 TextureKey(键)与 Identifier(值)关联起来。
你可以使用原版的,例如 TextureMap.all()(将所有 TextureSlot 与相同的 Identifier 关联),或者创建一个新实例然后用 .put() 将键与值关联起来。
TIP
TextureMapping.cube() 将所有的 TextureSlot 与相同的 Identifier 关联起来,无论有多少!
因为我们想要用橡木原木纹理,但是有 BOTTOM、TOP 和 SIDE 的 TextureSlot,所以我们需要创建一个新的。
java
public static TextureMapping blockAndTopForEnds(Block block) {
return new TextureMapping()
.put(TextureSlot.TOP, ModelLocationUtils.getModelLocation(block, "_top"))
.put(TextureSlot.BOTTOM, ModelLocationUtils.getModelLocation(block, "_top"))
.put(TextureSlot.SIDE, ModelLocationUtils.getModelLocation(block));
}1
2
3
4
5
6
7
2
3
4
5
6
7
bottom(底部)和 top(顶部)面使用 oak_log_top.png,侧面则使用 oak_log.png。
WARNING
TextureMapping 中的所有 TextureSlot 必须与父方块模型中的所有 TextureSlot 匹配!
自定义 BlockModelDefinitionGenerator 方法
BlockModelDefinitionGenerator 包含所有方块状态变体、旋转以及其他选项(如 UV 锁定)。
java
private static BlockModelDefinitionGenerator createVerticalSlabBlockStates(Block vertSlabBlock, Identifier vertSlabId, Identifier fullBlockId) {
MultiVariant vertSlabModel = BlockModelGenerators.plainVariant(vertSlabId);
MultiVariant fullBlockModel = BlockModelGenerators.plainVariant(fullBlockId);
return MultiVariantGenerator.dispatch(vertSlabBlock)
.with(PropertyDispatch.initial(VerticalSlabBlock.FACING, VerticalSlabBlock.SINGLE)
.select(Direction.NORTH, true, vertSlabModel.with(BlockModelGenerators.UV_LOCK))
.select(Direction.EAST, true, vertSlabModel.with(BlockModelGenerators.UV_LOCK).with(BlockModelGenerators.Y_ROT_90))
.select(Direction.SOUTH, true, vertSlabModel.with(BlockModelGenerators.UV_LOCK).with(BlockModelGenerators.Y_ROT_180))
.select(Direction.WEST, true, vertSlabModel.with(BlockModelGenerators.UV_LOCK).with(BlockModelGenerators.Y_ROT_270))
.select(Direction.NORTH, false, fullBlockModel.with(BlockModelGenerators.UV_LOCK))
.select(Direction.EAST, false, fullBlockModel.with(BlockModelGenerators.UV_LOCK))
.select(Direction.SOUTH, false, fullBlockModel.with(BlockModelGenerators.UV_LOCK))
.select(Direction.WEST, false, fullBlockModel.with(BlockModelGenerators.UV_LOCK))
);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
首先,我们使用 MultiVariantGenerator.dispatch() 创建一个新的 BlockModelDefinitionGenerator。 然后我们创建一个新的 PropertyDispatch,包含方块的所有变体的参数,在本例中是 FACING 和 SINGLE,并将其传递给 MultiVariantGenerator。 指定使用 .register() 时使用哪个模型和哪些变换(uvlock、rotation)。 例如:
- 第 6 行:_单个_台阶,朝向北,所以使用不带旋转的模型
- 第 9 行:_单行_台阶朝向西,所以我们将模型绕 Y 轴旋转 270°
- 第 10-13 行:_非单个_台阶,看上去像个完整方块,所以不需要旋转
自定义 Datagen 方法
最后一步——创建一个可以调用的实际方法并生成 JSON。 但这些参数是用来做什么的呢?
BlockModelGenerators generator,与传递到generateBlockStateModels的生成器相同。Block vertSlabBlock是我们将生成 JSON 的方块。Block fullBlock是当SINGLE属性为 false 时使用的模型 = 台阶方块看起来像一个完整方块。TextureMapping textures定义了模型使用的实际纹理。 参见使用纹理映射章节。
java
public static void registerVerticalSlab(BlockModelGenerators generator, Block vertSlabBlock, Block fullBlock, TextureMapping textures) {
Identifier slabModel = VERTICAL_SLAB.create(vertSlabBlock, textures, generator.modelOutput);
Identifier fullBlockModel = ModelLocationUtils.getModelLocation(fullBlock);
generator.blockStateOutput.accept(createVerticalSlabBlockStates(vertSlabBlock, slabModel, fullBlockModel));
generator.registerSimpleItemModel(vertSlabBlock, slabModel);
}1
2
3
4
5
6
7
2
3
4
5
6
7
首先,我们使用 VERTICAL_SLAB.create() 获取单个台阶模型的 Identifier。 然后,我们使用 ModelLocationUtils.getModelLocation() 得到完整方块模型的 Identifier。
我们将这两个模型传入 createVerticalSlabBlockStates,自己传入 blockStateOutput consumer 中,会为模型生成 JSON 文件。
最后,我们使用 BlockModelGenerators.registerSimpleItemModel() 为竖直台阶的物品创建模型。
就这样! 现在剩下要做的就是在 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
来源和链接
你可以查看 Fabric API 中的示例测试和此文档的示例模组以获取更多信息。
你还可以通过浏览模组的开源代码找到更多使用自定义数据生成方法的示例,例如 Fellteros 的 Vanilla+ Blocks 和 Vanilla+ Verticals。









