🇬🇧 English
🇬🇧 English
Appearance
🇬🇧 English
🇬🇧 English
Appearance
This page is written for version:
1.21.4
Sometimes, using Minecraft's model format is not enough. If you need to add dynamic rendering to your block's visuals, you will need to use a BlockEntityRenderer.
For example, let's make the Counter Block from the Block Entities article show the number of clicks on its top side.
First, we need to create a BlockEntityRenderer for our CounterBlockEntity.
When creating a BlockEntityRenderer for the CounterBlockEntity, it's important to place the class in the appropriate source set, such as src/client/, if your project uses split source sets for client and server. Accessing rendering-related classes directly in the src/main/ source set is not safe because those classes might be loaded on a server.
public class CounterBlockEntityRenderer implements BlockEntityRenderer<CounterBlockEntity> {
public CounterBlockEntityRenderer(BlockEntityRendererFactory.Context context) {
}
@Override
public void render(CounterBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) {
}
}The new class has a constructor with BlockEntityRendererFactory.Context as a parameter. The Context has a few useful rendering utilities, like the ItemRenderer or TextRenderer. Also, by including a constructor like this, it becomes possible to use the constructor as the BlockEntityRendererFactory functional interface itself:
public class FabricDocsBlockEntityRenderer implements ClientModInitializer {
@Override
public void onInitializeClient() {
BlockEntityRendererFactories.register(ModBlockEntities.COUNTER_BLOCK_ENTITY, CounterBlockEntityRenderer::new);
}
}You should register block entity renderers in your ClientModInitializer class.
BlockEntityRendererFactories is a registry that maps each BlockEntityType with custom rendering code to its respective BlockEntityRenderer.
Now that we have a renderer, we can draw. The render method is called every frame, and it's where the rendering magic happens.
First, we need to offset and rotate the text so that it's on the block's top side.
INFO
As the name suggests, the MatrixStack is a stack, meaning that you can push and pop transformations. A good rule-of-thumb is to push a new one at the beginning of the render method and pop it at the end, so that the rendering of one block doesn't affect others.
More information about the MatrixStack can be found in the Basic Rendering Concepts article.
To make the translations and rotations needed easier to understand, let's visualize them. In this picture, the green block is where the text would be drawn, by default in the furthest bottom-left point of the block:

So first we need to move the text halfway across the block on the X and Z axes, and then move it up to the top of the block on the Y axis:

This is done with a single translate call:
matrices.translate(0.5, 1, 0.5);That's the translation done, rotation and scale remain.
By default, the text is drawn on the XY plane, so we need to rotate it 90 degrees around the X axis to make it face upwards on the XZ plane:

The MatrixStack does not have a rotate function, instead we need to use multiply and RotationAxis.POSITIVE_X:
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(90));Now the text is in the correct position, but it's too large. The BlockEntityRenderer maps the whole block to a [-0.5, 0.5] cube, while the TextRenderer uses Y coordinates of [0, 9]. As such, we need to scale it down by a factor of 18:
matrices.scale(1/18f, 1/18f, 1/18f);Now, the whole transformation looks like this:
matrices.push();
matrices.translate(0.5, 1, 0.5);
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(90));
matrices.scale(1/18f, 1/18f, 1/18f);As mentioned earlier, the Context passed into the constructor of our renderer has a TextRenderer that we can use to draw text. For this example we'll save it in a field.
The TextRenderer has methods to measure text (getWidth), which is useful for centering, and to draw it (draw).
String text = entity.getClicks() + "";
float width = textRenderer.getWidth(text);
// draw the text. params:
// text, x, y, color, shadow, matrix, vertexConsumers, layerType, backgroundColor, light
textRenderer.draw(
text,
-width/2, -4f,
0xffffff,
false,
matrices.peek().getPositionMatrix(),
vertexConsumers,
TextRenderer.TextLayerType.SEE_THROUGH,
0,
light
);The draw method takes a lot of parameters, but the most important ones are:
Text (or String) to draw;x and y coordinates;color value;Matrix4f describing how it should be transformed (to get one from a MatrixStack, we can use .peek().getPositionMatrix() to get the Matrix4f for the topmost entry).And after all this work, here's the result:
