🇬🇧 English
🇬🇧 English
Appearance
🇬🇧 English
🇬🇧 English
Appearance
This page is written for version:
1.21
This page is written for version:
1.21
Sometimes, using Minecraft's model format is not enough. If you need to add dynamic rendering to it, 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);
}
}
Add the entrypoint to the fabric.mod.json
file, so that the renderer is registered.
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: