Manchmal reicht das Nutzen von Minecraft's Modellformat nicht aus. Wenn du dynamisches Rendering zu dessen visuellen Elemten hinzufügen willst, wirst du einen BlockEntityRenderer nutzen müssen.
Lasst uns als Beispiel den Zählerblock aus dem Artikel zu Block Entitäten die Zahl an Klicks auf der Oberseite anzeigen lassen.
Erstellen eines BlockEntityRenderer
Das Block-Entität-Rendering verwendet ein Submit-/Render-System, bei dem du zunächst die zum Rendern eines Objekts auf dem Bildschirm erforderlichen Daten übermittelst. Das Spiel rendert das Objekt dann anhand des übermittelten Status.
Beim Erstellen eines BlockEntityRenderer für die CounterBlockEntity ist es wichtig, wenn das Projekt geteilte Quellen für den Client und den Server nutzt, die Klasse in das passende Quellenverzeichnis, wie src/client/, zu platzieren. Der Zugriff auf Rendering-bezogene Klassen direkt im src/main/ Quellenverzeichnis ist nicht sicher, da diese Klassen möglicherweise am Server nicht geladen sind.
Zunächst müssen wir einen BlockEntityRenderState für unsere CounterBlockEntity erstellen, um die Daten zu speichern, die für das Rendern verwendet werden. In diesem Fall müssen die clicks während des Renderns verfügbar sein.
java
public class CounterBlockEntityRenderState extends BlockEntityRenderState {
private int clicks = 0;
public int getClicks() {
return clicks;
}
public void setClicks(int clicks) {
this.clicks = clicks;
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
Dann erstellen wir einen BlockEntityRenderer für unsere CounterBlockEntity.
java
public class CounterBlockEntityRenderer implements BlockEntityRenderer<CounterBlockEntity, CounterBlockEntityRenderState> {
public CounterBlockEntityRenderer(BlockEntityRendererProvider.Context context) {
}
@Override
public CounterBlockEntityRenderState createRenderState() {
return new CounterBlockEntityRenderState();
}
@Override
public void extractRenderState(CounterBlockEntity blockEntity, CounterBlockEntityRenderState state, float tickProgress, Vec3 cameraPos, @Nullable ModelFeatureRenderer.CrumblingOverlay crumblingOverlay) {
}
@Override
public void submit(CounterBlockEntityRenderState state, PoseStack matrices, SubmitNodeCollector queue, CameraRenderState cameraState) {
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Die neue Klasse hat einen Konstruktor mit einem BlockEntityRendererProvider.Context als Parameter. Der Context hat einige nützliche Rendering-Hilfsmittel, wie den ItemRenderer oder TextRenderer. Durch die Aufnahme eines derartigen Konstruktors, wird es außerdem möglich den Konstuktor als funktionales Interface der BlockEntityRendererProvider selbst zu verwenden:
java
public class ExampleModBlockEntityRenderer implements ClientModInitializer {
@Override
public void onInitializeClient() {
BlockEntityRenderers.register(ModBlockEntities.COUNTER_BLOCK_ENTITY, CounterBlockEntityRenderer::new);
}
}1
2
3
4
5
6
2
3
4
5
6
Wir werden einige Methoden überschreiben, um den Renderzustand zusammen mit der Methode render einzurichten, in der die Rendering-Logik eingerichtet wird.
createRenderState kann verwendet werden, um den Renderzustand zu initialisieren.
java
@Override
public CounterBlockEntityRenderState createRenderState() {
return new CounterBlockEntityRenderState();
}1
2
3
4
2
3
4
extractRenderState kan verwendet werden, um den Renderzustand mit Daten von einer Entität zu aktuaisieren.
java
@Override
public void extractRenderState(CounterBlockEntity blockEntity, CounterBlockEntityRenderState state, float tickProgress, Vec3 cameraPos, @Nullable ModelFeatureRenderer.CrumblingOverlay crumblingOverlay) {
// :::1
BlockEntityRenderer.super.extractRenderState(blockEntity, state, tickProgress, cameraPos, crumblingOverlay);
state.setClicks(blockEntity.getClicks());
// :::1
}1
2
3
4
5
6
7
2
3
4
5
6
7
Du solltest Renderer für Blockentitäten in deiner Klasse ClientModInitializer registrieren.
BlockEntityRenderers ist eine Registrierung, die jeden BlockEntityType mit benutzerdefinierten Rendering-Code dem entsprechenden BlockEntityRenderer zuordnet.
Auf Blöcke zeichnen
Jetzt, da wir den Renderer haben, können wir zeichnen. Die Methode render wird bei jedem Frame aufgerufen und ist der Ort, an dem die Magie des Renderns passiert.
Umher bewegen
Zunächst müssen wir den Text versetzen und drehen, damit er sich auf der oberen Seite des Blocks befindet.
INFO
Wie der Name bereits vermuten lässt ist der PoseStack ein Stapel, was bedeutet, dass du Transformationen darauf hinzufügen (push) und davon entfernen (pop) kannst. Eine gute Faustregel ist es, einen neuen Block an den Anfang der render-Methode hinzuzufügen und ihn am Ende wieder zu entfernen, so dass das Rendern eines Blocks die anderen nicht beeinflusst.
Mehr Informationen zu dem PoseStack kann in dem Artikel zu den grundlegenden Konzepten des Rendering gefunden werden.
Zum besseren Verständnis der erforderlichen Verschiebungen und Drehungen sollten wir sie visualisieren. In diesem Bild ist der grüne Block die Position, an der der Text gezeichnet werden würde, standardmäßig am äußersten linken unteren Punkt des Blocks:

Zunächst müssen wir den Text auf der X- und Z-Achse in die Mitte und ihn dann an der Y-Achse an den oberen Rand des Blocks verschieben:

Died wird durch einen einzelnen translate Aufruf gemacht:
java
matrices.translate(0.5, 1, 0.5);1
Somit ist die Verschiebung erledigt, Drehung und Skalierung bleiben.
Standardmäßig wird der Text auf der XY-Ebene gezeichnet, also müssen wir ihn um 90 Grad um die X-Achse drehen, damit er auf der XZ-Ebene nach oben zeigt:

Der PoseStack hat keine rotate Methode, stattdessen müssen wir multiply und Axis.XP verwenden:
java
matrices.multiply(Axis.XP.rotationDegrees(90));1
Jetzt ist der Text an der korrekten Position, aber ist zu groß. Der BlockEntityRenderer ordnet den ganzen Block zu einem [-0.5, 0.5] Würfel zu, während der TextRenderer X-Koordinaten von [0, 9] verwendet. Somit müssen wir es um den Faktor 18 herunter skalieren:
java
matrices.scale(1/18f, 1/18f, 1/18f);1
Jetzt sieht die ganze Transformation wie folgt aus:
java
matrices.pushPose();
matrices.translate(0.5, 1, 0.5);
matrices.mulPose(Axis.XP.rotationDegrees(90));
matrices.scale(1/18f, 1/18f, 1/18f);1
2
3
4
2
3
4
Zeichnen von Text
Wie bereits früher erwähnt, hat der an den Konstruktor unseres Renderers übergebene Context einen TextRenderer, den wir für das Messen von Text (width) verwenden können, was für die Zentrierung nützlich ist.
Um den Text zu zeichnen, übermitteln wir die erforderlichen Daten an die Render-Warteschlange. Da wir Text zeichnen, können wir die Methode submitText verwenden, die über die Instanz OrderedRenderCommandQueue bereitgestellt wird, die an die Methode render übergeben wird.
java
String text = state.getClicks() + "";
float width = textRenderer.width(text);
// draw the text. params:
// text, x, y, color, ordered text, shadow, text layer type, light, color, background color, outline color
queue.submitText(
matrices,
-width / 2, -4f,
Component.literal(text).getVisualOrderText(),
false,
Font.DisplayMode.SEE_THROUGH,
state.lightCoords,
0xffffffff,
0,
0
);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Die Methode submitText nimmt einige Paramter, aber die Wichtigsten sind:
- die zu zeichnende
FormattedCharSequence; - seine
xundyKoordinaten; - der RGB
colorWert; - die
Matrix4f, die beschreibt, wie er transformiert werden soll (um eine aus einemPoseStackzu erhalten, können wir.last().pose()verwenden, um dieMatrix4ffür den obersten Eintrag zu erhalten).
Und nach dieser ganzen Arbeit, ist hier das Ergebnis:


