Генерация модели блока 26.1.2
Руководство по созданию моделей блоков и состояний блока с помощью datagen.
ТРЕБОВАНИЯ
Сначала убедитесь, что вы установили datagen.
Установка
Во-первых, нам понадобится создать наш ModelProvider. Создайте класс, который расширяет (extends) FabricModelProvider. Реализуйте оба абстрактных метода: generateBlockStateModels и generateItemModels. Напоследок, создайте конструктор, соответствующий суперклассу (super).
java
public class ExampleModModelProvider extends FabricModelProvider {
public ExampleModModelProvider(FabricPackOutput 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. Вот несколько полезных примеров, которые вы можете использовать для создания нужных вам моделей:
Простой куб со всех сторон (Simple Cube All)
java
blockStateModelGenerator.createTrivialCube(ModBlocks.STEEL_BLOCK);1
Это самая часто используемая функция. Она генерирует файл JSON-модели для обычного блока типа cube_all. Одна и та же текстура используется для всех шести сторон, в этом случае мы используем 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-файл состояния блока (blockstate). Поскольку у нас нет свойств состояния блока (напр. Axis, Facing, ...), достаточно одного варианта, который используется каждый раз, когда блок поставлен.
json
{
"variants": {
"": {
"model": "example-mod:block/steel_block"
}
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
Одиночные варианты (Singletons)
Метод registerSingleton предоставляет файлы JSON-моделей на основе переданной вам TexturedModel и один вариант blockstate.
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!
Набор блочных текстур (Block Texture Pool)
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.
WARNING
Он также сгенерирует простую модель куба в формате JSON для "базового блока", чтобы убедиться, что у него есть модель блока.
Помните об этом, если вы меняете модель блока в данном конкретном блоке, так как это приведет к ошибке en.
Вы также можете добавить 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, вы можете использовать закомментированный метод для генерации файлов моделей с повернутыми текстурами = люк будет "ориентируемым". В противном случае он будет выглядеть одинаково независимо от того, в какую сторону он направлен.
- Здесь вам понадобится только одна текстура, в данном случае с именем
Пользовательские модели блока
В этом разделе мы создадим модели для вертикального сруба из дубовых бревен с текстурами дубовых бревен.
Все поля и методы для этой части руководства объявлены в статическом вложенном классе (static inner class) с именем CustomBlockStateModelGenerator.
Раскрыть класс CustomBlockStateModelGenerator
java
public static class CustomBlockStateModelGenerator {
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);
}
public static TextureMapping blockAndTopForEnds(Block block) {
return new TextureMapping()
.put(TextureSlot.TOP, new Material(ModelLocationUtils.getModelLocation(block, "_top")))
.put(TextureSlot.BOTTOM, new Material(ModelLocationUtils.getModelLocation(block, "_top")))
.put(TextureSlot.SIDE, new Material(ModelLocationUtils.getModelLocation(block)));
}
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))
);
}
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
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
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
Класс пользовательского блока (Custom Block Class)
Создайте блок VerticalSlab со свойством FACING и булевым свойством SINGLE, как в учебнике Block States. 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
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
Также переопределите метод 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
2
3
4
5
6
7
8
9
10
11
12
Готово! Теперь вы можете протестировать блок и поместить его в игру.
Модель родительского блока
Теперь давайте создадим модель родительского блока. Он определит размер, положение в руке или других слотах, а также координаты 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. Они работают как переменные, значения которых могут быть заданы в моделях, использующих текущую модель в качестве родительской (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
Значение 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
2
3
4
5
6
7
8
9
10
11
Метод block() создает новую Model, указывая на файл vertical_slab.json в папке resources/assets/example-mod/models/block/. Ключи TextureSlot представляют собой "заполнители" (placeholders) (#bottom, #top, ...) как объект.
Использование карты текстур
Что делает TextureMapping? На самом деле он предоставляет идентификаторы (Identifier), которые указывают на текстуры. Технически он работает как обычная карта (Map) — вы связываете слот текстуры TextureSlot (ключ) с идентификатором Identifier (значение).
Вы можете использовать либо стандартные ванильные варианты, такие как TextureMapping.cube() (который связывает все слоты TextureSlot с одним и тем же идентификатором), либо создать свой собственный, создав новый экземпляр класса и затем используя метод .put() для связывания ключей со значениями.
TIP
TextureMapping.cube() связывает абсолютно все слоты TextureSlot с одним и тем же идентификатором, сколько бы их ни было!
Поскольку мы хотим использовать текстуры дубового бревна, но при этом у нас есть слоты текстур BOTTOM (низ), TOP (верх) и SIDE (бока), нам нужно создать новую карту текстур.
java
public static TextureMapping blockAndTopForEnds(Block block) {
return new TextureMapping()
.put(TextureSlot.TOP, new Material(ModelLocationUtils.getModelLocation(block, "_top")))
.put(TextureSlot.BOTTOM, new Material(ModelLocationUtils.getModelLocation(block, "_top")))
.put(TextureSlot.SIDE, new Material(ModelLocationUtils.getModelLocation(block)));
}1
2
3
4
5
6
2
3
4
5
6
Для нижней и верхней граней будет использоваться файл oak_log_top.png, для боковых граней — файл oak_log.png.
WARNING
Все TextureSlot в TextureMapping должны совпадать со всеми TextureSlot в родительской блочной модели!
Пользовательский метод BlockModelDefinitionGenerator
BlockModelDefinitionGenerator содержит все варианты состояний блока (blockstate), их вращение и другие параметры, такие как блокировка UV-координат (UV lock).
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Сначала мы создаем новый BlockModelDefinitionGenerator с помощью метода MultiVariantGenerator.dispatch(). Затем мы создаем новый PropertyDispatch, который содержит параметры для всех вариантов блока — в данном случае FACING и SINGLE — и передаем его в MultiVariantGenerator. С помощью метода .register() укажите, какая модель и какие трансформации (uvlock, вращение) используются. Например:
- Строка 6: одиночная плита, направленная на север, поэтому мы используем модель без вращения
- Строка 9: одиночная плита, направленная на запад, поэтому мы поворачиваем модель по оси Y на 270°
- Строки 10-13: двойная плита (свойство SINGLE равно false), которая выглядит как полный блок, и нам не нужно её вращать
Кастомный метод 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
2
3
4
5
6
Сначала мы получаем идентификатор (Identifier) модели одиночной плиты с помощью VERTICAL_SLAB.create(). Затем мы получаем идентификатор модели полного блока с помощью ModelLocationUtils.getModelLocation().
После этого мы передаем обе эти модели в метод 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









