🇮🇹 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
I blocchi-entità sono un modo per memorizzare dati aggiuntivi per un blocco, che non siano parte dello stato del blocco: contenuti dell'inventario, nome personalizzato e così via. Minecraft usa i blocchi-entità per blocchi come bauli, fornaci, e blocchi dei comandi.
Come esempio, creeremo un blocco che conta quante volte esso è stato cliccato con il tasto destro.
Per fare in modo che Minecraft riconosca e carichi i nuovi blocchi-entità, dobbiamo creare un tipo di blocco-entità. Questo si fa estendendo la classe BlockEntity
e registrandola in una nuova classe ModBlockEntities
.
public class CounterBlockEntity extends BlockEntity {
public CounterBlockEntity(BlockPos pos, BlockState state) {
super(ModBlockEntities.COUNTER_BLOCK_ENTITY, pos, state);
}
}
La registrazione di un BlockEntity
produce un BlockEntityType
come il COUNTER_BLOCK_ENTITY
che abbiamo usato sopra:
public static final BlockEntityType<CounterBlockEntity> COUNTER_BLOCK_ENTITY =
register("counter", CounterBlockEntity::new, ModBlocks.COUNTER_BLOCK);
private static <T extends BlockEntity> BlockEntityType<T> register(String name,
FabricBlockEntityTypeBuilder.Factory<? extends T> entityFactory,
Block... blocks) {
Identifier id = Identifier.of(FabricDocsReference.MOD_ID, name);
return Registry.register(Registries.BLOCK_ENTITY_TYPE, id, FabricBlockEntityTypeBuilder.<T>create(entityFactory, blocks).build());
}
TIP
Nota come il costruttore del CounterBlockEntity
prenda due parametri, ma il costruttore del BlockEntity
ne prenda tre: il BlockEntityType
, la BlockPos
, e lo BlockState
. Se non fissassimo nel codice il BlockEntityType
, la classe ModBlockEntities
non compilerebbe! Questo perché la BlockEntityFactory
, che è un'interfaccia funzionale, descrive una funzione che prende solo due parametri, proprio come il nostro costruttore.
Dopo di che, per usare effettivamente il blocco-entità, ci serve un blocco che implementi BlockEntityProvider
. Creiamone uno e chiamiamolo CounterBlock
.
TIP
Ci sono due modi per approcciarsi a questo:
BlockWithEntity
e implementi il metodo createBlockEntity
(e il metodo getRenderType
, poiché BlockWithEntity
li rende invisibili in maniera predefinita)BlockEntityProvider
da solo e faccia override del metodo createBlockEntity
Useremo il primo approccio in questo esempio, poiché BlockWithEntity
fornisce anche alcune utilità comode.
public class CounterBlock extends BlockWithEntity {
public CounterBlock(Settings settings) {
super(settings);
}
@Override
protected MapCodec<? extends BlockWithEntity> getCodec() {
return createCodec(CounterBlock::new);
}
@Override
protected BlockRenderType getRenderType(BlockState state) {
return BlockRenderType.MODEL;
}
@Nullable
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new CounterBlockEntity(pos, state);
}
}
Usare BlockWithEntity
come classe genitore significa che dobbiamo anche implementare il metodo createCodec
, il che è piuttosto semplice.
A differenza dei blocchi, che sono dei singleton, viene creato un nuovo blocco-entità per ogni istanza del blocco. Questo viene fatto con il metodo createBlockEntity
, che prende la posizione e il BlockState
, e restituisce un BlockEntity
, o null
se non ce ne dovrebbe essere uno.
Non dimenticare di registrare il blocco nella classe ModBlocks
, proprio come nella guida Creare il Tuo Primo Blocco:
public static final RegistryKey<Block> COUNTER_BLOCK_KEY = RegistryKey.of(
RegistryKeys.BLOCK,
Identifier.of(FabricDocsReference.MOD_ID, "counter_block")
);
public static final Block COUNTER_BLOCK = register(
new CounterBlock(AbstractBlock.Settings.create().registryKey(COUNTER_BLOCK_KEY)), COUNTER_BLOCK_KEY, true
);
Ora che abbiamo un blocco-entità, possiamo usarlo per memorizzare il numero di clic con il tasto destro sul blocco. Faremo questo aggiungendo un attributo clicks
alla classe CounterBlockEntity
:
private int clicks = 0;
public int getClicks() {
return clicks;
}
public void incrementClicks() {
clicks++;
markDirty();
}
Il metodo markDirty
, usato in incrementClicks
, informa il gioco che i dati dell'entità sono stati aggiornati; questo sarà utile quando aggiungeremo i metodi per serializzare il contatore e ricaricarlo dal file di salvataggio.
Il prossimo passaggio è incrementare questo attributo ogni volta che il blocco viene cliccato con il tasto destro. Questo si fa facendo override del metodo onUse
nella classe CounterBlock
:
@Override
protected ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) {
if (!(world.getBlockEntity(pos) instanceof CounterBlockEntity counterBlockEntity)) {
return super.onUse(state, world, pos, player, hit);
}
counterBlockEntity.incrementClicks();
player.sendMessage(Text.literal("You've clicked the block for the " + counterBlockEntity.getClicks() + "th time."), true);
return ActionResult.SUCCESS;
}
Poiché il BlockEntity
non viene passato nel metodo, usiamo world.getBlockEntity(pos)
, e se il BlockEntity
non è valido, usciamo dal metodo.
Ora che abbiamo un blocco funzionante, dovremmo anche fare in modo che il contatore non si resetti dopo un riavvio del gioco. Questo si fa serializzandolo in NBT quando si salva il gioco, e deserializzandolo durante il caricamento.
La serializzazione avviene con il metodo writeNbt
:
@Override
protected void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
nbt.putInt("clicks", clicks);
super.writeNbt(nbt, registryLookup);
}
Qui, aggiungiamo gli attributi che dovrebbero essere salvati al NbtCompound
passato: nel caso del blocco contatore, l'attributo clicks
.
La lettura è simile, ma invece di salvare nel NbtCompound
si ottengono i valori salvati precendentemente, e salvarli negli attributi del BlockEntity
:
@Override
protected void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
super.readNbt(nbt, registryLookup);
clicks = nbt.getInt("clicks");
}
Ora, salvando e ricaricando il gioco, il blocco contatore dovrebbe riprendere da dove è stato salvato.
L'interfaccia BlockEntityProvider
definisce anche un metodo chiamato getTicker
, che può essere usato per eseguire del codice ogni tick per ogni istanza del blocco. Possiamo implementarlo creando un metodo statico che verrà usato come BlockEntityTicker
:
Il metodo getTicker
dovrebbe anche controllare che il BlockEntityType
passato sia quello che stiamo usando; se lo è, restituirà la funzione da chiamare a ogni tick. Vi è una funzione di utilità che fa il controllo in BlockWithEntity
:
@Nullable
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(World world, BlockState state, BlockEntityType<T> type) {
return validateTicker(type, ModBlockEntities.COUNTER_BLOCK_ENTITY, CounterBlockEntity::tick);
}
CounterBlockEntity::tick
è un riferimento al metodo statico tick
che dobbiamo creare nella classe CounterBlockEntity
. Non è necessario seguire questa struttura, ma buona pratica per mantenere del codice pulito e organizzato.
Diciamo di voler fare in modo che il contatore possa essere incrementato soltanto ogni 10 tick (2 volte al secondo). Possiamo fare ciò aggiungendo un attributo ticksSinceLast
alla classe CounterBlockEntity
, e incrementandolo a ogni tick:
public static void tick(World world, BlockPos blockPos, BlockState blockState, CounterBlockEntity entity) {
entity.ticksSinceLast++;
}
Non dimenticare di serializzare e deserializzare questo attributo!
Ora possiamo usare ticksSinceLast
per controllare se il contatore può essere incrementato in incrementClicks
:
if (ticksSinceLast < 10) return;
ticksSinceLast = 0;
TIP
Se sembra che il blocco-entità non faccia tick, prova a controllare il codice della registrazione! Dovrebbe passare i blocchi validi per questa entità nel BlockEntityType.Builder
, o altrimenti scriverà un avviso nella console:
[13:27:55] [Server thread/WARN] (Minecraft) Block entity fabric-docs-reference:counter @ BlockPos{x=-29, y=125, z=18} state Block{fabric-docs-reference:counter_block} invalid for ticking: