🇮🇹 Italiano (Italian)
🇮🇹 Italiano (Italian)
Aspetto
🇮🇹 Italiano (Italian)
🇮🇹 Italiano (Italian)
Aspetto
Questa pagina si applica alla versione:
1.21.4
Questa pagina si applica alla versione:
1.21.4
PREREQUISITI
Assicurati di aver prima completato il processo di configurazione della datagen.
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.
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";
}
}
Registra questa classe nella tua DataGeneratorEntrypoint
all'interno del metodo onInitializeDataGenerator
.
@Override
public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) {
}
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:
blockStateModelGenerator.registerSimpleCubeAll(ModBlocks.STEEL_BLOCK);
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
.
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "fabric-docs-reference:block/steel_block"
}
}
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.
{
"variants": {
"": {
"model": "fabric-docs-reference:block/steel_block"
}
}
}
Il metodo registerSingleton
fornisce file JSON di modelli in base al TexturedModel
che passi, e una singola variante di stato di blocco.
blockStateModelGenerator.registerSingleton(ModBlocks.PIPE_BLOCK, TexturedModel.END_FOR_TOP_CUBE_COLUMN);
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.
{
"parent": "minecraft:block/cube_column",
"textures": {
"end": "fabric-docs-reference:block/pipe_block_top",
"side": "fabric-docs-reference:block/pipe_block"
}
}
TIP
Se non sai quale TextureModel
dovresti scegliere, apri la classe TexturedModel
e dai un'occhiata alle TextureMaps
!
blockStateModelGenerator.registerCubeAllModelTexturePool(ModBlocks.RUBY_BLOCK)
.stairs(ModBlocks.RUBY_STAIRS)
.slab(ModBlocks.RUBY_SLAB)
.fence(ModBlocks.RUBY_FENCE);
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".
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);
Le porte e botole funzionano un po' diversamente. Qui, dovrai creare tre nuove texture - due per la porta, e una per la botola.
ruby_door_top
per la metà superiore e ruby_door_bottom
per l'inferiore.registerDoor()
creerà modelli per tutte le orientazioni della porta, sia aperta che chiusa.assets/mod-id/textures/item/
.ruby_trapdoor
. Verrà usata per tutti i lati.TrapdoorBlock
ha 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.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
.
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.
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);
}
Fai anche override del metodo canReplace()
, altrimenti non potresti rendere la lastra un blocco intero.
@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;
}
Tutto fatto! Ora puoi testare il blocco e piazzarlo nel gioco.
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:
{
"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
}
}
}
]
}
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:
{
"parent": "minecraft:block/cube_bottom_top",
"textures": {
"bottom": "minecraft:block/sandstone_bottom",
"side": "minecraft:block/sandstone",
"top": "minecraft:block/sandstone_top"
}
}
Il valore bottom
sostituirà il segnaposto #bottom
eccetera. Mettilo nella cartella resources/assets/mod-id/models/block/
.
Un'altra cosa che ci serve è un'istanza della classe Model
. Essa rappresenterà proprio il modello di blocco genitore nella nostra mod.
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);
}
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.
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.
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));
}
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!
BlockStateSupplier
Personalizzato Il BlockStateSupplier
contiene tutte le varianti degli stati del blocco, le rotazioni, e altre opzioni come 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)));
}
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:
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 in generateBlockStateModels
.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 textures
definisce le texture che il modello usa effettivamente. Controlla il capitolo Usare le Mappe di Texture.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);
}
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
:
CustomBlockStateModelGenerator.registerVerticalSlab(
blockStateModelGenerator,
ModBlocks.VERTICAL_OAK_LOG_SLAB,
Blocks.OAK_LOG,
CustomBlockStateModelGenerator.blockAndTopForEnds(Blocks.OAK_LOG)
);
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.