====== Добавление BlockEntity ======
===== Введение =====
BlockEntity в основном используется для хранения данных внутри блоков. Прежде чем создать его, вам понадобится [[ru:tutorial:blocks|Блок]]. В этом руководстве будет рассказано о создании вашего класса BlockEntity и его регистрации.
===== Создание BlockEntity =====
Простейший BlockEntity просто расширяет ''BlockEntity'' и использует конструктор по умолчанию. Это вполне допустимо, но не предоставит вашему блоку никаких специальных функций.
public class DemoBlockEntity extends BlockEntity {
public DemoBlockEntity(BlockPos pos, BlockState state) {
super(ExampleMod.DEMO_BLOCK_ENTITY, pos, state);
}
}
Ниже будет показано, как создать поле ''ExampleMod.DEMO_BLOCK_ENTITY''.
Вы можете просто добавить переменные в этот скелет класса или реализовать такие интерфейсы, как ''Tickable'' и ''Inventory'' , чтобы добавить больше функциональности. ''Tickable'' предоставляет один метод ''tick()'' , который вызывается один раз за тик для каждого загруженного экземпляра вашего блока в мире, в то время как ''Inventory'' позволяет вашему BlockEntity взаимодействовать с автоматизацией, такой как воронки - вероятно, позже будет отдельный туториал, полностью посвященный этому интерфейсу.
===== Регистрация вашего BlockEntity =====
Как только вы создадите класс ''BlockEntity'' , вам нужно будет зарегистрировать его, чтобы он начал функционировать. Первым шагом этого процесса является создание ''BlockEntityType'', который связывает ваши ''Block'' и ''BlockEntity'' вместе. Предполагая, что ваш ''Block'' был создан и сохранен в локальной переменной ''DEMO_BLOCK'', вы должны создать соответствующий ''BlockEntityType'' со строкой ниже. В этом руководстве идентификатор BlockEntity - ''tutorial:demo_block_entity''.
''BlockEntityType'' должен быть зарегистрирован в вашем методе ''onInitialize'', это необходимо для того, чтобы он был зарегистрирован в нужное время.
public static BlockEntityType DEMO_BLOCK_ENTITY;
@Override
public void onInitialize() {
DEMO_BLOCK_ENTITY = Registry.register(Registry.BLOCK_ENTITY_TYPE, "tutorial:demo_block_entity", FabricBlockEntityTypeBuilder.create(DemoBlockEntity::new, DEMO_BLOCK).build(null));
}
==== Connecting a Block Entity to a Block ====
Once your ''BlockEntityType'' has been created and registered, you'll need a block that is associated with it. You can do this by implementing ''BlockEntityProvider'' and overriding ''createBlockEntity''. Each time your block is placed, your Block Entity will spawn alongside it.
public class DemoBlock extends Block implements BlockEntityProvider {
[...]
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new DemoBlockEntity(pos, state);
}
}
===== Serializing Data =====
If you want to store any data in your ''BlockEntity'', you will need to save and load it, or it will only be held while the ''BlockEntity'' is loaded, and the data will reset whenever you come back to it. Luckily, saving and loading is quite simple - you only need to override ''writeNbt()'' and ''readNbt()''.
''writeNbt()'' returns a ''NBTCompound'', which should contain all of the data in your ''BlockEntity''. This data is saved to the disk and also send through packets if you need to sync your ''BlockEntity'' data with clients. It is very important to call the default implementation of ''writeNbt'', as it saves "Identifying Data" (position and ID) to the tag. Without this, any further data you try and save will be lost as it is not associated with a position and ''BlockEntityType''. Knowing this, the example below demonstrates saving an integer from your ''BlockEntity'' to the tag. In the example, the integer is saved under the key ''"number"'' - you can replace this with any string, but you can only have one entry for each key in your tag, and you will need to remember the key in order to retrieve the data later.
public class DemoBlockEntity extends BlockEntity {
// Store the current value of the number
private int number = 7;
public DemoBlockEntity(BlockPos pos, BlockState state) {
super(ExampleMod.DEMO_BLOCK_ENTITY, pos, state);
}
// Serialize the BlockEntity
@Override
public void writeNbt(NbtCompound tag) {
// Save the current value of the number to the tag
tag.putInt("number", number);
super.writeNbt(tag);
}
}
In order to retrieve the data later, you will also need to override ''readNbt''. This method is the opposite of ''writeNbt'' - instead of saving your data to a ''NBTCompound'', you are given the tag which you saved earlier, enabling you to retrieve any data that you need. As with ''writeNbt'', it is essential that you call ''super.readNbt'', and you will need to use the same keys to retrieve data that you saved. To retrieve, the number we saved earlier, see the example below.
// Deserialize the BlockEntity
@Override
public void readNbt(NbtCompound tag) {
super.readNbt(tag);
number = tag.getInt("number");
}
Once you have implemented the ''writeNbt'' and ''readNbt'' methods, you simply need to ensure that they are called at the right time. Whenever your ''BlockEntity'' data changes and needs to be saved, call ''markDirty()''. This will force the ''writeNbt'' method to be called when the world is next saved by marking the chunk which your block is in as dirty. As a general rule of thumb, simply call ''markDirty()'' whenever you change any custom variable in your ''BlockEntity'' class.
===== Sync data from server to client =====
The data is read in the server world usually. Sometimes you may have to sync all or some of the data to the client, for example, for renderering.
For version 1.17.1 and below, implement ''BlockEntityClientSerializable'' from the Fabric API. This class provides the ''fromClientTag'' and ''toClientTag'' methods, which work much the same as the previously discussed ''readNbt'' and ''writeNbt'' methods, except that they are used specifically for sending to and receiving data on the client. You may simply call ''readNbt'' and ''writeNbt'' in the ''fromClientTag'' and ''toClientTag'' methods.
For version 1.18 and above, override ''toUpdatePacket'' and ''toInitialChunkDataNbt'':
@Nullable
@Override
public Packet toUpdatePacket() {
return BlockEntityUpdateS2CPacket.create(this);
}
@Override
public NbtCompound toInitialChunkDataNbt() {
return createNbt();
}
===== Block Entity Ticking =====
1.17 has added static ticking, where before you'd implement the ''Tickable'' interface. For your block to tick, you would normally use ''getTicker'' in ''Block'', linking back to a ''Block Entity''. See below for the common implementation of ticking.
In your ''Block'' class:
public class DemoBlock extends BlockWithEntity {
[...]
@Override
public BlockRenderType getRenderType(BlockState state) {
// With inheriting from BlockWithEntity this defaults to INVISIBLE, so we need to change that!
return BlockRenderType.MODEL;
}
@Override
public BlockEntityTicker getTicker(World world, BlockState state, BlockEntityType type) {
return checkType(type, ExampleMod.DEMO_BLOCK_ENTITY, (world1, pos, state1, be) -> DemoBlockEntity.tick(world1, pos, state1, be));
}
And in your ''Block Entity'':
public class DemoBlockEntity extends BlockEntity {
public DemoBlockEntity(BlockPos pos, BlockState state) {
super(ExampleMod.DEMO_BLOCK_ENTITY, pos, state);
}
public static void tick(World world, BlockPos pos, BlockState state, DemoBlockEntity be) {
[...]
}
}
===== Overview =====
You should now have your very own ''BlockEntity'', which you can expand in various ways to suit your needs. You registered a ''BlockEntityType'', and used it to connect your ''Block'' and ''BlockEntity'' classes together. Then, you implemented ''BlockEntityProvider'' in your ''Block'' class, and used the interface to provide an instance of your new ''BlockEntity''. You also learned how to save data to your ''BlockEntity'', how to retrieve for use later, and finally, you learned how to add ticking to it.