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 extends FabricModelProvider. Implementa entrambi i metodi astratti: generateBlockStateModels e generateItemModels. Infine, crea un costruttore che corrisponda a super.
java
public class FabricDocsReferenceModelProvider extends FabricModelProvider {
public FabricDocsReferenceModelProvider(FabricDataOutput output) {
super(output);
}
@Override
public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) {
}
@Override
public void generateItemModels(ItemModelGenerator itemModelGenerator) {
}
@Override
public String getName() {
return "FabricDocsReference Model Provider";
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Registra questa classe nella tua DataGeneratorEntrypoint all'interno del metodo onInitializeDataGenerator.
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.registerSimpleCubeAll(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": "fabric-docs-reference: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": "fabric-docs-reference: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.registerSingleton(ModBlocks.PIPE_BLOCK, TexturedModel.END_FOR_TOP_CUBE_COLUMN);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": "fabric-docs-reference:block/pipe_block_top",
"side": "fabric-docs-reference: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.registerCubeAllModelTexturePool(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)
.build();1
2
3
4
5
6
2
3
4
5
6
java
blockStateModelGenerator.registerCubeAllModelTexturePool(ModBlocks.RUBY_BLOCK).family(ModBlocks.RUBY_FAMILY);1
Porte e Botole
java
blockStateModelGenerator.registerDoor(ModBlocks.RUBY_DOOR);
blockStateModelGenerator.registerTrapdoor(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/mod-id/textures/item/.
- 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.
Modelli del Blocco Personalizzati
In questa sezione creeremo i modelli per una Lastra Verticale di Quercia, con texture di un Tronco di Quercia.
I punti 2. - 6. sono dichiarati in una classe ausiliaria interna chiamata CustomBlockStateModelGenerator.
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.createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 8.0);
public static final VoxelShape SOUTH_SHAPE = Block.createCuboidShape(0.0, 0.0, 8.0, 16.0, 16.0, 16.0);
public static final VoxelShape WEST_SHAPE = Block.createCuboidShape(0.0, 0.0, 0.0, 8.0, 16.0, 16.0);
public static final VoxelShape EAST_SHAPE = Block.createCuboidShape(8.0, 0.0, 0.0, 16.0, 16.0, 16.0);1
2
3
4
2
3
4
java
@Override
protected VoxelShape getSidesShape(BlockState state, BlockView world, BlockPos pos) {
boolean type = state.get(SINGLE);
Direction direction = state.get(FACING);
VoxelShape voxelShape;
if (type) {
switch (direction) {
case WEST -> voxelShape = WEST_SHAPE.asCuboid();
case EAST -> voxelShape = EAST_SHAPE.asCuboid();
case SOUTH -> voxelShape = SOUTH_SHAPE.asCuboid();
case NORTH -> voxelShape = NORTH_SHAPE.asCuboid();
default -> throw new MatchException(null, null);
}
return voxelShape;
} else {
return VoxelShapes.fullCube();
}
}
@Override
protected VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return this.getSidesShape(state, world, pos);
}
@Override
protected VoxelShape getCollisionShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return this.getSidesShape(state, world, 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 canReplace(BlockState state, ItemPlacementContext context) {
Direction direction = state.get(FACING);
if (context.getStack().isOf(this.asItem()) && state.get(SINGLE)) {
if (context.canReplaceExisting()) {
return context.getSide().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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
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/mod-id/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 Model VERTICAL_SLAB = block("vertical_slab", TextureKey.BOTTOM, TextureKey.TOP, TextureKey.SIDE);
//helper method for creating Models
private static Model block(String parent, TextureKey... requiredTextureKeys) {
return new Model(Optional.of(Identifier.of(FabricDocsReference.MOD_ID, "block/" + parent)), Optional.empty(), requiredTextureKeys);
}
//helper method for creating Models with variants
private static Model block(String parent, String variant, TextureKey... requiredTextureKeys) {
return new Model(Optional.of(Identifier.of(FabricDocsReference.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/mod-id/models/block/. Le TextureKey rappresentano i "segnaposto" (#bottom, #top, ...) come oggetti.
Usare le Mappe di Texture
Cosa fa TextureMap? In effetti fornisce gli identificativi che puntano alle texture. Tecnicamente funziona proprio come una mappa normale - si associa una TextureKey (chiave) con un Identifier (valore).
Puoi usare quelle vanilla, come TextureMap.all()(che associa tute le TextureKey allo stesso Identifier), o crearne una nuova creandone una nuova istanza e usando .put() per associare valori alle chiavi.
TIP
TextureMap.all() associa tutte le TextureKey allo stesso Identifier, indipendentemente dalla loro quantità!
Poiché vogliamo usare le texture del Tronco di Quercia, ma abbiamo le TextureKey BOTTOM, TOP e SIDE, ne dovremo creare un'altra.
java
public static TextureMap blockAndTopForEnds(Block block) {
return new TextureMap()
.put(TextureKey.TOP, ModelIds.getBlockSubModelId(block, "_top"))
.put(TextureKey.BOTTOM, ModelIds.getBlockSubModelId(block, "_top"))
.put(TextureKey.SIDE, ModelIds.getBlockModelId(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
Tutte le TextureKey nella TextureMap devono corrispondere a tutte le TextureKey nel tuo modello di blocco genitore!
Metodo BlockStateSupplier Personalizzato
Il BlockStateSupplier contiene tutte le varianti degli stati del blocco, le rotazioni, e altre opzioni come uvlock.
java
private static BlockModelDefinitionCreator createVerticalSlabBlockStates(Block vertSlabBlock, Identifier vertSlabId, Identifier fullBlockId) {
WeightedVariant vertSlabModel = BlockStateModelGenerator.createWeightedVariant(vertSlabId);
WeightedVariant fullBlockModel = BlockStateModelGenerator.createWeightedVariant(fullBlockId);
return VariantsBlockModelDefinitionCreator.of(vertSlabBlock)
.with(BlockStateVariantMap.models(VerticalSlabBlock.FACING, VerticalSlabBlock.SINGLE)
.register(Direction.NORTH, true, vertSlabModel.apply(BlockStateModelGenerator.UV_LOCK))
.register(Direction.EAST, true, vertSlabModel.apply(BlockStateModelGenerator.UV_LOCK).apply(BlockStateModelGenerator.ROTATE_Y_90))
.register(Direction.SOUTH, true, vertSlabModel.apply(BlockStateModelGenerator.UV_LOCK).apply(BlockStateModelGenerator.ROTATE_Y_180))
.register(Direction.WEST, true, vertSlabModel.apply(BlockStateModelGenerator.UV_LOCK).apply(BlockStateModelGenerator.ROTATE_Y_270))
.register(Direction.NORTH, false, fullBlockModel.apply(BlockStateModelGenerator.UV_LOCK))
.register(Direction.EAST, false, fullBlockModel.apply(BlockStateModelGenerator.UV_LOCK))
.register(Direction.SOUTH, false, fullBlockModel.apply(BlockStateModelGenerator.UV_LOCK))
.register(Direction.WEST, false, fullBlockModel.apply(BlockStateModelGenerator.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 creeremo un nuovo VariantsBlockStateSupplier tramite VariantsBlockStateSupplier.create(). Poi creeremo una nuova BlockStateVariantMap che contiene parametri per tutte le varianti del blocco, in questo caso FACING e SINGLE, e la passeremo nel nostro VariantsBlockStateSupplier. Puoi indicare il modello e le trasformazioni (uvlock, rotazione) da usare in .register(). Per esempio:
- Sulla prima linea, il blocco è in direzione nord, ed è singolo => useremo il modello senza ruotarlo.
- Sulla quarta linea, il blocco è in direzione ovest, ed è singolo => ruoteremo il modello di 270° sull'asse Y.
- Sulla sesta linea, il blocco è in direzione est, ma non è singolo => sembra un normale tronco di quercia => 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?
BlockStateModelGenerator 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.TextureMap texturesdefinisce le texture che il modello usa effettivamente. Controlla il capitolo Usare le Mappe di Texture.
java
public static void registerVerticalSlab(BlockStateModelGenerator generator, Block vertSlabBlock, Block fullBlock, TextureMap textures) {
Identifier slabModel = VERTICAL_SLAB.upload(vertSlabBlock, textures, generator.modelCollector);
Identifier fullBlockModel = ModelIds.getBlockModelId(fullBlock);
generator.blockStateCollector.accept(createVerticalSlabBlockStates(vertSlabBlock, slabModel, fullBlockModel));
generator.registerParentedItemModel(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.upload(). Poi otteniamo l'Identifier del modello di blocco intero con ModelIds.getBlockModelId(), e passiamo entrambi i modelli in createVerticalSlabBlockStates. Il BlockStateSupplier viene passato nel blockStateCollector, di modo che i file JSON vengano effettivamente generati. Inoltre, creiamo un modello per l'oggetto di lastra verticale con BlockStateModelGenerator.registerParentedItemModel().
È 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 il test di esempio nell'API di Fabric e nella Mod di Riferimento 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.








