Renderizado en el cliente de Fabric
Todo el código en este skill es solo para el cliente: va en src/client/java y se registra
desde tu ClientModInitializer. Referenciarlo desde código común crashea servidores dedicados.
Paso 0 - ¿Qué API de dibujo tiene esta versión?
La clase de dibujo de la GUI y la API del HUD se renombraron varias veces. Verifica antes de escribir código:
grep -E "minecraft_version|yarn_mappings" gradle.properties
grep -n "mappings" build.gradle
| Versión / mapeos | Clase de dibujo GUI | Método de dibujo Screen | Método de texto | API de HUD |
|---|---|---|---|---|
| 26.1+ (Mojang, sin ofuscar) | GuiGraphicsExtractor | extractRenderState(graphics, mouseX, mouseY, delta) | graphics.text(font, ...) | HudElementRegistry |
| 1.21.6 - 1.21.11, mapeos Mojang | 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 | como arriba para tus mapeos | render(...) | como arriba | HudLayerRegistrationCallback (HudRenderCallback obsoleto) |
| 1.20 - 1.21.1 | como arriba para tus mapeos | render(...) | como arriba | HudRenderCallback |
Luego confirma en la dependencia real, porque los nombres siguen moviéndose (26.2 movió
minecraft.setScreen a minecraft.gui.setScreen, por ejemplo):
./gradlew genSources
# abre HudElementRegistry / tu clase de dibujo GUI en el IDE, o lee el jar de fuentes de Fabric API
Guías adaptadas por versión con código funcional: https://docs.fabricmc.net/develop/rendering/hud (usa el selector de versión en la parte superior de la página).
Los ejemplos a continuación usan los nombres de Mojang 26.1+. Traduce a través de la tabla para versiones más antiguas; la estructura es idéntica.
Elementos 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() en 26.3; mc.options.hideGui en versiones antiguas.
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 un id de VanillaHudElements como HOTBAR, CHAT,
CROSSHAIR, BOSS_BAR, SCOREBOARD), removeElement y replaceElement (para ocultar o
envolver un elemento vanilla). La API maneja las capas; no gestionas desplazamientos z.
Reglas para el código HUD:
- Retorna temprano cuando
mc.player == null, cuando el HUD está oculto con F1 (mc.gui.hud.isHidden()en 26.3,mc.options.hideGuien versiones antiguas - Fabric no oculta tu elemento por ti), y cuando tu función está deshabilitada. También considera ocultar mientras la pantalla de depuración F3 está abierta si tu elemento se superpondría (busca el accessor de superposición de depuración en las fuentes de tu versión). - Posición relativa a
graphics.guiWidth()/graphics.guiHeight()(tamaño escalado), nunca suposiciones fijas de píxeles sobre la ventana. - Usa
DeltaTracker#getGameTimeDeltaPartialTick(false)para animación interpolada por tick yUtil.getMillis()para animación en tiempo real.
Callbacks HUD antiguos (pre-1.21.6)
// 1.20 - 1.21.1 (nombres Mojang mostrados; Yarn: DrawContext, RenderTickCounter)
HudRenderCallback.EVENT.register((graphics, deltaTracker) -> { /* dibujar */ });
HudLayerRegistrationCallback (1.21.2 - 1.21.5) registra capas relativas a
ids de IdentifiedLayer, similar en espíritu a HudElementRegistry. Consulta la página de documentación para esa
versión exacta antes de usarlo.
Primitivas de dibujo
graphics.fill(x1, y1, x2, y2, 0x80000000); // ¡ARGB! 0x80 = 50% alfa
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; las llamadas deben estar equilibradas
// ... dibujar ...
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 del atlas de la GUI
graphics.centeredText(font, component, centerX, y, 0xFFFFFFFF);
graphics.setTooltipForNextFrame(font, component, mouseX, mouseY);
graphics.item(stack, x, y); // icono de objeto (nombre 26.x; más antiguo: renderItem / drawItem)
Los colores son ARGB desde 1.21.6. 0xFFFFFF es blanco completamente transparente y no renderiza nada.
Usa 0xFFFFFFFF, o envuelve RGB con ARGB.opaque(rgb). Este es el error más común de "mi texto no
se muestra" al portar código antiguo.
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
- Pon las cadenas visibles para el usuario en
assets/<modid>/lang/en_us.jsony usaComponent.translatable. Las cadenas en inglés hardcodeadas enComponent.literalno pueden traducirse. - Equivalentes en Yarn:
Text.translatable,Text.literal,Formatting,textRenderer.
Tooltips
Añade líneas al tooltip de cualquier objeto sin un mixin:
ItemTooltipCallback.EVENT.register((stack, tooltipContext, tooltipFlag, lines) -> {
if (stack.is(Items.COMPASS)) {
lines.add(Component.translatable("tooltip.examplemod.compass").withStyle(ChatFormatting.GRAY));
}
});
ItemTooltipCallback vive en net.fabricmc.fabric.api.client.item.v1. Para tus propios
objetos, sobrescribe el método tooltip del objeto o usa un componente de datos. Usa
tooltipFlag.isAdvanced() para mostrar información de depuración detrás de F3+H.
Pantallas 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() { // se llama al abrir Y en cada redimensionamiento - reconstruye widgets aquí
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); // fondo + widgets
graphics.centeredText(this.font, this.title, this.width / 2, 15, 0xFFFFFFFF);
}
@Override
public void onClose() {
this.minecraft.gui.setScreen(this.parent); // 26.2+; más antiguo: this.minecraft.setScreen(...)
}
}
- Crea widgets en
init(), no en el constructor;init()se vuelve a ejecutar al redimensionar y la lista de widgets se limpia cada vez. - Siempre guarda un
parenty vuelve a él (Mod Menu y las pantallas de opciones dependen de esto). - Abre una pantalla desde un manejador de teclas o tick en el hilo del cliente. Abrirla desde un
manejador de red u otro hilo necesita
client.execute(() -> ...). - Prefiere widgets vanilla (
Button,EditBox,CycleButton,AbstractSliderButton) antes de escribir personalizados.
Rendimiento - este código se ejecuta cada fotograma
Los métodos de dibujo de HUD y pantalla se ejecutan 60-500+ veces por segundo. Trátalos como un bucle caliente:
- Sin asignaciones por fotograma donde sea evitable: sin objetos
new, sinString.format, sin concatenación de cadenas en la ruta de dibujo, sin streams/lambdas creados por llamada, sinComponent.literalde un valor que no ha cambiado. Almacena en caché elComponenty reconstrúyelo solo cuando el valor subyacente cambie (por ejemplo, en un evento de tick, o cuando la posición del bloque difiera). - Sin trabajo de estado del juego en el render: sin escaneos del mundo, búsquedas de entidades, raycasts, E/S de archivos,
lecturas de configuración desde el disco, o búsquedas en registros. Calcula en
ClientTickEvents.END_CLIENT_TICK(20 veces/s) y dibuja el resultado almacenado en caché. - No hagas logs desde la ruta de renderizado.
- Evita texturas únicas subidas cada fotograma; registra las texturas una vez.
- Mide con el gráfico de tiempo por fotograma de F3 o un perfilador (Spark) antes y después.
// Bueno: recalcular como máximo una vez por tick, solo si cambió
public final class CoordsText {
private static Component cached = Component.empty();
private static BlockPos last = null;
public static void onTick(Minecraft mc) { // desde 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; }
}
Renderizado del mundo
Dibujar en el mundo 3D (contornos, cajas, líneas) usa LevelRenderEvents (26.1+; antes llamado
WorldRenderEvents) y pipelines de renderizado, y cambia casi cada versión mientras Mojang reescribe
el motor de renderizado. Lee https://docs.fabricmc.net/develop/rendering/world para tu versión
exacta y copia su estructura; no adaptes un fragmento de renderizado del mundo de otra versión.
Errores comunes
- Usar
DrawContext/MatrixStack/TextRenderer(Yarn) en un proyecto con nombres Mojang, oGuiGraphicsen un proyecto 26.1+ donde esGuiGraphicsExtractor. - Sobrescribir
render(...)en unScreen26.1+ - el método esextractRenderState; el nombre antiguo compila solo si también olvidas@Override, y entonces nunca se ejecuta. Siempre usa@Override. - Colores RGB (
0xFFFFFF) después de 1.21.6 -> texto y rellenos invisibles. - Usar
HudRenderCallbackobsoleto en 1.21.6+, o un mixin en la claseGuivanilla para una superposición simple. - Asignar cadenas/componentes cada fotograma, o hacer consultas al mundo dentro del callback del HUD.
- No verificar
mc.player == null(crash en la pantalla de título) o ignorar el estado de ocultación del HUD con F1. - Construir widgets en el constructor de
Screenen lugar deinit(). - Olvidar llamar a
super.extractRenderState(...)/super.render(...)y perder el fondo y todos los widgets. - Cadenas en inglés hardcodeadas en lugar de claves de traducción.
- Poner cualquiera de esto en
src/main.