🇩🇪 Deutsch (German)
🇩🇪 Deutsch (German)
Erscheinungsbild
🇩🇪 Deutsch (German)
🇩🇪 Deutsch (German)
Erscheinungsbild
Diese Seite ist für folgende Version geschrieben:
1.21.4
VORAUSSETZUNGEN
Stelle sicher, dass du den Prozess der Einrichtung der Datengenerierung zuerst abgeschlossen hast.
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.
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";
}
}Registriere diese Klasse in deinem DataGeneratorEntrypoint innerhalb der onInitializeDataGenerator-Methode.
@Override
public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) {
}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:
blockStateModelGenerator.registerSimpleCubeAll(ModBlocks.STEEL_BLOCK);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.
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "fabric-docs-reference:block/steel_block"
}
}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.
{
"variants": {
"": {
"model": "fabric-docs-reference:block/steel_block"
}
}
}Die registerSingleton-Methode liefert JSON-Modelldateien basierend auf dem übergebenen TexturedModel und einer einzelnen Blockzustand-Variante.
blockStateModelGenerator.registerSingleton(ModBlocks.PIPE_BLOCK, TexturedModel.END_FOR_TOP_CUBE_COLUMN);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.
{
"parent": "minecraft:block/cube_column",
"textures": {
"end": "fabric-docs-reference:block/pipe_block_top",
"side": "fabric-docs-reference:block/pipe_block"
}
}TIP
Wenn du dich nicht entscheiden kannst, welches TextureModel du verwenden sollst, öffne die Klasse TexturedModel und sieh dir die TextureMaps an!
blockStateModelGenerator.registerCubeAllModelTexturePool(ModBlocks.RUBY_BLOCK)
.stairs(ModBlocks.RUBY_STAIRS)
.slab(ModBlocks.RUBY_SLAB)
.fence(ModBlocks.RUBY_FENCE);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.
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();blockStateModelGenerator.registerCubeAllModelTexturePool(ModBlocks.RUBY_BLOCK).family(ModBlocks.RUBY_FAMILY);blockStateModelGenerator.registerDoor(ModBlocks.RUBY_DOOR);
blockStateModelGenerator.registerTrapdoor(ModBlocks.RUBY_TRAPDOOR);
// blockStateModelGenerator.registerOrientableTrapdoor(ModBlocks.RUBY_TRAPDOOR);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.
ruby_door_top für die obere und ruby_door_bottom für die untere Hälfte.registerDoor() wird Modelle für alle Ausrichtungen der Tür, sowohl offen als auch geschlossen erstellen.assets/<mod_id>/textures/item/ ab.ruby_trapdoor heißt. Diese wird für alle Seiten genutzt.TrapdoorBlock eine Eigenschaft FACING hat, 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.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.
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.
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);@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);
}Überschreibe auch die Methode canReplace(), sonst kannst du die Stufe nicht zu einem vollen Block machen.
@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;
}Und du bist fertig! Du kannst jetzt den Block austesten und im Spiel platzieren.
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:
{
"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
}
}
}
]
}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:
{
"parent": "minecraft:block/cube_bottom_top",
"textures": {
"bottom": "minecraft:block/sandstone_bottom",
"side": "minecraft:block/sandstone",
"top": "minecraft:block/sandstone_top"
}
}Der Wert bottom wird den Platzhalter #bottom ersetzen und so weiter. Füge es in den Ordner resources/assets/mod_id/models/block/ ein.
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.
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);
}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.
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.
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));
}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!
BlockStateSupplier-Methode Der BlockStateSupplier beinhaltet alle Varianten an Blockzuständen, deren Rotation und anderen Optionen, wie uvlock.
private static BlockStateSupplier createVerticalSlabBlockStates(Block vertSlabBlock, Identifier vertSlabId, Identifier fullBlockId) {
VariantSetting<Boolean> uvlock = VariantSettings.UVLOCK;
VariantSetting<VariantSettings.Rotation> yRot = VariantSettings.Y;
return VariantsBlockStateSupplier.create(vertSlabBlock).coordinate(BlockStateVariantMap.create(VerticalSlabBlock.FACING, VerticalSlabBlock.SINGLE)
.register(Direction.NORTH, true, BlockStateVariant.create().put(VariantSettings.MODEL, vertSlabId).put(uvlock, true))
.register(Direction.EAST, true, BlockStateVariant.create().put(VariantSettings.MODEL, vertSlabId).put(uvlock, true).put(yRot, VariantSettings.Rotation.R90))
.register(Direction.SOUTH, true, BlockStateVariant.create().put(VariantSettings.MODEL, vertSlabId).put(uvlock, true).put(yRot, VariantSettings.Rotation.R180))
.register(Direction.WEST, true, BlockStateVariant.create().put(VariantSettings.MODEL, vertSlabId).put(uvlock, true).put(yRot, VariantSettings.Rotation.R270))
.register(Direction.NORTH, false, BlockStateVariant.create().put(VariantSettings.MODEL, fullBlockId).put(uvlock, true))
.register(Direction.EAST, false, BlockStateVariant.create().put(VariantSettings.MODEL, fullBlockId).put(uvlock, true))
.register(Direction.SOUTH, false, BlockStateVariant.create().put(VariantSettings.MODEL, fullBlockId).put(uvlock, true))
.register(Direction.WEST, false, BlockStateVariant.create().put(VariantSettings.MODEL, fullBlockId).put(uvlock, true)));
}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:
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 an generateBlockStateModels übergeben haben.Block vertSlabBlock ist der Block, zu dem wir die JSONs generieren werden.Block fullBlock - ist das Modell, dass genutzt wird, wenn die Eigenschaft SINGLE false ist = der Stufenblock sieht wie ein voller Block aus.TextureMap textures definiert die tatsächlichen Texturen, die das Modell nutzt. Siehe das Kapitel Die Texture Map nutzen verwenden.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);
}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:
CustomBlockStateModelGenerator.registerVerticalSlab(
blockStateModelGenerator,
ModBlocks.VERTICAL_OAK_LOG_SLAB,
Blocks.OAK_LOG,
CustomBlockStateModelGenerator.blockAndTopForEnds(Blocks.OAK_LOG)
);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.