🇩🇪 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
Block Entitäten sind ein Weg für Blöcke zusätzliche Daten, die nicht Teil des Blockzustands sind, zu speichern: Inventarinhalte, benutzerdefinierter Name und so weiter. Minecraft nutzt Block Entitäten für Blöcke, wie Kisten, Öfen und Befehlsblöcke.
Als Beispiel werden wir einen Block erstellen, der zählt, wie oft er mit der rechten Maustaste angeklickt wurde.
Damit Minecraft die neuen Block Entitäten erkennt und lädt, müssen wir einen Block Entität Typen erstellen. Das machen wir, indem wir die BlockEntity
Klasse erweitern und in einer neuen ModBlockEntities
Klasse registrieren.
public class CounterBlockEntity extends BlockEntity {
public CounterBlockEntity(BlockPos pos, BlockState state) {
super(ModBlockEntities.COUNTER_BLOCK_ENTITY, pos, state);
}
}
Wenn eine BlockEntity
registriert wird, gibt es einen BlockEntityType
zurück, wie bei dem COUNTER_BLOCK_ENTITY
, welches wir oben benutzt haben:
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
Man beachte, dass der Konstruktor von der CounterBlockEntity
zwei Parameter benötigt, der Konstruktor von der BlockEntity
jedoch drei: den BlockEntityType
, BlockPos
und den BlockState
. Wenn wir den BlockEntityType
nicht hart kodiert hätten, würde die Klasse ModBlockEntities
nicht kompiliert werden! Das liegt daran, dass die BlockEntityFactory
, die ein funktionales Interface ist, eine Funktion beschreibt, die nur zwei Parameter benötigt, genau wie unser Konstruktor.
Um als Nächstes die Block Entität zu nutzen, brauchen wir einen Block, der BlockEntityProvider
implementiert. Lass uns einen erstellen und CounterBlock
nennen.
TIP
Es gibt zwei Wege, um das zu machen:
BlockWithEntity
erweitert und die createBlockEntity
Methode implementiert (und die getRenderType
Methode, da BlockWithEntity
den Block standardmäßig Unsichtbar macht)BlockEntityProvider
implementiert und die createBlockEntity
Methode überschreibtWir werden in diesem Beispiel den ersten Weg nutzen, da BlockWithEntity
ein paar nützliche Funktionen anbietet.
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);
}
}
Da wir die BlockWithEntity
Klasse erweitern, müssen wir auch die createCodec
Methode implementieren, was aber recht leicht ist.
Im Gegensatz zu Blöcken, die Singletons sind, wird für jede Instanz des Blocks eine neue Blockentität erstellt. Dies geschieht mit der Methode createBlockEntity
, die die Position und den BlockState
entgegennimmt und ein BlockEntity
zurückgibt, oder null
, wenn es keins geben sollte.
Vergiss nicht, den Block in der Klasse ModBlocks
zu registrieren, genau wie in der Anleitung Deinen ersten Block erstellen:
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
);
Jetzt, da wir eine Blockentität haben, können wir sie verwenden, um die Anzahl der Rechtsklicks auf den Block zu speichern. Dafür werden wir der Klasse CounterBlockEntity
ein Feld clicks
hinzufügen:
private int clicks = 0;
public int getClicks() {
return clicks;
}
public void incrementClicks() {
clicks++;
markDirty();
}
Die Methode markDirty
, die in incrementClicks
verwendet wird, teilt dem Spiel mit, dass die Daten dieser Entität aktualisiert wurden; dies wird nützlich sein, wenn wir die Methoden hinzufügen, um den Zähler zu serialisieren und ihn aus der Speicherdatei zurückzuladen.
Als Nächstes müssen wir dieses Feld jedes Mal erhöhen, wenn der Block mit der rechten Maustaste angeklickt wird. Dies geschieht indem die Methode onUse
in der Klasse CounterBlock
überschrieben wird:
@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;
}
Da die BlockEntity
nicht an die Methode übergeben wird, verwenden wir world.getBlockEntity(pos)
, und wenn die BlockEntity
nicht gültig ist, kehren wir aus der Methode zurück.
Da wir nun einen funktionierenden Block haben, sollten wir dafür sorgen, dass der Zähler zwischen den Neustarts des Spiels nicht zurückgesetzt wird. Dies geschieht durch Serialisierung in NBT, wenn das Spiel speichert, und Deserialisierung, wenn es geladen wird.
Die Serialisierung erfolgt mit der Methode writeNbt
:
@Override
protected void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
nbt.putInt("clicks", clicks);
super.writeNbt(nbt, registryLookup);
}
Hier fügen wir die Felder hinzu, die in dem übergebenen NbtCompound
gespeichert werden sollen: im Fall des Zählerblocks ist das das Feld clicks
.
Das Lesen ist ähnlich, aber anstatt in dem NbtCompound
zu speichern, holt man sich die Werte, die man vorher gespeichert hat, und speichert sie in den Feldern der BlockEntity:
@Override
protected void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
super.readNbt(nbt, registryLookup);
clicks = nbt.getInt("clicks");
}
Wenn wir nun speichern und das Spiel neu laden, sollte der Zählerblock dort weitermachen, wo er beim Speichern aufgehört hat.
Das Interface BlockEntityProvider
definiert auch eine Methode namens getTicker
, mit der für jede Instanz des Blocks bei jedem Tick Code ausgeführt werden kann. Wir können das implementieren, indem wir eine statische Methode erstellen, die als BlockEntityTicker
verwendet wird:
Die Methode getTicker
sollte auch prüfen, ob der übergebene BlockEntityType
derselbe ist wie der, den wir verwenden, und wenn ja, die Funktion zurückgeben, die bei jedem Tick aufgerufen wird. Glücklicherweise gibt es eine Hilfsfunktion, die diese Prüfung in BlockWithEntity
durchführt:
@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
ist ein Verweis auf die statische Methode tick
, die wir in der Klasse CounterBlockEntity
erstellen sollten. Eine solche Strukturierung ist nicht erforderlich, aber es ist eine gute Praxis, um den Code sauber und übersichtlich zu halten.
Nehmen wir an, wir wollen, dass der Zähler nur alle 10 Ticks (2 Mal pro Sekunde) erhöht werden kann. Wir können dies tun, indem wir der Klasse CounterBlockEntity
ein Feld ticksSinceLast
hinzufügen und es bei jedem Tick erhöhen:
public static void tick(World world, BlockPos blockPos, BlockState blockState, CounterBlockEntity entity) {
entity.ticksSinceLast++;
}
Vergiss nicht, dieses Feld zu serialisieren und zu deserialisieren!
Jetzt können wir ticksSinceLast
verwenden, um zu prüfen, ob der Zähler in incrementClicks
erhöht werden kann:
if (ticksSinceLast < 10) return;
ticksSinceLast = 0;
TIP
Wenn die Blockentität nicht zu ticken scheint, überprüfe den Registrierungscode! Es sollte die Blöcke, die für diese Entität gültig sind, an den BlockEntityType.Builder
, übergeben, sonst wird eine Warnung in der Konsole ausgegeben:
[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: