Communitygithub.com

vvs1off/fabric-client-rendering

Dessiner des éléments sur le client Minecraft avec Fabric - éléments HUD (HudElementRegistry et anciens callbacks HUD), dessin GUI (GuiGraphicsExtractor / GuiGraphics / DrawContext selon la version), textes et Components, infobulles d'objets, écrans et widgets personnalisés, et performances de rendu. À utiliser pour ajouter ou corriger un overlay HUD, un écran, une infobulle ou tout code de dessin client par frame.

Qu'est-ce que fabric-client-rendering ?

Cette compétence est dédiée au rendu client côté Minecraft avec Fabric, couvrant tous les aspects de l'interface utilisateur : éléments HUD, écrans personnalisés, infobulles d'objets, textes et primitives de dessin. Elle fournit un guide détaillé pour naviguer entre les différentes versions de Minecraft (de 1.20 à la 26.1+), où les classes et APIs ont été renommées (GuiGraphics, DrawContext, GuiGraphicsExtractor, HudElementRegistry, etc.). L'accent est mis sur les performances en limitant les allocations par frame, en mettant en cache les résultats coûteux et en déléguant les calculs lourds aux ticks. Des exemples concrets montrent comment attacher un élément HUD, dessiner des primitives, ajouter des infobulles, créer un écran personnalisé et respecter les bonnes pratiques comme la vérification de la nullité du joueur ou la gestion du mode F1. Un tableau de compatibilité et des commandes de vérification aident à choisir la bonne API selon l'environnement de développement.

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

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Rendu client Fabric

Tout le code de cette compétence est uniquement côté client : il appartient à src/client/java et est enregistré à partir de votre ClientModInitializer. Y faire référence depuis du code commun plantera les serveurs dédiés.

Étape 0 – Quelle API de dessin cette version utilise-t-elle ?

La classe de dessin GUI et l'API HUD ont été renommées plusieurs fois. Vérifiez avant d'écrire du code :

grep -E "minecraft_version|yarn_mappings" gradle.properties
grep -n "mappings" build.gradle
Version / mappingsClasse de dessin GUIMéthode draw de ScreenMéthode TextAPI HUD
26.1+ (Mojang, non obscurci)GuiGraphicsExtractorextractRenderState(graphics, mouseX, mouseY, delta)graphics.text(font, ...)HudElementRegistry
1.21.6 - 1.21.11, mappings MojangGuiGraphicsrender(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.5comme ci-dessus pour vos mappingsrender(...)comme ci-dessusHudLayerRegistrationCallback (HudRenderCallback déprécié)
1.20 - 1.21.1comme ci-dessus pour vos mappingsrender(...)comme ci-dessusHudRenderCallback

Confirmez ensuite dans la dépendance réelle, car les noms continuent de bouger (26.2 a déplacé minecraft.setScreen vers minecraft.gui.setScreen, par exemple) :

./gradlew genSources
# ouvrez HudElementRegistry / votre classe de dessin GUI dans l'IDE, ou lisez le jar des sources de Fabric API

Guides adaptés à la version avec code fonctionnel : https://docs.fabricmc.net/develop/rendering/hud (utilisez le sélecteur de version en haut de la page).

Les exemples ci-dessous utilisent les noms Mojang 26.1+. Traduisez via le tableau pour les versions plus anciennes ; la structure est identique.

Éléments HUD (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 "hide HUD" : mc.gui.hud.isHidden() sur 26.3 ; mc.options.hideGui sur les versions plus anciennes.
        if (mc.player == null || mc.gui.hud.isHidden() || !ExampleConfig.get().showCoords) {
            return;
        }
        graphics.text(mc.font, CoordsText.current(mc.player), 4, 4, 0xFFFFFFFF, true);
    }
}

Méthodes de HudElementRegistry : addFirst, addLast, attachElementBefore, attachElementAfter (par rapport à un identifiant VanillaHudElements tel que HOTBAR, CHAT, CROSSHAIR, BOSS_BAR, SCOREBOARD), removeElement et replaceElement (pour masquer ou encapsuler un élément vanilla). L'API gère le calque ; vous ne gérez pas les décalages Z.

Règles pour le code HUD :

  • Retournez tôt quand mc.player == null, quand le HUD est masqué avec F1 (mc.gui.hud.isHidden() sur 26.3, mc.options.hideGui sur les versions plus anciennes – Fabric ne masque pas votre élément pour vous), et quand votre fonctionnalité est désactivée. Envisagez également de masquer l'élément pendant que l'écran de débug F3 est ouvert si votre élément le chevaucherait (recherchez l'accesseur du debug-overlay dans les sources de votre version).
  • Positionnez par rapport à graphics.guiWidth() / graphics.guiHeight() (taille mise à l'échelle), jamais d'hypothèses de pixels codées en dur sur la fenêtre.
  • Utilisez DeltaTracker#getGameTimeDeltaPartialTick(false) pour l'animation interpolée par tick et Util.getMillis() pour l'animation en temps réel.

Anciens callbacks HUD (pre-1.21.6)

// 1.20 - 1.21.1 (noms Mojang affichés ; Yarn : DrawContext, RenderTickCounter)
HudRenderCallback.EVENT.register((graphics, deltaTracker) -> { /* dessiner */ });

HudLayerRegistrationCallback (1.21.2 - 1.21.5) enregistre les couches par rapport aux identifiants IdentifiedLayer, dans le même esprit que HudElementRegistry. Consultez la page de documentation pour cette version précise avant de l'utiliser.

Primitives de dessin

graphics.fill(x1, y1, x2, y2, 0x80000000);                     // ARGB ! 0x80 = 50 % d'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);   // clip ; les appels doivent être équilibrés
// ... dessiner ...
graphics.disableScissor();

graphics.blit(RenderPipelines.GUI_TEXTURED, ExampleMod.id("textures/gui/icon.png"),
              x, y, 0, 0, 16, 16, 16, 16);   // pipeline, texture, x, y, u, v, l, h, texL, texH
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, spriteId, x, y, l, h);   // sprite de l'atlas GUI
graphics.centeredText(font, component, centerX, y, 0xFFFFFFFF);
graphics.setTooltipForNextFrame(font, component, mouseX, mouseY);
graphics.item(stack, x, y);               // icône d'objet (nom 26.x ; plus ancien : renderItem / drawItem)

Les couleurs sont en ARGB depuis 1.21.6. 0xFFFFFF est un blanc totalement transparent et n'affiche rien. Utilisez 0xFFFFFFFF, ou enveloppez le RGB avec ARGB.opaque(rgb). C'est le bug "mon texte ne s'affiche pas" le plus courant lors du portage de code plus ancien.

Texte

Component title = Component.translatable("hud.examplemod.title");        // localisé
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);   // dernier argument : ombre portée
  • Placez les chaînes visibles par l'utilisateur dans assets/<modid>/lang/en_us.json et utilisez Component.translatable. Les chaînes anglaises codées en dur dans Component.literal ne peuvent pas être traduites.
  • Équivalents Yarn : Text.translatable, Text.literal, Formatting, textRenderer.

Infobulles

Ajoutez des lignes à l'infobulle de n'importe quel objet sans mixin :

ItemTooltipCallback.EVENT.register((stack, tooltipContext, tooltipFlag, lines) -> {
    if (stack.is(Items.COMPASS)) {
        lines.add(Component.translatable("tooltip.examplemod.compass").withStyle(ChatFormatting.GRAY));
    }
});

ItemTooltipCallback se trouve dans net.fabricmc.fabric.api.client.item.v1. Pour vos propres objets, remplacez la méthode d'infobulle de l'objet ou utilisez un composant de données à la place. Utilisez tooltipFlag.isAdvanced() pour limiter les informations de débogage à F3+H.

Écrans personnalisés

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() {   // appelé à l'ouverture ET à chaque redimensionnement – reconstruisez les widgets ici
        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);   // arrière-plan + widgets
        graphics.centeredText(this.font, this.title, this.width / 2, 15, 0xFFFFFFFF);
    }

    @Override
    public void onClose() {
        this.minecraft.gui.setScreen(this.parent);   // 26.2+ ; avant : this.minecraft.setScreen(...)
    }
}
  • Créez les widgets dans init(), pas dans le constructeur ; init() est relancé lors du redimensionnement et la liste des widgets est vidée à chaque fois.
  • Gardez toujours un parent et revenez-y (Mod Menu et les écrans d'options en dépendent).
  • Ouvrez un écran depuis un gestionnaire de touches ou un gestionnaire de ticks sur le thread client. L'ouvrir depuis un gestionnaire réseau ou un autre thread nécessite client.execute(() -> ...).
  • Préférez les widgets vanilla (Button, EditBox, CycleButton, AbstractSliderButton) avant d'en écrire des personnalisés.

Performances – ce code s'exécute à chaque frame

Les méthodes de dessin HUD et d'écran s'exécutent 60 à 500+ fois par seconde. Traitez-les comme une boucle chaude :

  • Pas d'allocations par frame quand c'est évitable : pas de new objets, pas de String.format, pas de concaténation de chaînes dans le chemin de dessin, pas de streams/lambdas créés par appel, pas de Component.literal d'une valeur qui n'a pas changé. Mettez en cache le Component et ne le reconstruisez que lorsque la valeur sous-jacente change (par exemple lors d'un événement de tick, ou lorsque la position du bloc diffère).
  • Pas de travail d'état du jeu dans le rendu : pas de scans du monde, de recherches d'entités, de raycasts, d'E/S de fichiers, de lectures de configuration depuis le disque, ou de recherches dans les registres. Calculez dans ClientTickEvents.END_CLIENT_TICK (20 fois/s) et dessinez le résultat mis en cache.
  • Ne faites pas de logging depuis le chemin de rendu.
  • Évitez les textures uniques téléchargées à chaque frame ; enregistrez les textures une fois.
  • Mesurez avec le graphique de temps par frame de F3 ou un profileur (Spark) avant et après.
// Bien : ne recalculez qu'au plus une fois par tick, seulement si modifié
public final class CoordsText {
    private static Component cached = Component.empty();
    private static BlockPos last = null;

    public static void onTick(Minecraft mc) {             // depuis 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; }
}

Rendu du monde

Le dessin dans le monde 3D (contours, boîtes, lignes) utilise LevelRenderEvents (26.1+ ; nommé WorldRenderEvents avant) et des pipelines de rendu, et change presque à chaque version car Mojang réécrit le moteur de rendu. Lisez https://docs.fabricmc.net/develop/rendering/world pour votre version précise et copiez sa structure ; n'adaptez pas un extrait de rendu du monde depuis une autre version.

Erreurs courantes

  • Utiliser DrawContext/MatrixStack/TextRenderer (Yarn) dans un projet nommé Mojang, ou GuiGraphics dans un projet 26.1+ où il s'agit de GuiGraphicsExtractor.
  • Surcharger render(...) dans un Screen 26.1+ – la méthode est extractRenderState ; l'ancien nom compile seulement si vous oubliez aussi @Override, puis ne s'exécute jamais. Utilisez toujours @Override.
  • Couleurs RGB (0xFFFFFF) après 1.21.6 -> textes et remplissages invisibles.
  • Utiliser HudRenderCallback déprécié sur 1.21.6+, ou un mixin dans la classe vanilla Gui pour un simple overlay.
  • Allouer des chaînes/composants à chaque frame, ou faire des requêtes monde dans le callback HUD.
  • Ne pas vérifier mc.player == null (crash sur l'écran titre) ou ignorer l'état de masquage HUD F1.
  • Construire des widgets dans le constructeur de Screen au lieu de init().
  • Oublier d'appeler super.extractRenderState(...) / super.render(...) et perdre l'arrière-plan et tous les widgets.
  • Chaînes anglaises codées en dur au lieu de clés de traduction.
  • Placer quoi que ce soit de ceci dans src/main.

Individual skills in this repo

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

Skills associés