Communitygithub.com

vvs1off/fabric-client-rendering

Desenhe elementos na tela do cliente Minecraft com Fabric – HUD, telas personalizadas, tooltips e renderização eficiente.

O que é fabric-client-rendering?

Esta skill fornece orientação completa para implementar renderização no lado do cliente com Fabric, abrangendo desde elementos de HUD (via HudElementRegistry e callbacks antigos) até desenho de GUI (GuiGraphicsExtractor / GuiGraphics / DrawContext), texto e componentes, tooltips de itens, telas personalizadas e widgets. Ela é essencial para quem precisa adicionar ou corrigir sobreposições de HUD, telas, tooltips ou qualquer código de desenho por quadro. A skill inclui tabelas de compatibilidade entre versões do Minecraft (1.20 a 26.1+), orientações sobre namespaces de código (Mojang vs Yarn), práticas de desempenho (evitar alocações por quadro, não realizar consultas ao mundo no renderizador) e armadilhas comuns como usar cores RGB após 1.21.6 (que ficam invisíveis) ou esquecer de verificar `mc.player == null`. Ela suporta todas as versões modernas do Fabric, com exemplos práticos de código Java e referências à documentação oficial.

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

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Renderização cliente Fabric

Todo o código desta skill é apenas para o cliente: deve ficar em src/client/java e ser registrado a partir do seu ClientModInitializer. Referenciá-lo no código comum (shared) causará crash em servidores dedicados.

Passo 0 – Qual API de desenho esta versão usa?

A classe de desenho da GUI e a API de HUD foram renomeadas várias vezes. Verifique antes de escrever código:

grep -E "minecraft_version|yarn_mappings" gradle.properties
grep -n "mappings" build.gradle
Versão / mapeamentosClasse de desenho GUIMétodo de desenho da telaMétodo de textoAPI de HUD
26.1+ (Mojang, não ofuscado)GuiGraphicsExtractorextractRenderState(graphics, mouseX, mouseY, delta)graphics.text(font, ...)HudElementRegistry
1.21.6 – 1.21.11, mapeamentos 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.5como acima para seus mapeamentosrender(...)como acimaHudLayerRegistrationCallback (HudRenderCallback obsoleto)
1.20 – 1.21.1como acima para seus mapeamentosrender(...)como acimaHudRenderCallback

Em seguida, confirme na dependência real, porque os nomes continuam mudando (26.2 moveu minecraft.setScreen para minecraft.gui.setScreen, por exemplo):

./gradlew genSources
# abra HudElementRegistry / sua classe de desenho GUI na IDE, ou leia o jar de fontes da Fabric API

Guias compatíveis com versões e com código funcional: https://docs.fabricmc.net/develop/rendering/hud (use o seletor de versão no topo da página).

Os exemplos abaixo usam os nomes Mojang 26.1+. Traduza conforme a tabela para versões mais antigas; a estrutura é idêntica.

Elementos de 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 "ocultar HUD": mc.gui.hud.isHidden() no 26.3; mc.options.hideGui em versões antigas.
        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étodos de HudElementRegistry: addFirst, addLast, attachElementBefore, attachElementAfter (relativo a um id de VanillaHudElements como HOTBAR, CHAT, CROSSHAIR, BOSS_BAR, SCOREBOARD), removeElement e replaceElement (para ocultar ou encapsular um elemento vanilla). A API gerencia camadas; você não precisa gerenciar eixos Z.

Regras para código de HUD:

  • Retorne cedo quando mc.player == null, quando o HUD estiver oculto com F1 (mc.gui.hud.isHidden() no 26.3, mc.options.hideGui em versões antigas – a Fabric não oculta seu elemento automaticamente) e quando sua funcionalidade estiver desativada. Considere também ocultar enquanto a tela de depuração F3 estiver aberta se seu elemento sobrepujá-la (consulte o accessor de debug-overlay nas fontes da sua versão).
  • Posicione relativo a graphics.guiWidth() / graphics.guiHeight() (tamanho escalado), nunca suposições de pixel codificadas sobre a janela.
  • Use DeltaTracker#getGameTimeDeltaPartialTick(false) para animação interpolada por tick e Util.getMillis() para animação baseada em tempo real (wall-clock).

Callbacks de HUD antigos (pré-1.21.6)

// 1.20 – 1.21.1 (nomes Mojang mostrados; Yarn: DrawContext, RenderTickCounter)
HudRenderCallback.EVENT.register((graphics, deltaTracker) -> { /* desenhar */ });

HudLayerRegistrationCallback (1.21.2 – 1.21.5) registra camadas relativas a ids de IdentifiedLayer, semelhante em espírito a HudElementRegistry. Consulte a página de documentação para essa versão exata antes de usá-la.

Primitivas de desenho

graphics.fill(x1, y1, x2, y2, 0x80000000);                     // ARGB! 0x80 = 50% de 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);   // recortar; as chamadas devem ser equilibradas
// ... desenhar ...
graphics.disableScissor();

graphics.blit(RenderPipelines.GUI_TEXTURED, ExampleMod.id("textures/gui/icon.png"),
              x, y, 0, 0, 16, 16, 16, 16);   // pipeline, textura, x, y, u, v, w, h, texW, texH
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, spriteId, x, y, w, h);   // sprite do atlas da GUI
graphics.centeredText(font, component, centerX, y, 0xFFFFFFFF);
graphics.setTooltipForNextFrame(font, component, mouseX, mouseY);
graphics.item(stack, x, y);               // ícone de item (nome 26.x; antigo: renderItem / drawItem)

Cores são ARGB desde 1.21.6. 0xFFFFFF é branco completamente transparente e não renderiza nada. Use 0xFFFFFFFF, ou envolva RGB com ARGB.opaque(rgb). Esse é o erro mais comum de "meu texto não aparece" ao portar código antigo.

Texto

Component title = Component.translatable("hud.examplemod.title");        // localizado
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);   // último argumento: sombra
  • Coloque strings visíveis ao usuário em assets/<modid>/lang/en_us.json e use Component.translatable. Strings em inglês codificadas em Component.literal não podem ser traduzidas.
  • Equivalentes Yarn: Text.translatable, Text.literal, Formatting, textRenderer.

Tooltips

Adicione linhas ao tooltip de qualquer item sem um mixin:

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

ItemTooltipCallback está em net.fabricmc.fabric.api.client.item.v1. Para seus próprios itens, sobrescreva o método de tooltip do item ou use um componente de dados. Use tooltipFlag.isAdvanced() para mostrar informações de depuração atrás de F3+H.

Telas personalizadas

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() {   // chamado ao abrir E em todo redimensionamento – reconstrua widgets aqui
        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);   // fundo + widgets
        graphics.centeredText(this.font, this.title, this.width / 2, 15, 0xFFFFFFFF);
    }

    @Override
    public void onClose() {
        this.minecraft.gui.setScreen(this.parent);   // 26.2+; anterior: this.minecraft.setScreen(...)
    }
}
  • Crie widgets em init(), não no construtor; init() é reexecutado no redimensionamento e a lista de widgets é limpa a cada vez.
  • Sempre mantenha um parent e retorne a ele (Mod Menu e telas de opções dependem disso).
  • Abra uma tela a partir de um pressionamento de tecla ou manipulador de tick na thread do cliente. Abri-la a partir de um manipulador de rede ou outra thread requer client.execute(() -> ...).
  • Prefira widgets vanilla (Button, EditBox, CycleButton, AbstractSliderButton) antes de criar os próprios.

Desempenho – este código roda a cada quadro

Os métodos de desenho de HUD e tela rodam 60–500+ vezes por segundo. Trate-os como um loop quente:

  • Sem alocações por quadro sempre que possível: sem objetos new, sem String.format, sem concatenação de strings no caminho de desenho, sem streams/lambdas criados por chamada, sem Component.literal de um valor que não mudou. Armazene em cache o Component e reconstrua-o apenas quando o valor subjacente mudar (por exemplo, em um evento de tick ou quando a posição do bloco diferir).
  • Sem trabalho de estado do jogo no renderizador: sem varreduras do mundo, buscas por entidades, raycasts, E/S de arquivo, leitura de configuração do disco ou consultas a registries. Calcule em ClientTickEvents.END_CLIENT_TICK (20 vezes/s) e desenhe o resultado em cache.
  • Não faça log a partir do caminho de renderização.
  • Evite texturas únicas enviadas a cada quadro; registre texturas uma vez.
  • Meça com o gráfico de tempo por quadro do F3 ou um profiler (Spark) antes e depois.
// Bom: recalcula no máximo uma vez por tick, apenas se mudou
public final class CoordsText {
    private static Component cached = Component.empty();
    private static BlockPos last = null;

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

Renderização do mundo

Desenhar no mundo 3D (contornos, caixas, linhas) usa LevelRenderEvents (26.1+; antes chamado WorldRenderEvents) e pipelines de renderização, e muda quase toda versão conforme a Mojang reescreve o renderizador. Leia https://docs.fabricmc.net/develop/rendering/world para sua versão exata e copie sua estrutura; não adapte um trecho de renderização de mundo de outra versão.

Erros comuns

  • Usar DrawContext/MatrixStack/TextRenderer (Yarn) em um projeto com nomes Mojang, ou GuiGraphics em um projeto 26.1+ onde é GuiGraphicsExtractor.
  • Sobrescrever render(...) em uma Screen 26.1+ – o método é extractRenderState; o nome antigo compila apenas se você também esquecer @Override, e então nunca executa. Sempre use @Override.
  • Cores RGB (0xFFFFFF) após 1.21.6 → textos e preenchimentos invisíveis.
  • Usar HudRenderCallback obsoleto no 1.21.6+, ou um mixin na classe vanilla Gui para uma sobreposição simples.
  • Alocar strings/componentes a cada quadro, ou fazer consultas ao mundo dentro do callback de HUD.
  • Não verificar mc.player == null (crash na tela de título) ou ignorar o estado de ocultação do HUD (F1).
  • Construir widgets no construtor da Screen em vez de init().
  • Esquecer de chamar super.extractRenderState(...) / super.render(...) e perder o fundo e todos os widgets.
  • Strings em inglês codificadas em vez de chaves de tradução.
  • Colocar qualquer um destes em src/main.

Individual skills in this repo

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

Habilidades Relacionadas