Geração de Modelo de Bloco 26.2
Um guia para gerar modelos de bloco e estados de bloco via datagen.
PRÉ-REQUISITOS
Tenha certeza que você completou o processo de configuração do datagen primeiro.
Configuração
Primeiro, nós precisaremos criar o nosso ModelProvider. Crie uma classe que estenda FabricModelProvider. Implemente esses dois métodos abstratos: generateBlockStateModels e generateItemModels. Por último, crie um construtor que combine ao 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
Registre essa classe em seu DataGeneratorEntrypoint dentro do método onInitializeDataGenerator.
java
pack.addProvider(ExampleModModelProvider::new);1
Modelo de Blocos e Estados de Bloco
java
@Override
public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) {
}1
2
3
2
3
Para modelos de bloco, nós focaremos primeiro no método generateBlockStateModels. Note que o parâmetro BlockStateModelGenerator blockStateModelGenerator - esse objeto será responsável por gerar todos os arquivos JSON. Aqui temos alguns exemplos úteis que você pode usar para gerar os modelos desejados:
Cubo Inteiro Simples
java
blockStateModelGenerator.createTrivialCube(ModBlocks.STEEL_BLOCK);1
Essa é a função mais comumente usada. Ela gera um arquivo modelo JSON para um modelo de bloco normal cube_all. Uma textura é usada para todos os seis lados, nesse caso usamos 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
Ela também gera um arquivo JSON de estado de bloco. Como nós não temos propriedades do estado de bloco (ex.: Eixos, Faces, etc), uma variante é suficiente, e será usada toda vez que o bloco é colocado.
json
{
"variants": {
"": {
"model": "example-mod:block/steel_block"
}
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
Singletons
O método registerSingleton fornece arquivos de modelo JSON baseado no TextureModel fornecido e uma única variante de estado de bloco.
java
blockStateModelGenerator.createTrivialBlock(ModBlocks.PIPE_BLOCK, TexturedModel.COLUMN_ALT);1
Esse método irá gerar modelos para um cubo normal, que usa o arquivo de textura pipe_block para os lados e a textura pipe_block_top para a face superior e inferior.
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
Se você está preso escolhendo qual TextureModel você deve usar, abra a classe TextureModel e dê uma olhada nos TextureMappings!
Pool de Textura de Bloco
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
O family é outro método útil, ele define a textura fornecendo ao "bloco base" e acrescentando "filhos", que vão ter as mesmas texturas. Nesse caso, nós fornecemos ao RUBY_BLOCK, então as escadas, lajes e cercas irão usar a textura RUBY_BLOCK.
WARNING
Ele também gerará um modelo JSON cubo simples para o "bloco base" para garantir que ele tenha um modelo de bloco.
Tenha consciência que se você está mudando o modelo desse bloco em particular, isso resultará em um erro.
Você pode acrescentar um BlockFamily, cujo vai garantir modelos para todos seus "filhos".
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
Portas e Alçapões
java
blockStateModelGenerator.createDoor(ModBlocks.RUBY_DOOR);
blockStateModelGenerator.createTrapdoor(ModBlocks.RUBY_TRAPDOOR);
// blockStateModelGenerator.registerOrientableTrapdoor(ModBlocks.RUBY_TRAPDOOR);1
2
3
2
3
Portas e Alçapões são um pouco diferentes. Aqui você deve fazer três novas texturas - duas para a porta e uma para o alçapão.
- A porta:
- Ela tem duas partes - a parte de cima e a de baixo. Cada uma precisa da própria texura: nesse caso
ruby_door_toppara a parte de cima eruby_door_bottompara a parte de baixo. - O método
registerDoor()irá criar modelos para as orientações da porta, tanto aberta quando fechada. - Você também vai precisar de uma textura de item! Coloque-a na pasta
assets/mod-id/textures/item/.
- Ela tem duas partes - a parte de cima e a de baixo. Cada uma precisa da própria texura: nesse caso
- O alçapão:
- Aqui, você precisa de uma única textura, nesse caso
ruby_trapdoor. Ela será usada em todos os lados. - Como a
TrapdoorBlockpossui uma propriedadeFACING, você pode usar esse método para gerar um arquivo modelo com texturas rotacionadas = o alçapão será "orientável." Caso contrário, ele irá parecer o mesmo independente da direção.
- Aqui, você precisa de uma única textura, nesse caso
Modelos Personalizados de Bloco
Nessa seção, nós iremos criar modelos para uma Laje de Tronco de Carvalho Vertical, com as texturas do Tronco de Carvalho.
Todos os campos e texturas para essa parte do tutorial estão declarados em uma classe interna estática chamada CustomBlockStateModelGenerator.
Mostrar 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(ExampleMod.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(ExampleMod.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
Classe de Bloco Personalizado
Crie um bloco VerticalSlab com a propriedade FACING e a propriedade booliana SINGLE, como no tutorial de Estados de Bloco. SINGLE indicará se as duas lajes estão presentes. Então você deve sobrescrever getShape e getCollisionShape para que o contorno renderize corretamente e que o bloco tenha o formato correto de colisão.
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
Sobrescreva igualmente o método canBeReplaced(), caso contrário você não poderá fazer um bloco inteiro com as lajes.
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
E está pronto! Você pode testar o bloco e colocá-lo dentro do jogo.
Modelo de Bloco Parente
Agora, vamos criar um modelo de bloco parente. Ele irá determinar o tamanho, a posição na mão ou em outros slots e as coordenadas x e y da textura. É recomendado usar um editor como Blockbench para isso, pois criá-lo manualmente é um processo realmente tedioso. Ele deve se parecer com isso:
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
Veja como estados de bloco são formatados para mais informações. Note as palavras-chave #bottom, #top, #side. Elas agem como variáveis que podem ser escolhidas por modelos que têm esse como parente:
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
O valor bottom irá substituir o espaço reservado #bottom e assim por diante. Coloque-o na pasta resources/assets/mod_id/models/block/.
Modelo Personalizado
Outra coisa que precisará de uma instância é a classe Model. Ela representará o verdadeiro modelo de bloco parente dentro do nosso mod.
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(ExampleMod.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(ExampleMod.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
O método block() cria um novo Model, apontando para o arquivo vertical_slab.json dentro da pasta resources/assets/mod_id/models/block/. As TextureSlots representam os "espaços reservados" (#bottom, #top, ...) como um Objeto.
Usando Mapa de Textura
O que TextureMap faz? Ele, na verdade, fornece aos identificadores que apontam para as texturas. Ele tecnicamente se comporta como um mapa normal - você associa uma TextureSlot (chave) com um Identifier (valor).
Você pode tanto usar os mapas vanillas, como TextureMapping.cube() (que associa todas as TextureSlots com o mesmo Identifier), ou criar uma nova, criando uma instância e usando .put() para associar as chaves com os valores.
TIP
TextureMapping.cube() associa todas as TextureSlots com o mesmo Identifier, não importa quantas delas existam!
Nós queremos usar as texturas do Tronco de Carvalho, porém temos as TextureSlots `BOTTOM, TOPeSIDE``. Para isso, precisamos criar chaves novas.
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
As faces bottom e top vão usar oak_log_top.png, os lados irão usar oak_log.png.
WARNING
Todas as TextureSlots no TextureMapping devem combinar com todas as TextureSlots no seu modelo de bloco parente!
Método BlockModelDefinitionGenerator Personalizado
O BlockModelDefinitionGenerator contém todas as variantes dos estados de bloco, as suas rotações e outras opções como o 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
Primeiramente, nós criaremos um novo BlockModelDefinitionGenerator usando o MultiVariantGenerator.dispatch(). Então nós criamos um novo PropertyDispatch que contém os parâmetros para todas as variantes do bloco, nesse caso FACING e SINGLE, e que os passa para o MultiVariantGenerator. Especifique qual modelo e qual transformação (UV lock, rotação) são usados ao utilizar .register(). Por exemplo:
- Linha 9: single slab voltada para o norte, então nós usaremos o modelo sem rotação
- Linha 9: single slab voltada para o oeste, então nós vamos rotacionar o modelo no eixo Y a 270°
- Linhas 10-13: non-single slab, que parece um bloco inteiro e que não precisamos rotacionar
Método Datagen Personalizado
O último passo - criar um método que de fato vai chamar e gerar os JSONs. Mas para que servem os parâmetros?
BlockModelGenerators generatoro mesmo que foi passado para ogenerateBlockStateModels.Block vertSlabBlocké o bloco para qual vamos gerar os JSONs.Block fullBlock- é o modelo usado quando a propriedadeSINGLEé falsa = a laje se parece com um bloco inteiro.TextureMapping texturesdefine as verdadeiras texturas que o modelo usa. Veja o capítulo Usando Mapa de Textura.
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
Primeiro, nós pegamos o Identifier do modelo de laje única usando VERTICAL_SLAB.create(). Então, nós pegamos o Identifier do bloco inteiro usando ModelLocationUtils.getModelLocation().
Nós então passamos esses dois modelos para o createVerticalSlabBlockStates, que ele mesmo é passado para o consumer blockStateOutput, que gera os arquivos JSON para os modelos.
Finalmente, nós criamos um modelo para o item de laje vertical usando BlockModelGenerators.registerSimpleItemModel().
Isso é tudo! Agora tudo que nos resta a fazer é chamar o nosso método no nosso 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









