Was macht fabric-client-rendering?
Der gesamte Code in diesem Skill ist nur für den Client bestimmt: Er gehört nach src/client/java und wird
von deinem ClientModInitializer registriert. Ein Verweis auf diesen Code im gemeinsamen Code führt zum Absturz von Servern.
Schritt 0 – Welche Zeichen-API hat diese Version?
Die GUI-Zeichenklasse und die HUD-API wurden mehrmals umbenannt. Überprüfe vor dem Schreiben von Code:
grep -E "minecraft_version|yarn_mappings" gradle.properties
grep -n "mappings" build.gradle
| Version / Mappings | GUI-Zeichenklasse | Screen-Zeichenmethode | Text-Methode | HUD-API |
|---|---|---|---|---|
| 26.1+ (Mojang, unverschleiert) | GuiGraphicsExtractor | extractRenderState(graphics, mouseX, mouseY, delta) | graphics.text(font, ...) | HudElementRegistry |
| 1.21.6 – 1.21.11, Mojang-Mappings | GuiGraphics | render(graphics, mouseX, mouseY, delta) | graphics.drawString(font, ...) | HudElementRegistry |
| 1.21.6 – 1.21.11, Yarn | DrawContext | render(context, mouseX, mouseY, delta) | context.drawText(textRenderer, ...) | HudElementRegistry |
| 1.21.2 – 1.21.5 | wie oben für deine Mappings | render(...) | wie oben | HudLayerRegistrationCallback (HudRenderCallback veraltet) |
| 1.20 – 1.21.1 | wie oben für deine Mappings | render(...) | wie oben | HudRenderCallback |
Dann überprüfe in der tatsächlichen Abhängigkeit, denn die Namen ändern sich ständig (26.2 hat
minecraft.setScreen z.B. nach minecraft.gui.setScreen verschoben):
./gradlew genSources
# Öffne HudElementRegistry / deine GUI-Zeichenklasse in der IDE oder lies das Quell-JAR von Fabric-API
Versionsspezifische Anleitungen mit funktionierendem Code: https://docs.fabricmc.net/develop/rendering/hud (verwende den Versionsauswahl oben auf der Seite).
Die folgenden Beispiele verwenden die 26.1+ Mojang-Namen. Übersetze sie anhand der Tabelle für alte Versionen; die Struktur ist identisch.
HUD-Elemente (HudElementRegistry)
package com.example.examplemod.client;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.rendering.v1.hud.HudElementRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.hud.VanillaHudElements;
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import com.example.examplemod.ExampleMod;
public class ExampleModClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
HudElementRegistry.attachElementAfter(
VanillaHudElements.HOTBAR, ExampleMod.id("coords"), ExampleModClient::extractCoords);
}
private static void extractCoords(GuiGraphicsExtractor graphics, DeltaTracker delta) {
Minecraft mc = Minecraft.getInstance();
// F1 "HUD verstecken": mc.gui.hud.isHidden() ab 26.3; mc.options.hideGui in älteren Versionen.
if (mc.player == null || mc.gui.hud.isHidden() || !ExampleConfig.get().showCoords) {
return;
}
graphics.text(mc.font, CoordsText.current(mc.player), 4, 4, 0xFFFFFFFF, true);
}
}
HudElementRegistry-Methoden: addFirst, addLast, attachElementBefore,
attachElementAfter (relativ zu einer VanillaHudElements-ID wie HOTBAR, CHAT,
CROSSHAIR, BOSS_BAR, SCOREBOARD), removeElement und replaceElement (zum Verstecken oder
Verpacken eines Vanilla-Elements). Die API übernimmt die Layering; du verwaltest keine Z-Offsets.
Regeln für HUD-Code:
- Vorzeitiges Zurückgeben, wenn
mc.player == null, wenn das HUD mit F1 versteckt wird (mc.gui.hud.isHidden()ab 26.3,mc.options.hideGuiin älteren Versionen – Fabric versteckt dein Element nicht für dich), und wenn deine Funktion deaktiviert ist. Ziehe auch in Betracht auszublenden, während der F3-Debug-Bildschirm geöffnet ist, wenn dein Element sich mit ihm überschneiden würde (siehe den Debug-Overlay-Zugriff in den Quellen deiner Version). - Positioniere relativ zu
graphics.guiWidth()/graphics.guiHeight()(skalierte Größe), niemals mit hartcodierten Pixelannahmen über das Fenster. - Verwende
DeltaTracker#getGameTimeDeltaPartialTick(false)für tick-interpolierte Animationen undUtil.getMillis()für Echtzeitanimationen.
Ältere HUD-Callbacks (vor 1.21.6)
// 1.20 – 1.21.1 (Mojang-Namen gezeigt; Yarn: DrawContext, RenderTickCounter)
HudRenderCallback.EVENT.register((graphics, deltaTracker) -> { /* zeichne */ });
HudLayerRegistrationCallback (1.21.2 – 1.21.5) registriert Layer relativ zu
IdentifiedLayer-IDs, ähnlich wie HudElementRegistry. Überprüfe die Dokumentation für diese
exakte Version, bevor du sie verwendest.
Zeichenprimitive
graphics.fill(x1, y1, x2, y2, 0x80000000); // ARGB! 0x80 = 50% Alpha
graphics.outline(x, y, width, height, 0xFFFF0000);
graphics.fillGradient(x1, y1, x2, y2, 0xFFFF0000, 0xFF0000FF);
graphics.horizontalLine(x1, x2, y, color);
graphics.verticalLine(x, y1, y2, color);
graphics.enableScissor(x1, y1, x2, y2); // Clipping; Aufrufe müssen ausgeglichen sein
// ... zeichne ...
graphics.disableScissor();
graphics.blit(RenderPipelines.GUI_TEXTURED, ExampleMod.id("textures/gui/icon.png"),
x, y, 0, 0, 16, 16, 16, 16); // Pipeline, Textur, x, y, u, v, w, h, texW, texH
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, spriteId, x, y, w, h); // GUI-Atlas-Sprite
graphics.centeredText(font, component, centerX, y, 0xFFFFFFFF);
graphics.setTooltipForNextFrame(font, component, mouseX, mouseY);
graphics.item(stack, x, y); // Item-Symbol (26.x Name; älter: renderItem / drawItem)
Farben sind ARGB seit 1.21.6. 0xFFFFFF ist vollständig transparentes Weiß und rendert nichts.
Verwende 0xFFFFFFFF oder umschließe RGB mit ARGB.opaque(rgb). Dies ist der häufigste "mein Text
wird nicht angezeigt"-Fehler beim Portieren von altem Code.
Text
Component title = Component.translatable("hud.examplemod.title"); // lokalisiert
Component value = Component.literal(String.valueOf(count)).withStyle(ChatFormatting.GREEN);
int width = mc.font.width(title);
graphics.text(mc.font, title, x, y, 0xFFFFFFFF, true); // letztes Argument: Schatten
- Lege benutzersichtbare Zeichenketten in
assets/<modid>/lang/en_us.jsonab und verwendeComponent.translatable. Hartcodiertes Englisch inComponent.literalkann nicht übersetzt werden. - Yarn-Entsprechungen:
Text.translatable,Text.literal,Formatting,textRenderer.
Tooltips
Füge Zeilen zum Tooltip jedes Items hinzu, ohne einen Mixin:
ItemTooltipCallback.EVENT.register((stack, tooltipContext, tooltipFlag, lines) -> {
if (stack.is(Items.COMPASS)) {
lines.add(Component.translatable("tooltip.examplemod.compass").withStyle(ChatFormatting.GRAY));
}
});
ItemTooltipCallback befindet sich in net.fabricmc.fabric.api.client.item.v1. Bei eigenen
Items überschreibe die Tooltip-Methode des Items oder verwende stattdessen eine Datenkomponente. Verwende
tooltipFlag.isAdvanced(), um Debug-Informationen hinter F3+H zu verstecken.
Benutzerdefinierte Bildschirme
public class ExampleScreen extends Screen {
private final Screen parent;
public ExampleScreen(Screen parent) {
super(Component.translatable("screen.examplemod.title"));
this.parent = parent;
}
@Override
protected void init() { // wird beim Öffnen UND bei jeder Größenänderung aufgerufen – baue Widgets hier
this.addRenderableWidget(Button.builder(Component.translatable("gui.done"), b -> this.onClose())
.bounds(this.width / 2 - 100, this.height - 28, 200, 20)
.build());
}
@Override
public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) {
super.extractRenderState(graphics, mouseX, mouseY, delta); // Hintergrund + Widgets
graphics.centeredText(this.font, this.title, this.width / 2, 15, 0xFFFFFFFF);
}
@Override
public void onClose() {
this.minecraft.gui.setScreen(this.parent); // 26.2+; älter: this.minecraft.setScreen(...)
}
}
- Erstelle Widgets in
init(), nicht im Konstruktor;init()wird bei Größenänderung erneut ausgeführt und die Widget-Liste wird jedes Mal geleert. - Behalte immer einen
parentund kehre zu ihm zurück (Mod Menu und Optionsbildschirme verlassen sich darauf). - Öffne einen Bildschirm von einem Tastendruck oder Tick-Handler auf dem Client-Thread. Beim Öffnen von einem
Netzwerk-Handler oder einem anderen Thread ist
client.execute(() -> ...)erforderlich. - Bevorzuge Vanilla-Widgets (
Button,EditBox,CycleButton,AbstractSliderButton), bevor du eigene schreibst.
Performance – dieser Code wird in jedem Frame ausgeführt
HUD- und Bildschirm-Zeichenmethoden werden 60–500+ Mal pro Sekunde ausgeführt. Behandle sie wie eine heiße Schleife:
- Keine Allokationen pro Frame, wo vermeidbar: keine
new-Objekte, keinString.format, keine String-Konkatenation im Zeichenpfad, keine pro Aufruf erstellten Streams/Lambdas, keinComponent.literalmit einem Wert, der sich nicht geändert hat. Cache dasComponentund baue es nur dann neu auf, wenn der zugrunde liegende Wert sich ändert (z.B. bei einem Tick-Ereignis oder wenn die Blockposition sich unterscheidet). - Kein Game-State in der Darstellung: keine World-Scans, Entitätssuchen, Raycasts, Datei-I/O,
Konfigurationslesen von der Festplatte oder Registry-Suchen. Berechne in
ClientTickEvents.END_CLIENT_TICK(20 Mal/s) und zeichne das gecachte Ergebnis. - Logge nicht aus dem Render-Pfad.
- Vermeide einmalige Texturen, die in jedem Frame hochgeladen werden; registriere Texturen einmal.
- Miss mit dem F3-Frame-Time-Diagramm oder einem Profiler (Spark) vorher und nachher.
// Gut: höchstens einmal pro Tick neu berechnen, nur wenn geändert
public final class CoordsText {
private static Component cached = Component.empty();
private static BlockPos last = null;
public static void onTick(Minecraft mc) { // von END_CLIENT_TICK
if (mc.player == null) return;
BlockPos pos = mc.player.blockPosition();
if (!pos.equals(last)) {
last = pos;
cached = Component.translatable("hud.examplemod.coords", pos.getX(), pos.getY(), pos.getZ());
}
}
public static Component current(LocalPlayer player) { return cached; }
}
Welt-Rendering
Das Zeichnen in der 3D-Welt (Umrisse, Kästchen, Linien) verwendet LevelRenderEvents (26.1+; früher
WorldRenderEvents) und Render-Pipelines und ändert sich fast mit jeder Version, da Mojang den Renderer
umschreibt. Lese https://docs.fabricmc.net/develop/rendering/world für deine genaue Version und kopiere
deren Struktur; passe kein Weltrendering-Snippet aus einer anderen Version an.
Häufige Fehler
- Verwenden von
DrawContext/MatrixStack/TextRenderer(Yarn) in einem Mojang-benannten Projekt, oderGuiGraphicsin einem 26.1+-Projekt, wo esGuiGraphicsExtractorist. - Überschreiben von
render(...)in einem 26.1+-Screen– die Methode heißtextractRenderState; der alte Name kompiliert nur, wenn du auch@Overridevergisst, und wird dann nie ausgeführt. Verwende immer@Override. - RGB-Farben (
0xFFFFFF) nach 1.21.6 → unsichtbarer Text und Füllungen. - Verwenden des veralteten
HudRenderCallbackab 1.21.6+, oder eines Mixin in die Vanilla-Gui-Klasse für ein einfaches Overlay. - Allokieren von Strings/Components in jedem Frame oder Ausführen von World-Queries innerhalb des HUD-Callbacks.
- Nichtprüfen von
mc.player == null(Absturz auf dem Titelbildschirm) oder Ignorieren des F1-Hide-HUD-Zustands. - Erstellen von Widgets im
Screen-Konstruktor statt ininit(). - Vergessen von
super.extractRenderState(...)/super.render(...)und Verlust des Hintergrunds und aller Widgets. - Hartcodierte englische Zeichenketten anstelle von Übersetzungsschlüsseln.
- Platzieren von irgendetwas davon in
src/main.