User Tools

Site Tools


tutorial:blockentity

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
tutorial:blockentity [2022/05/26 09:21] – Add a prompt to actively trigger BlockEntity synchronization mohengtutorial:blockentity [2024/10/27 14:21] (current) – [Registering block and block entities] solidblock
Line 1: Line 1:
 ====== Adding a BlockEntity ====== ====== Adding a BlockEntity ======
  
-===== Introduction =====+A **block entity** is primarily used to store data within blocks. Before creating one, you will need a [[blocks|Block]]. This tutorial will cover the creation of your BlockEntity class, and its registration.
  
-A BlockEntity is primarily used to store data within blocks. Before creating one, you will need [[tutorial:blocks|Block]]. This tutorial will cover the creation of your BlockEntity class, and it's registration.+===== Creating block entity type =====
  
-===== Creating a BlockEntity =====+The simplest block entity simply extends ''BlockEntity'', and uses the default constructor. This is perfectly valid, but will not grant any special functionality to your block.
  
-The simplest Block Entity simply extends ''BlockEntity'', and uses the default constructor. This is perfectly valid, but will not grant any special functionality to your block. +<code java DemoBlockEntity.java>
- +
-<code java>+
 public class DemoBlockEntity extends BlockEntity { public class DemoBlockEntity extends BlockEntity {
     public DemoBlockEntity(BlockPos pos, BlockState state) {     public DemoBlockEntity(BlockPos pos, BlockState state) {
-        super(ExampleMod.DEMO_BLOCK_ENTITY, pos, state);+        super(TutorialBlockEntityTypes.DEMO_BLOCK, pos, state);
     }     }
 } }
 </code> </code>
  
-Below will show you how to create the ''ExampleMod.DEMO_BLOCK_ENTITY'' field.+Please ensure that the constructor only takes the two parameters, otherwise the method reference ''DemoBlockEntity::new'' that we write later will be invalidThe ''TutorialBlockEntityTypes.DEMO_BLOCK'' field will be created later.
  
-You can simply add variables to this barebone class or implement interfaces such as ''Tickable'' and ''Inventory'' to add more functionality''Tickable'' provides a single ''tick()'' method, which is called once per tick for every loaded instance of your Block in the world., while ''Inventory'' allows your BlockEntity to interact with automation such as hoppers - there will likely be a separate tutorial dedicated entirely to this interface later.+Block entities support a variety of methods to enable functionality such as serialization to and deserialization from NBT, providing inventories, and more. This tutorial covers the most common implementations of block entity functionality.
  
-===== Registering your BlockEntity =====+===== Registering block and block entities =====
  
-Once you have created the ''BlockEntity'' class, you will need to register it for it to function. The first step of this process is to create a ''BlockEntityType'', which links your ''Block'' and ''BlockEntity'' together. Assuming your ''Block'' has been created and saved to a local variable ''DEMO_BLOCK'', you would create the matching ''BlockEntityType'' with the line below. In this tutorial, the ID of the block entity is ''tutorial:demo_block_entity''.+Once you have created the ''BlockEntity'' class, you will need to register it for it to function. The first step of this process is to create a ''BlockEntityType'' object in our ''TutorialBlockEntityTypes'' class, which links your ''Block'' and ''BlockEntity'' together. Create a ''Block'' object as the static final field ''DEMO_BLOCK'' in the ''TutorialBlocks'' class we created earlier. In this tutorial, the ID of the block entity is ''tutorial:demo_block''.
  
-The ''BlockEntityType'' should be registered in your ''onInitialize'' method, this is to ensure it gets registered at the correct time.+The ''BlockEntityType'' can be registered in the initialization of class or in your ''onInitialize'' method. This is to ensure it gets registered at the correct time. In this example, we register them in separate classes (see [[blocks]]).
  
-<code java> +<code java TutorialBlocks.java> 
-public static BlockEntityType<DemoBlockEntity> DEMO_BLOCK_ENTITY; +public final class TutorialBlocks { 
- +    [...] 
-@Override +     
-public void onInitialize() { +    public static final DemoBlock DEMO_BLOCK = register("demo_block", new DemoBlock(AbstractBlock.Settings.create())); 
-    DEMO_BLOCK_ENTITY Registry.register(Registry.BLOCK_ENTITY_TYPE, "tutorial:demo_block_entity", FabricBlockEntityTypeBuilder.create(DemoBlockEntity::new, DEMO_BLOCK).build(null));+     
 +    [...]
 } }
 </code> </code>
  
-==== Connecting a Block Entity to a Block ====+<code java TutorialBlockEntityTypes.java> 
 +public class TutorialBlockEntityTypes { 
 +  public static <T extends BlockEntityType<?>> T register(String path, T blockEntityType) { 
 +    return Registry.register(Registries.BLOCK_ENTITY_TYPE, Identifier.of("tutorial", path), blockEntityType); 
 +  }
  
-Once your ''BlockEntityType'' has been created and registeredyou'll need a block that is associated with itYou can do this by implementing ''BlockEntityProvider'' and overriding ''createBlockEntity''Each time your block is placedyour Block Entity will spawn alongside it.+  public static final BlockEntityType<DemoBlockEntity> DEMO_BLOCK = register( 
 +      "demo_block", 
 +      // For versions 1.21.2 and above, 
 +      // replace `BlockEntityType.Builder` with `FabricBlockEntityTypeBuilder`. 
 +      BlockEntityType.Builder.create(DemoBlockEntity::newTutorialBlocks.DEMO_BLOCK).build() 
 +  ); 
 +   
 +  public static void initialize() { 
 +  } 
 +}
  
-<code java> +</code>
-public class DemoBlock extends Block implements BlockEntityProvider {+
  
 +Remember to refer to the ''initialize'' method in the ''ModInitializer'':
 +<code java ExampleMod.java>
 +public class ExampleMod implements ModInitializer {
     [...]     [...]
 +    
     @Override     @Override
-    public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { +    public void onInitialize() { 
-        return new DemoBlockEntity(pos, state);+        [...] 
 +         
 +        TutorialBlockEntityTypes.initialize();
     }     }
 } }
 </code> </code>
  
-===== Serializing Data =====+For old versions, if you cannot access ''BlockEntityType.Builder.create'', try ''FabricBlockEntityTypeBuilder.create''.
  
-If you want to store any data in your ''BlockEntity''you will need to save and load itor it will only be held while the ''BlockEntity'' is loaded, and the data will reset whenever you come back to itLuckily, saving and loading is quite simple - you only need to override ''writeNbt()'' and ''readNbt()''+The block entity type defines that only the ''TutorialBlocks.DEMO_BLOCK'' can have this block entity type. If you want the block entity type to support more blocksjust add them in the parameters of ''BlockEntityType.Builder.create''If the method reference ''DemoBlockEntity::new'' does not parse, check if the constructor of ''DemoBlockEntity'' has the correct parameters.
  
-''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 thisany further data you try and save will be lost as it is not associated with position and ''BlockEntityType''. Knowing thisthe example below demonstrates saving an integer from your ''BlockEntity'' to the tagIn 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 tagand you will need to remember the key in order to retrieve the data later.+> **Note:** Like other blocks, the block also needs a block model and an item model, and may also need loot tablesee [[blocks]] for how to create themAs for loot tables[[blockentity_sync_itemstack|subsequent tutorials]] will cover how to improve the loot tables to include block entity data.
  
-<code java> +===== Connecting the block entity and the block =====
-public class DemoBlockEntity extends BlockEntity {+
  
-    // Store the current value of the number +Once your ''BlockEntityType'' has been created and registered, you'll need a block that is associated with it. You can do this by extending ''BlockWithEntity'' (or implementing ''BlockEntityProvider'') and overriding ''createBlockEntity''. Each time your block is placed, your block entity will be created. 
-    private int number = 7; + 
-    +<code java DemoBlock.java> 
-    public DemoBlockEntity(BlockPos pos, BlockState state) { +public class DemoBlock extends BlockWithEntity { 
-        super(ExampleMod.DEMO_BLOCK_ENTITY, pos, state);+    public DemoBlock(Settings settings) { 
 +        super(settings);
     }     }
-    +
-    // Serialize the BlockEntity+
     @Override     @Override
-    public void writeNbt(NbtCompound tag) { +    protected MapCodec<? extends DemoBlock> getCodec() { 
-        // Save the current value of the number to the tag +        return createCodec(DemoBlock::new); 
-        tag.putInt("number", number);+    }
  
-        super.writeNbt(tag);+    @Override 
 +    public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { 
 +        return new DemoBlockEntity(pos, state);
     }     }
-} 
-</code> 
  
-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. +    @Override 
- +    protected BlockRenderType getRenderType(BlockState state) { 
-<code java> +        return BlockRenderType.MODEL
-// Deserialize the BlockEntity +    }
-@Override +
-public void readNbt(NbtCompound tag) { +
-    super.readNbt(tag)+
-     +
-    number = tag.getInt("number");+
 } }
 </code> </code>
  
-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. +Overriding ''getRenderType'' is because ''BlockWithEntity'' makes it invisible by default.
- +
-===== 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'': +
-<code java> +
-  @Nullable +
-  @Override +
-  public Packet<ClientPlayPacketListener> toUpdatePacket() { +
-    return BlockEntityUpdateS2CPacket.create(this); +
-  } +
- +
-  @Override +
-  public NbtCompound toInitialChunkDataNbt() { +
-    return createNbt(); +
-  } +
-</code> +
-**Warning**: Need to call ''world.updateListeners(pos, state, state, Block.NOTIFY_LISTENERS);'' on the Client to trigger the update. +
- +
-===== 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+
  
 +===== Block entity ticking =====
 +Ticking means the block should run something on every tick (which is 1/20 second). For your block to tick, you would normally use ''getTicker'' in ''Block'', linking back to a static ''tick'' method in the ''BlockEntity''. See below for the common implementation of ticking. 
  
 In your ''Block'' class: In your ''Block'' class:
-<code java>+<code java DemoBlock.java>
 public class DemoBlock extends BlockWithEntity { 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     @Override
     public <T extends BlockEntity> BlockEntityTicker<T> getTicker(World world, BlockState state, BlockEntityType<T> type) {     public <T extends BlockEntity> BlockEntityTicker<T> getTicker(World world, BlockState state, BlockEntityType<T> type) {
-        return checkType(type, ExampleMod.DEMO_BLOCK_ENTITY, (world1, pos, state1, be) -> DemoBlockEntity.tick(world1, pos, state1, be));+        // Make sure to check world.isClient if you only want to tick only on serverside. 
 +        return validateTicker(type, ExampleMod.DEMO_BLOCK_ENTITY, DemoBlockEntity::tick);
     }     }
 +}
 </code> </code>
-And in your ''Block Entity'': +And in your ''BlockEntity'' class
-<code java>+<code java DemoBlockEntity.java>
 public class DemoBlockEntity extends BlockEntity { public class DemoBlockEntity extends BlockEntity {
-    public DemoBlockEntity(BlockPos pos, BlockState state) { +    [...] 
-        super(ExampleMod.DEMO_BLOCK_ENTITY, pos, state); +     
-    } +    @Override 
-    public static void tick(World world, BlockPos pos, BlockState state, DemoBlockEntity be) {+    public static void tick(World world, BlockPos pos, BlockState state, DemoBlockEntity blockEntity) {
         [...]         [...]
     }     }
Line 143: Line 130:
 </code> </code>
  
-===== Overview =====+===== Next steps ===== 
 + 
 +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 extended ''BlockWithEntity'', and used its interface ''BlockEntityProvider'' to provide an instance of your new ''BlockEntity''.
  
-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 togetherThen, 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.+You also learned how to add ticking for it. Next step, you can try some other complex operations for the block entities, such as: 
 +  * [[blockentity_modify_data|Modifying block entity data]] 
 +  * [[inventory|Storing items in the block entity as an inventory]] 
 +  * [[blockentityrenderers|Using block entity renderers to dynamically render]] 
 +  * [[screenhandler|Creating a container block]]
tutorial/blockentity.1653556910.txt.gz · Last modified: 2022/05/26 09:21 by moheng