🇵🇱 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ę createBlockEntity
BlockEntityProvider
samodzielnie i nadpisać metodę createBlockEntity
W 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: