BlockEntityRenderer
A BlockEntityRenderer
, often abbreviated as BER, is used to render blocks in a way that cannot be represented with a static baked model (JSON, OBJ, others). For example, this could be used to dynamically render container contents of a chest-like block. A block entity renderer requires the block to have a BlockEntity
, even if the block does not store any data otherwise.
To create a BER, create a class that inherits from BlockEntityRenderer
. It takes a generic argument specifying the block's BlockEntity
class, which is used as a parameter type in the BER's render
method.
// Assumes the existence of MyBlockEntity as a subclass of BlockEntity.
public class MyBlockEntityRenderer implements BlockEntityRenderer<MyBlockEntity> {
// Add the constructor parameter for the lambda below. You may also use it to get some context
// to be stored in local fields, such as the entity renderer dispatcher, if needed.
public MyBlockEntityRenderer(BlockEntityRendererProvider.Context context) {
}
// This method is called every frame in order to render the block entity. Parameters are:
// - blockEntity: The block entity instance being rendered. Uses the generic type passed to the super interface.
// - partialTick: The amount of time, in fractions of a tick (0.0 to 1.0), that has passed since the last tick.
// - poseStack: The pose stack to render to.
// - bufferSource: The buffer source to get vertex buffers from.
// - packedLight: The light value of the block entity.
// - packedOverlay: The current overlay value of the block entity, usually OverlayTexture.NO_OVERLAY.
@Override
public void render(MyBlockEntity blockEntity, float partialTick, PoseStack stack, MultiBufferSource bufferSource, int packedLight, int packedOverlay) {
// Do the rendering here.
}
}
Only one BER may exist for a given BlockEntityType<?>
. Therefore, values that are specific to a single block entity instance should be stored in that block entity instance, rather than the BER itself.
When you have created your BER, you must also register it to EntityRenderersEvent.RegisterRenderers
, an event fired on the mod event bus:
@SubscribeEvent
public static void registerEntityRenderers(EntityRenderersEvent.RegisterRenderers event) {
event.registerBlockEntityRenderer(
// The block entity type to register the renderer for.
MyBlockEntities.MY_BLOCK_ENTITY.get(),
// A function of BlockEntityRendererProvider.Context to BlockEntityRenderer.
MyBlockEntityRenderer::new
);
}
In the event that you do not need the BER provider context in your BER, you can also remove the constructor:
public class MyBlockEntityRenderer implements BlockEntityRenderer<MyBlockEntity> {
@Override
public void render( /* ... */ ) { /* ... */ }
}
// In your event handler class
@SubscribeEvent
public static void registerEntityRenderers(EntityRenderersEvent.RegisterRenderers event) {
event.registerBlockEntityRenderer(MyBlockEntities.MY_BLOCK_ENTITY.get(),
// Pass the context to an empty (default) constructor call
context -> new MyBlockEntityRenderer()
);
}
BlockEntityWithoutLevelRenderer
BlockEntityWithoutLevelRenderer
, colloquially known as BEWLR, is an adaptation of the regular BlockEntityRenderer
for special item rendering (hence "without level", as items do not have level context). Its overall purpose is the same: do special rendering for cases where static models aren't enough.
To add a BEWLR, create a class that extends BlockEntityWithoutLevelRenderer
and overrides #renderByItem
. It also requires some additional constructor setup:
public class MyBlockEntityWithoutLevelRenderer extends BlockEntityWithoutLevelRenderer {
// We need some boilerplate in the constructor, telling the superclass where to find the central block entity and entity renderers.
public MyBlockEntityWithoutLevelRenderer() {
super(Minecraft.getInstance().getBlockEntityRenderDispatcher(), Minecraft.getInstance().getEntityModels());
}
@Override
public void renderByItem(ItemStack stack, ItemDisplayContext transform, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay) {
// Do the rendering here.
}
}
Keep in mind that, like with BERs, there is only one instance of your BEWLR. Stack-specific properties should therefore be stored in the stack, not the BEWLR.
Unlike BERs, we do not register BEWLRs directly. Instead, we register an instance of IClientItemExtensions
to the RegisterClientExtensionsEvent
. IClientItemExtensions
is an interface that allows us to specify a number of rendering-related behaviors on items, such as (but not limited to) a BEWLR. As such, our implementation of that interface could look like so:
public class MyClientItemExtensions implements IClientItemExtensions {
// Cache our BEWLR in a field.
private final MyBlockEntityWithoutLevelRenderer myBEWLR = new MyBlockEntityWithoutLevelRenderer();
// Return our BEWLR here.
@Override
public BlockEntityWithoutLevelRenderer getCustomRenderer() {
return myBEWLR;
}
}
And then, we can register our IClientItemExtensions
to the event:
@SubscribeEvent
public static void registerClientExtensions(RegisterClientExtensionsEvent event) {
event.registerItem(
// The only instance of our IClientItemExtensions, and as such, the only instance of our BEWLR.
new MyClientItemExtensions(),
// A vararg list of items that use this BEWLR.
MyItems.ITEM_1, MyItems.ITEM_2
);
}
IClientItemExtensions
are generally expected to be treated as singletons. Do not construct them outside RegisterClientExtensionsEvent
!
Finally, the item has to know that it should use the BEWLR for its rendering. This is done by having the final [BakedModel
][bakedmodel] return true for #isCustomRenderer
. The easiest way to do this is to have the item model JSON with a parent
of minecraft:builtin/entity
:
// In some item model file assets/<mod_id>/models/item/<registry_name>.json
{
"parent": "minecraft: builtin/entity",
// ...
}