PREREQUISITI
Assicurati di aver prima completato il processo di configurazione della datagen.
Configurazione
Anzitutto, avremo bisogno di creare il nostro ModelProvider. Crea una classe che estenda FabricModelProvider. Implementa entrambi i metodi astratti: generateBlockStateModels e generateItemModels. Infine, crea un costruttore che corrisponda a 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
Registra questa classe nella tua DataGeneratorEntrypoint all'interno del metodo onInitializeDataGenerator.
java
pack.addProvider(ExampleModModelProvider::new);1
Stati e Modelli dei Blocchi
java
@Override
public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) {
}1
2
3
2
3
Per i modelli dei blocchi ci concentreremo soprattutto sul metodo generateBlockStateModels. Nota il parametro BlockStateModelGenerator blockStateModelGenerator - questo oggetto sarà responsabile della generazione di tutti i file JSON. Ecco alcuni esempi utili che puoi usare per generare i tuoi modelli desiderati:
Cubo Intero Semplice
java
blockStateModelGenerator.createTrivialCube(ModBlocks.STEEL_BLOCK);1
Questa è la funzione usata più spesso. Essa genera un file JSON per un semplice modello cube_all di un blocco. Una texture viene usata per tutti e sei le facce, in questo caso useremo 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
Essa genera anche un file JSON dei stati del blocco. Poiché non abbiamo alcuna proprietà per gli stati del blocco (per esempio Asse, Orientazione, ...), ci basta una variante che verrà usata ogni volta che il blocco è piazzato.
json
{
"variants": {
"": {
"model": "example-mod:block/steel_block"
}
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
Singleton
Il metodo registerSingleton fornisce file JSON di modelli in base al TexturedModel che passi, e una singola variante di stato di blocco.
java
blockStateModelGenerator.createTrivialBlock(ModBlocks.PIPE_BLOCK, TexturedModel.COLUMN_ALT);1
Questo metodo genererà modelli per un cubo normale, che usa il file di texture pipe_block per i lati e pipe_block_top per le facce superiore e inferiore.
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 non sai quale TextureModel dovresti scegliere, apri la classe TexturedModel e dai un'occhiata alle TextureMaps!
Pool di Texture dei Blocchi
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
Un altro metodo utile è registerCubeAllModelTexturePool: definisce le texture passandoci il "blocco di base", per poi aggiungerci i "figli", che avranno le stesse texture. In questo caso, abbiamo passato il RUBY_BLOCK, quindi scalini, lastra e staccionata useranno la texture RUBY_BLOCK.
WARNING
Genererà anche un modello JSON per un cubo intero semplice per il "blocco di base" per assicurarsi che il blocco abbia un modello.
Tieni conto di questo se vuoi cambiare il modello di quel blocco in particolare, poiché causerà un errore.
Puoi anche aggiungere una BlockFamily, che genererà modelli per tutti i suoi "figli".
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
Porte e Botole
java
blockStateModelGenerator.createDoor(ModBlocks.RUBY_DOOR);
blockStateModelGenerator.createTrapdoor(ModBlocks.RUBY_TRAPDOOR);
// blockStateModelGenerator.registerOrientableTrapdoor(ModBlocks.RUBY_TRAPDOOR);1
2
3
2
3
Le porte e botole funzionano un po' diversamente. Qui, dovrai creare tre nuove texture - due per la porta, e una per la botola.
- La porta:
- Ha due parti - la metà superiore e quella inferiore. Ciascuna necessita di una texture propria: in questo caso
ruby_door_topper la metà superiore eruby_door_bottomper l'inferiore. - Il metodo
registerDoor()creerà modelli per tutte le orientazioni della porta, sia aperta che chiusa. - Servirà anche una texture per l'oggetto! Mettila nella cartella
assets/example-mod/textures/item/.
- Ha due parti - la metà superiore e quella inferiore. Ciascuna necessita di una texture propria: in questo caso
- La botola:
- Qui ti basta una texture sola, in questo caso chiamata
ruby_trapdoor. Verrà usata per tutti i lati. - Poiché il
TrapdoorBlockha una proprietàFACING, puoi usare il metodo commentato per generare file di modello con texture ruotate = la botola sarà "orientabile". Altrimenti avrà lo stesso aspetto in tutte le direzioni.
- Qui ti basta una texture sola, in questo caso chiamata
Modelli del Blocco Personalizzati
In questa sezione creeremo i modelli per una Lastra Verticale di Quercia, con texture di un Tronco di Quercia.
Tutti gli attributi e i metodi di questa parte del tutorial sono dichiarati in una classe statica interna chiamata CustomBlockStateModelGenerator.
Mostra 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
Classe dei Blocchi Personalizzati
Crea un blocco VerticalSlab con una proprietà FACING e una proprietà booleana SINGLE, come nel tutorial degli Stati dei Blocchi. SINGLE indicherà se ci sono entrambe le lastre. Poi dovresti fare override di getOutlineShape e getCollisionShape, cosicché il contorno sia renderizzato correttamente, e il blocco abbia la forma di collisione corretta.
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
Fai anche override del metodo canReplace(), altrimenti non potresti rendere la lastra un blocco intero.
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
Tutto fatto! Ora puoi testare il blocco e piazzarlo nel gioco.
Modello di Blocco Genitore
Ora creiamo un modello di blocco genitore. Esso determinerà la dimensione, posizione nella mano o in altri slot e le coordinate x e y della texture. Si consiglia, per questo, di usare un editor come Blockbench poiché crearlo manualmente è davvero faticoso. Dovrebbe avere un aspetto del genere:
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
Controlla il formato degli stati del blocco per maggiori informazioni. Nota le parole chiave #bottom, #top, #side. Queste saranno variabili impostabili in modelli che ereditino da questo genitore:
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
Il valore bottom sostituirà il segnaposto #bottom eccetera. Mettilo nella cartella resources/assets/example-mod/models/block/.
Modello Personalizzato
Un'altra cosa che ci serve è un'istanza della classe Model. Essa rappresenterà proprio il modello di blocco genitore nella nostra 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(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
Il metodo block() crea un nuovo Model, puntando al file vertical_slab.json nella cartella resources/assets/example-mod/models/block/. Le TextureSlot rappresentano i "segnaposto" (#bottom, #top, ...) come oggetti.
Usare le Mappe di Texture
Cosa fa TextureMapping? In effetti fornisce gli identificativi che puntano alle texture. Tecnicamente funziona proprio come una mappa normale - si associa un TextureSlot (chiave) con un Identifier (valore).
Puoi usare quelli vanilla, come TextureMapping.cube()(che associa tutti i TextureSlot allo stesso Identifier), o crearne uno nuovo, instanziando un nuovo oggetto e usando .put() per associare valori alle chiavi.
TIP
TextureMapping.cube() associa tutti i TextureSlot allo stesso Identifier, indipendentemente dalla loro quantità!
Poiché vogliamo usare le texture del Tronco di Quercia, ma abbiamo i TextureSlot BOTTOM, TOP, e SIDE, ne dovremo creare uno nuovo.
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
Le facce bottom e top useranno oak_log_top.png, i lati useranno oak_log.png.
WARNING
Tutti i TextureSlot nel TextureMapping devono corrispondere a tutti i TextureSlot nel tuo modello di blocco genitore!
Metodo BlockModelDefinitionGenerator Personalizzato
Il BlockModelDefinitionGenerator contiene tutte le varianti di stato del blocco, le loro rotazioni, e altre opzioni come blocco 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
Anzitutto, creiamo un nuovo BlockModelDefinitionGenerator tramite MultiVariantGenerator.dispatch(). Poi creeremo una nuova PropertyDispatch che contiene parametri per tutte le varianti del blocco, in questo caso FACING e SINGLE, e la passeremo nel nostro MultiVariantGenerator. Puoi indicare il modello e le trasformazioni (uvlock, rotazione) da usare in .register(). Per esempio:
- Linea 6: la lastra è singola, e in direzione nord, quindi useremo il modello senza ruotarlo
- Linea 9: la lastra è singola, e in direzione ovest, quindi ruoteremo il modello di 270° sull'asse Y
- Linee 10-13: le lastre sono sovrapposte (sembrano un normale tronco di quercia), e in direzione est, quindi non dobbiamo ruotarlo
Metodo di Generazione Dati Personalizzato
L'ultimo passaggio - creare effettivamente un metodo che si possa chiamare e che generi i JSON. Ma che parametri sono necessari?
BlockModelGenerators generator, quello che abbiamo passato ingenerateBlockStateModels.Block vertSlabBlockè il blocco di cui genereremo i JSON.Block fullBlock- il modello usato quando la proprietàSINGLEè falsa = il blocco di lastre sembra un blocco intero.TextureMapping texturesdefinisce le texture che il modello usa effettivamente. Controlla il capitolo Usare le Mappe di Texture.
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
Anzitutto, otteniamo l'Identifier del modello di lastra singola con VERTICAL_SLAB.create(). Poi otteniamo l'Identifier del modello di blocco intero con ModelLocationUtils.getModelLocation().
Poi passiamo entrambi i modelli in createVerticalSlabBlockStates, che viene ancora passato nel consumer di blockStateOutput, che genera i file JSON per i modelli.
Infine, creiamo un modello per l'oggetto di lastra verticale con BlockModelGenerators.registerSimpleItemModel().
È tutto! Tutto ciò che rimane da fare è chiamare il metodo nel nostro 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
Fonti e Link
Per maggiori informazioni, puoi trovare esempi di test nell'API di Fabric e nella mod d'esempio di questa documentazione.
Puoi anche trovare altri esempi dell'uso di metodi di datagen personalizzati navigando il codice sorgente aperto delle mod, per esempio di Vanilla+ Blocks e Vanilla+ Verticals di Fellteros.









