🇩🇪 Deutsch (German)
🇩🇪 Deutsch (German)
Erscheinungsbild
🇩🇪 Deutsch (German)
🇩🇪 Deutsch (German)
Erscheinungsbild
Diese Seite ist für folgende Version geschrieben:
1.21.4
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 TextureKey
s 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
TextureKey
s 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 TextureKey
s in deiner TextureMap müssen mit den TextureKey
s 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.