ПЕРЕДУМОВИ
Спершу переконайтеся, що ви виконали процес налаштування datagen.
Налаштування
По-перше, ми повинні створити наш 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. Ось декілька зручних прикладів, які можна використовувати для створення бажаних моделей:
Усі прості куби
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 зі станом блока. Оскільки у нас немає властивостей стану блока (наприклад, Axis, Facing…), достатньо одного варіанту, який використовується кожного разу, коли блок розміщується.
json
{
"variants": {
"": {
"model": "example-mod:block/steel_block"
}
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
Сінглтони
Метод registerSingleton надає файли моделі JSON на основі TexturedModel, який ви передаєте, і єдиного варіанту стану блока.
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 і подивіться на TextureMaps!
Пул текстур блока
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: визначте текстури, передавши «base block», а потім додайте «children», які матимуть ті самі текстури. У цьому випадку ми передали RUBY_BLOCK, тому сходи, плита та паркан використовуватимуть текстуру RUBY_BLOCK.
WARNING
Він також створить просту модель куба з усіма JSON для «base block», щоб переконатися, що він має модель блока.
Зверніть увагу на це, якщо ви змінюєте модель блока цього конкретного блока, оскільки це призведе до помилки.
Ви також можете додати BlockFamily, який генеруватиме моделі для всіх своїх «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)
.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
Власний клас блока
Створіть блок VerticalSlab з властивостями FACING і булевою властивістю SINGLE, як у підручнику [станів блоків(../blocks/blockstates). 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, вказуючи на файл vertical_slab.json у теці resources/assets/example-mod/models/block/. TextureSlot представляють «заповнювачі» (#bottom, #top, …) як об'єкт.
Використання мапи текстури
Що робить TextureMapping? Він фактично надає ідентифікатори, які вказують на текстури. Технічно вона поводиться як звичайна мапа — ви пов’язуєте TextureSlot (ключ) з Identifier (значення).
Ви можете використати стандартні, як-от TextureMapping.cube() (який пов’язує всі TextureKeys з тим самим Identifier), або створити новий, створивши новий екземпляр, а потім використавши .put() для зв’язування ключів зі значеннями.
TIP
TextureMapping.cube() пов'язує всі TextureSlot з одним Identifier, незалежно від того, скільки їх є!
Оскільки ми хочемо використовувати текстури дубової колоди, але маємо BOTTOM, TOP і SIDE TextureSlots, нам потрібно створити новий.
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
Нижня та верхня сторони використовуватимуть oak_log_top.png, бокові — oak_log.png.
WARNING
Усі TextureSlot у TextureMap мають збігатися з усіма TextureSlot у вашій моделі батьківського блока!
Власний метод BlockModelDefinitionGenerator
BlockModelDefinitionGenerator містить усі варіанти стану блока, його оберт та інші параметри, як-от 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
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Спочатку ми створюємо новий BlockModelDefinitionGenerator за допомогою MultiVariantGenerator.dispatch(). Потім ми створюємо новий PropertyDispatch, який містить параметри для всіх варіантів блока, в цьому випадку FACING і SINGLE, і передаємо його в MultiVariantGenerator. Укажіть, яка модель і які перетворення (uvlock, rotation) використовуються під час використання .register(). Наприклад:
- Рядок 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
Спочатку ми отримуємо Identifier моделі однієї плити за допомогою VERTICAL_SLAB.create(). Потім ми отримуємо Identifier моделі повного блока за допомогою ModelLocationUtils.getModelLocation().
Потім ми передаємо ці дві моделі в createVerticalSlabBlockStates, який сам передається в споживач blockStateOutput, який генерує файли 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 та прикладному моді цієї документації, щоб дізнатися більше.
Ви також можете знайти більше прикладів використання власних методів даних, переглянувши відкритий вихідний код модів, наприклад Vanilla+ Blocks і Vanilla+ Verticals від Fellteros.









