VORAUSSETZUNGEN
Stelle sicher, dass du den Prozess der Einrichtung der Datengenerierung zuerst abgeschlossen hast.
Einrichten
Zuerst müssen wir unseren ModelProvider erstellen. Erstelle eine Klasse, welche extends FabricModelProvider. Implementiere beide abstrakten Methoden: generateBlockStateModels und generateItemModels. Zum Schluss, erstelle einen Konstruktor, der zu super passt.
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
Registriere diese Klasse in deinem DataGeneratorEntrypoint innerhalb der onInitializeDataGenerator-Methode.
Blockzustände und Blockmodelle
java
@Override
public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) {
}1
2
3
2
3
Für Blockmodelle werden wir uns hauptsächlich auf die generateBlockStateModels-Methode fokusieren. Beachte den Parameter BlockStateModelGenerator blockStateModelGenerator - dieses Objekt wird für die Generierung aller JSON-Dateien verantwortlich sein. Hier sind einige praktische Beispiele, die du zur Generierung deiner gewünschten Modelle verwenden kannst:
Einfacher Cube All
java
blockStateModelGenerator.registerSimpleCubeAll(ModBlocks.STEEL_BLOCK);1
Dies ist die am häufigsten verwendete Funktion. Sie generiert eine JSON-Modell-Datei für ein normales cube_all Blockmodell. Eine Textur wird für alle sechs Seiten genutzt, in diesem Fall nutzen wir 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
Sie generiert auch eine Blockzustand-JSON-Datei. Da wir keine Blockzustand-Eigenschaften (z. B. Achsen, Ausrichtung, ...) haben, ist eine Variante ausreichend und wird jedes Mal verwendet, wenn der Block platziert wird.
json
{
"variants": {
"": {
"model": "fabric-docs-reference:block/steel_block"
}
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
Singletons
Die registerSingleton-Methode liefert JSON-Modelldateien basierend auf dem übergebenen TexturedModel und einer einzelnen Blockzustand-Variante.
java
blockStateModelGenerator.registerSingleton(ModBlocks.PIPE_BLOCK, TexturedModel.END_FOR_TOP_CUBE_COLUMN);1
Diese Methode wird Modelle für einen normalen Würfel generieren, der die Texturdatei pipe_block für die Seiten und die Texturdatei pipe_block_top für die obere und untere Seite nutzt.
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
Wenn du dich nicht entscheiden kannst, welches TextureModel du verwenden sollst, öffne die Klasse TexturedModel und sieh dir die TextureMaps an!
Block-Textur-Pool
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
Eine andere nützliche Methode ist registerCubeAllModelTexturePool: Definiere die Texturen, indem du den "Basisblock" übergibst, und füge dann die "Kinder" hinzu, die die gleichen Texturen haben. In diesem Fall haben wir den RUBY_BLOCK übergeben, so dass die Treppe, die Stufe und der Zaun die Textur RUBY_BLOCK verwenden werden.
WARNING
Sie wird auch ein einfaches Cube All JSON-Modell für den "Basisblock" generieren, um sicherzustellen, dass er ein Blockmodell hat.
Sei dir dessen bewusst, wenn du das Blockmodell dieses bestimmten Blocks änderst, da dies zu einem Fehler führen wird.
Du kannst auch eine BlockFamily anhängen, die Modelle für alle ihre "Kinder" generieren wird.
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
Türen und Falltüren
java
blockStateModelGenerator.registerDoor(ModBlocks.RUBY_DOOR);
blockStateModelGenerator.registerTrapdoor(ModBlocks.RUBY_TRAPDOOR);
// blockStateModelGenerator.registerOrientableTrapdoor(ModBlocks.RUBY_TRAPDOOR);1
2
3
2
3
Türen und Falltüren sein ein wenig anders. Hier musst du drei neue Texturen erstellen - zwei für die Türe, und eine für die Falltüre.
- Die Tür:
- Sie hat zwei Teile - die obere und die untere Hälfte. Jede benötigt ihre eigene Textur: In diesem Fall
ruby_door_topfür die obere undruby_door_bottomfür die untere Hälfte. - Die Methode
registerDoor()wird Modelle für alle Ausrichtungen der Tür, sowohl offen als auch geschlossen erstellen. - Du benötigst auch eine Itemtextur! Lege sie in dem Ordner
assets/<mod_id>/textures/item/ab.
- Die Falltür:
- Hier benötigst du nur eine Textur, die in diesem Fall
ruby_trapdoorheißt. Diese wird für alle Seiten genutzt. - Da
TrapdoorBlockeine EigenschaftFACINGhat, kannst du die auskommentierte Methode verwenden, um Modell-Dateien mit rotierten Texturen zu generieren = Die Falltüre wird "orientierbar" sein. Andernfalls sieht sie immer gleich aus, egal in welche Richtung sie gerichtet ist.
Benutzerdefinierte Blockklasse
In diesem Abschnitt werden wir die Modelle für eine vertikale Eichenstammstufe, mit einer Eichenstamm-Textur, erstellen.
Punkte 2. - 6. werden in einer inneren, statischen Hilfsklasse namens CustomBlockStateModelGenerator deklariert.
Benutzerdefinierte Blockmodelle
Erstelle einen Block VerticalSlab mit einer Eigenschaft FACING und einer boolean-Eigenschaft SINGLE, wie in dem Tutorial Block States beschrieben. SINGLE zeigt an, ob beide Stufen sind. Dann solltest du getOutlineShape und getCollisionShape überschreiben, so dass die Umrandung korrekt gerendert wird und der Block die richtige Kollisionsform hat.
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
Überschreibe auch die Methode canReplace(), sonst kannst du die Stufe nicht zu einem vollen Block machen.
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
Und du bist fertig! Du kannst jetzt den Block austesten und im Spiel platzieren.
Übergeordnetes Blockmodell
Lasst und jetzt ein übergeordnetes Blockmodell erstellen. Es bestimmt die Größe, Position in der Hand oder in anderen Slots und die x und y Koordinaten der Textur. Es wird empfohlen für dies einen Editor, wie Blockbench zu verwenden, da die manuelle Erstellung ein wirklich mühsamer Prozess ist. Es sollte wie folgt aussehen:
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
Für weitere Informationen, siehe dir an wie Blockzustände formatiert sind. Beachte die Schlüsselwörter #bottom, #top, #side. Sie dienen als Variablen, die von Modellen gesetzt werden können, die dieses Modell als übergeordnetes Modell haben:
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
Der Wert bottom wird den Platzhalter #bottom ersetzen und so weiter. Füge es in den Ordner resources/assets/mod_id/models/block/ ein.
Benutzerdefiniertes Modell
Eine weitere Sache, die wir benötigen, ist eine Instanz der Klasse Model. Sie wird das tatsächliche übergeordnete Blockmodell in unserem Mod repräsentieren.
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
Die Methode block() erstellt ein neues Model, das auf die Datei vertical_slab.json in unserem Ordner resources/assets/mod_id/models/block/ zeigt. Die TextureKeys repräsentieren die "Platzhalter" (#bottom, #top, ...) als ein Objekt.
Die Texture Map verwenden
Was macht die TextureMap? Sie liefert die Identifikatoren, die auf die Textur verweisen. Technisch gesehen verhält sie sich wie eine normale Map - man verbindet einen TextureKey (Schlüssel) mit einem Identifier (Wert).
Du kannst entweder die von Vanilla verwenden, wie TextureMap.all() (die alle TextureKeys mit dem selben Identifikator verknüpft), oder eine neue erstellen, indem du eine neue Instanz erstellst und dann .put() aufrufst, um die Schlüssel mit Werten zu verknüpfen.
TIP
TextureMap.all() verknüpft alle TextureKeys mit dem selben Identifikator, egal wie viele es davon gibt!
Da wir die Eichenstammtexturen nutzen wollen, aber die BOTTOM, TOP und SIDE TextureKeys haben, müssen wir eine neue erstellen.
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
Die bottom und top Flächen werden oak_log_top.png verwenden, die Seiten werden oak_log.png verwenden.
WARNING
Alle TextureKeys in deiner TextureMap müssen mit den TextureKeys in deinem übergeordneten Blockmodell übereinstimmen!
Benutzerdefinierte BlockStateSupplier-Methode
Der BlockStateSupplier beinhaltet alle Varianten an Blockzuständen, deren Rotation und anderen Optionen, wie 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
Zuerst erstellen wir einen neuen VariantsBlockStateSupplier mit Hilfe von VariantsBlockStateSupplier.create(). Dann erstellen wir eine neue BlockStateVariantMap, die Parameter für alle Varianten des Blocks beinhaltet, in diesem Fall FACING und SINGLE und übergeben diese in den VariantsBlockStateSupplier. Gebe an, welches Modell und welche Transformation (uvlock, rotation) bei der Verwendung von .register() genutzt wird. Zum Beispiel:
- In der ersten Zeile, zeigt der Block nach Norden und ist einzeln => Wir verwenden das Modell ohne Rotation.
- In der vierten Zeile, zeigt der Block nach Westen und ist einzeln => Wir rotieren das Modell auf der Y-Achse für 270°.
- In der sechsten Zeile, zeigt der Block nach Osten, ist jedoch nicht einzeln => Er sieht aus wie ein normaler Eichenstamm => Wie müssen ihn nicht rotieren.
Benutzerdefinierte Methode für den Datengenerator
Der letzte Schritt - die Erstellung der tatsächlichen Methode, die du aufrufen kannst und die die JSONs generiert. Aber für was sind die Parameter?
BlockStateModelGenerator generator, das gleiche, dass wir angenerateBlockStateModelsübergeben haben.Block vertSlabBlockist der Block, zu dem wir die JSONs generieren werden.Block fullBlock- ist das Modell, dass genutzt wird, wenn die EigenschaftSINGLEfalse ist = der Stufenblock sieht wie ein voller Block aus.TextureMap texturesdefiniert die tatsächlichen Texturen, die das Modell nutzt. Siehe das Kapitel Die Texture Map nutzen verwenden.
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
Zunächst erhalten wir den Identifier des einzelnen Stufenmodell mit VERTICAL_SLAB.upload(). Dann erhalten wir den Identifier des vollen Blockmodells mit ModelIds.getBlockModelId(), und übergeben diese beiden Modelle an createVerticalSlabBlockStates. Der BlockStateSupplier wird an den blockStateCollector übergeben, so dass die JSON-Dateien tatsächlich generiert werden. Außerdem, erstellen wir ein Modell für das Item der vertikalen Stufe mit BlockStateModelGenerator.registerParentedItemModel().
Und dies ist alles! Jetzt müssen wir nur noch unsere Methode in unserem ModelProvider aufrufen:
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
Quellen und Links
Du kannst für weitere Informationen die Beispieltests in der Fabric API und im Referenz-Mod dieser Dokumentation ansehen.
Du kannst auch weitere Beispiele für die Verwendung von benutzerdefinierten Methoden für den Datengenerator finden, indem du den Open-Source-Code von Mods durchsuchst, zum Beispiel Vanilla+ Blocks und Vanilla+ Verticals von Fellteros.








