🇵🇱 Polski (Polish)
🇵🇱 Polski (Polish)
Wygląd
🇵🇱 Polski (Polish)
🇵🇱 Polski (Polish)
Wygląd
Ta strona jest napisana dla wersji:
1.21.4
Byty bloków stanowią sposób na przechowywanie dodatkowych danych dla bloku, które nie są częścią stanu bloku: zawartości ekwipunku, własnej nazwy itd. W grze Minecraft stosuje się byty bloków do tworzenia bloków, takich jak skrzynie, piece i bloki poleceń.
Jako przykład utworzymy blok, który zlicza, ile razy został kliknięty prawym przyciskiem myszy.
Aby Minecraft rozpoznał i załadował nowe byty bloków, musimy utworzyć typ bytu bloku. Można to zrobić poprzez rozszerzenie klasy BlockEntity i zarejestrowanie jej w nowej klasie ModBlockEntities.
public class CounterBlockEntity extends BlockEntity {
public CounterBlockEntity(BlockPos pos, BlockState state) {
super(ModBlockEntities.COUNTER_BLOCK_ENTITY, pos, state);
}
}Zarejestrowanie BlockEntity daje w wyniku BlockEntityType podobny do COUNTER_BLOCK_ENTITY, którego użyliśmy powyżej:
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
Zwróć uwagę, że konstruktor CounterBlockEntity przyjmuje dwa parametry, natomiast konstruktor BlockEntity przyjmuje trzy: BlockEntityType, BlockPos i BlockState. Gdybyśmy nie zakodowali na stałe BlockEntityType, klasa ModBlockEntities nie skompilowałaby się! Dzieje się tak, ponieważ BlockEntityFactory, jest interfejsem funkcyjnym, opisuje funkcję, która przyjmuje tylko dwa parametry, podobnie jak nasz konstruktor.
Następnie, aby faktycznie użyć bytu bloku, potrzebujemy bloku implementującego BlockEntityProvider. Utwórzmy jeden i nazwijmy go CounterBlock.
TIP
Można do tego podejść na dwa sposoby:
BlockWithEntity i zaimplementować metodę createBlockEntityBlockEntityProvider samodzielnie i nadpisać metodę createBlockEntityW tym przykładzie wykorzystamy pierwsze podejście, ponieważ BlockWithEntity również udostępnia przydatne narzędzia.
public class CounterBlock extends BlockWithEntity {
public CounterBlock(Settings settings) {
super(settings);
}
@Override
protected MapCodec<? extends BlockWithEntity> getCodec() {
return createCodec(CounterBlock::new);
}
@Nullable
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new CounterBlockEntity(pos, state);
}
}Użycie BlockWithEntity jako klasy nadrzędnej oznacza, że musimy również zaimplementować metodę createCodec, co jest dość proste.
W przeciwieństwie do bloków, które są singletonami, dla każdego bytu bloku tworzony jest nowy element bloku. Do tego celu służy metoda createBlockEntity, która przyjmuje pozycję i BlockState, i zwraca BlockEntity lub null, jeśli nie powinno go być.
Nie zapomnij zarejestrować bloku w klasie ModBlocks tak jak w poradniku Tworzenie pierwszego bloku:
public static final Block COUNTER_BLOCK = register(
"counter_block",
CounterBlock::new,
AbstractBlock.Settings.create(),
true
);Teraz gdy mamy blok, możemy go użyć do zapisania liczby kliknięć prawym przyciskiem myszy na bloku. Zrobimy to, dodając pole clicks do klasy CounterBlockEntity:
private int clicks = 0;
public int getClicks() {
return clicks;
}
public void incrementClicks() {
clicks++;
markDirty();
}Metoda markDirty używana w incrementClicks informuje grę, że dane tego bytu zostały zaktualizowane. Będzie to przydatne, gdy dodamy metody serializujące licznik i wczytujące go z pliku zapisu.
Następnie musimy zwiększać wartość tego pola za każdym razem, gdy klikniemy prawym przyciskiem myszy na bloku. Można to zrobić poprzez nadpisanie metody onUse w klasie 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;
}Ponieważ BlockEntity nie jest przekazywany do metody, używamy world.getBlockEntity(pos) i jeśli BlockEntity jest nieprawidłowy, zwracamy z metody.

Teraz gdy mamy już blok funkcjonalny, powinniśmy sprawić, aby licznik nie resetował się pomiędzy restartami gry. Można to zrobić poprzez serializację do NBT podczas zapisywania gry i deserializację podczas jej ładowania.
Serializacja odbywa się za pomocą metody writeNbt:
@Override
protected void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
nbt.putInt("clicks", clicks);
super.writeNbt(nbt, registryLookup);
}Tutaj dodajemy pola, które powinny zostać zapisane w przekazanym NbtCompound: w przypadku bloku licznika jest to pole clicks.
Odczyt przebiega podobnie, jednak, zamiast zapisywać w NbtCompound otrzymujesz wcześniej zapisane wartości i zapisujesz je w polach BlockEntity:
@Override
protected void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
super.readNbt(nbt, registryLookup);
clicks = nbt.getInt("clicks");
}Teraz, jeśli zapiszemy i ponownie wczytamy grę, blok licznika powinien być kontynuowany od miejsca, w którym został przerwany podczas zapisywania.
Interfejs BlockEntityProvider definiuje również metodę o nazwie getTicker, która może być używana do uruchamiania kodu przy każdym cyklu dla każdej instancji bloku. Możemy to zaimplementować, tworząc metodę statyczną, która będzie używana jako BlockEntityTicker:
Metoda getTicker powinna również sprawdzać, czy przekazany typ BlockEntityType jest taki sam, jak ten, którego używamy, i jeśli tak, zwracać funkcję, która będzie wywołana przy każdym ticku. Na szczęście istnieje funkcja narzędziowa, która wykonuje sprawdzenie w 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 to odwołanie do metody statycznej tick, którą powinniśmy utworzyć w klasie CounterBlockEntity. Takie strukturyzowanie nie jest konieczne, ale dobrym zwyczajem jest zachowanie czystości i organizacji kodu.
Załóżmy, że chcemy, aby licznik można było zwiększać tylko raz na 10 ticków (2 razy na sekundę). Możemy to zrobić, dodając pole ticksSinceLast do klasy CounterBlockEntity i zwiększając je co każdy tick:
public static void tick(World world, BlockPos blockPos, BlockState blockState, CounterBlockEntity entity) {
entity.ticksSinceLast++;
}Nie zapomnij o serializacji i deserializacji tego pola!
Teraz możemy użyć ticksSinceLast, aby sprawdzić, czy licznik można zwiększyć w incrementClicks:
if (ticksSinceLast < 10) return;
ticksSinceLast = 0;TIP
Jeśli byt bloku nie jest zaznaczony, spróbuj sprawdzić kod rejestracyjny! Powinien przekazać bloki, które są prawidłowe dla tego bytu, do BlockEntityType.Builder, w przeciwnym razie w konsoli pojawi się ostrzeżenie:
[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: