Communitygithub.com

vvs1off/fabric-client-rendering

Zeichnen von HUD-Elementen, GUI-Overlays, benutzerdefinierten Bildschirmen, Tooltips und Text im Minecraft-Client mit Fabric.

Was ist fabric-client-rendering?

Dieses Skill bietet eine umfassende Einführung in die clientseitige Bildschirmdarstellung mit Fabric für Minecraft-Mods. Es behandelt alle relevanten APIs und Versionen von 1.20 bis 26.2+, einschließlich der verschiedenen GUI-Zeichenklassen (GuiGraphics, DrawContext, GuiGraphicsExtractor) und HUD-Registrierungsmethoden (HudElementRegistry, HudLayerRegistrationCallback, HudRenderCallback). Der Skill zeigt, wie man HUD-Elemente relativ zu Vanilla-Elementen wie Hotbar oder Chat einfügt, wie man Zeichenprimitive wie Rechtecke, Farbverläufe, Sprites und Text rendert, wie man benutzerdefinierte Bildschirme und Widgets erstellt und wie man Item-Tooltips erweitert. Ein besonderer Schwerpunkt liegt auf Performance-Optimierung: Da HUD- und Bildschirmmethoden bis zu 500 Mal pro Sekunde ausgeführt werden, zeigt der Skill, wie man Allokationen pro Frame vermeidet, Game-State-Berechnungen aus dem Render-Pfad auslagert und Ressourcen wie Texte und Components cached. Die Dokumentation enthält eine Versionstabelle zum Zuordnen der unterschiedlichen API-Namen und warnt vor den häufigsten Fehlern wie falschen Methodensignaturen, ARGB-Farben nach 1.21.6 oder dem Übersehen des F1-Hide-HUD-Zustands. Plattformen: Fabric-basierte Minecraft-Mods ab Version 1.20.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/vvs1off/fabric-skills/tree/main/skills/fabric-client-rendering

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

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 / MappingsGUI-ZeichenklasseScreen-ZeichenmethodeText-MethodeHUD-API
26.1+ (Mojang, unverschleiert)GuiGraphicsExtractorextractRenderState(graphics, mouseX, mouseY, delta)graphics.text(font, ...)HudElementRegistry
1.21.6 – 1.21.11, Mojang-MappingsGuiGraphicsrender(graphics, mouseX, mouseY, delta)graphics.drawString(font, ...)HudElementRegistry
1.21.6 – 1.21.11, YarnDrawContextrender(context, mouseX, mouseY, delta)context.drawText(textRenderer, ...)HudElementRegistry
1.21.2 – 1.21.5wie oben für deine Mappingsrender(...)wie obenHudLayerRegistrationCallback (HudRenderCallback veraltet)
1.20 – 1.21.1wie oben für deine Mappingsrender(...)wie obenHudRenderCallback

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.hideGui in ä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 und Util.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.json ab und verwende Component.translatable. Hartcodiertes Englisch in Component.literal kann 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 parent und 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, kein String.format, keine String-Konkatenation im Zeichenpfad, keine pro Aufruf erstellten Streams/Lambdas, kein Component.literal mit einem Wert, der sich nicht geändert hat. Cache das Component und 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, oder GuiGraphics in einem 26.1+-Projekt, wo es GuiGraphicsExtractor ist.
  • Überschreiben von render(...) in einem 26.1+-Screen – die Methode heißt extractRenderState; der alte Name kompiliert nur, wenn du auch @Override vergisst, und wird dann nie ausgeführt. Verwende immer @Override.
  • RGB-Farben (0xFFFFFF) nach 1.21.6 → unsichtbarer Text und Füllungen.
  • Verwenden des veralteten HudRenderCallback ab 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 in init().
  • 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.

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

Verwandte Skills