Communitygithub.com

vvs1off/fabric-client-rendering

在 Minecraft Fabric 客户端中进行绘制:HUD 元素、GUI 绘制、文字、物品提示、自定义屏幕及性能优化。

fabric-client-rendering 是什么?

该技能专注于 Fabric 客户端的渲染开发,涵盖 HUD 元素(HudElementRegistry 及旧版回调)、GUI 绘制、文字与组件、物品提示框、自定义屏幕及性能优化。它基于 Fabric 模组开发环境,仅存在于 `src/client/java` 中,通过 `ClientModInitializer` 注册,避免在通用代码中引用导致服务器崩溃。 技能详细说明了不同 Minecraft 版本(如 26.1+、1.21.2-1.21.5、1.20-1.21.1)对应的 GUI 绘图类、屏幕绘制方法、文字方法及 HUD API 的映射关系,并提供命令行查询版本和映射方式。示例代码使用 26.1+ Mojang 映射名称(GuiGraphicsExtractor、HudElementRegistry),但通过表格指导适配旧版本。 重点包括 HUD 元素的位置规则(如检查玩家是否为空、是否被 F1 隐藏、是否被 F3 调试重叠)、坐标计算相对 GUI 尺寸、使用 DeltaTracker 进行帧同步动画;屏幕开发中强调在 `init()` 中创建组件、保留父屏幕引用、使用原生组件;性能方面要求每帧避免对象分配和游戏状态计算,通过客户端 tick 事件缓存结果。 此外,还介绍了绘制基元(填充、线条、纹理 blit、裁剪、工具提示)、文字国际化、物品提示回调(ItemTooltipCallback)以及自定义屏幕的实现模式。最后总结了常见错误,如版本 API 混淆、颜色格式错误、在 HUD 回调中进行世界查询等。

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

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Fabric 客户端渲染

本技能中的所有代码都是仅客户端的:它们应放在 src/client/java 中,并通过 ClientModInitializer 注册。从通用代码中引用它们会导致专用服务器崩溃。

第 0 步 - 当前版本使用哪个绘图 API?

GUI 绘图类和 HUD API 被重命名了多次。在编写代码之前先检查:

grep -E "minecraft_version|yarn_mappings" gradle.properties
grep -n "mappings" build.gradle
版本/映射GUI 绘制类Screen 绘制方法文字方法HUD API
26.1+ (Mojang, 未混淆)GuiGraphicsExtractorextractRenderState(graphics, mouseX, mouseY, delta)graphics.text(font, ...)HudElementRegistry
1.21.6 - 1.21.11, Mojang 映射GuiGraphicsrender(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.5同上(根据您的映射)render(...)同上HudLayerRegistrationCallback(HudRenderCallback 已弃用)
1.20 - 1.21.1同上(根据您的映射)render(...)同上HudRenderCallback

然后在实际依赖中确认,因为名称不断变化(例如 26.2 将 minecraft.setScreen 移到了 minecraft.gui.setScreen):

./gradlew genSources
# 在 IDE 中打开 HudElementRegistry / 您的 GUI 绘制类,或阅读 Fabric API 的源代码 jar

带有有效代码的版本匹配指南:https://docs.fabricmc.net/develop/rendering/hud(使用页面顶部的版本选择器)。

下面的示例使用 26.1+ Mojang 名称。对于旧版本,请通过上表进行转换;结构完全相同。

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 "隐藏 HUD":26.3 为 mc.gui.hud.isHidden();旧版本为 mc.options.hideGui。
        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 方法:addFirst、addLast、attachElementBefore、attachElementAfter(相对于 VanillaHudElements 的 ID,如 HOTBAR、CHAT、CROSSHAIR、BOSS_BAR、SCOREBOARD)、removeElement 和 replaceElement(用于隐藏或包装原版元素)。API 处理分层;您无需管理 z 偏移。

HUD 代码的规则:

  • 当 mc.player == null、当使用 F1 隐藏 HUD(26.3 为 mc.gui.hud.isHidden(),旧版本为 mc.options.hideGui - Fabric 不会自动隐藏您的元素)以及当您的功能被禁用时,尽早返回。如果您的元素可能与 F3 调试屏幕重叠,也要考虑在 F3 调试屏幕打开时隐藏(在您版本的源代码中查找调试覆盖访问器)。
  • 相对 graphics.guiWidth() / graphics.guiHeight()(缩放尺寸)定位,切勿对窗口进行硬编码像素假设。
  • 对于基于刻度的插值动画使用 DeltaTracker#getGameTimeDeltaPartialTick(false),对于挂钟动画使用 Util.getMillis()。

旧版 HUD 回调(1.21.6 之前)

// 1.20 - 1.21.1(显示 Mojang 名称;Yarn:DrawContext, RenderTickCounter)
HudRenderCallback.EVENT.register((graphics, deltaTracker) -> { /* 绘制 */ });

HudLayerRegistrationCallback(1.21.2 - 1.21.5)相对于 IdentifiedLayer ID 注册图层,本质上类似于 HudElementRegistry。在使用之前请查看对应版本的文档页面。

绘制基元

graphics.fill(x1, y1, x2, y2, 0x80000000);                     // ARGB!0x80 = 50% 透明度
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);   // 裁剪;调用必须平衡
// ... 绘制 ...
graphics.disableScissor();

graphics.blit(RenderPipelines.GUI_TEXTURED, ExampleMod.id("textures/gui/icon.png"),
              x, y, 0, 0, 16, 16, 16, 16);   // 管线、纹理、x、y、u、v、w、h、texW、texH
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, spriteId, x, y, w, h);   // GUI 纹理集精灵
graphics.centeredText(font, component, centerX, y, 0xFFFFFFFF);
graphics.setTooltipForNextFrame(font, component, mouseX, mouseY);
graphics.item(stack, x, y);               // 物品图标(26.x 名称;旧版:renderItem / drawItem)

从 1.21.6 起颜色为 ARGB。 0xFFFFFF 是完全透明的白色,不渲染任何内容。请使用 0xFFFFFFFF,或将 RGB 包装为 ARGB.opaque(rgb)。这是移植旧代码时最常见的“我的文本不显示”错误。

文字

Component title = Component.translatable("hud.examplemod.title");        // 本地化
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);   // 最后一个参数:阴影
  • 将用户可见的字符串放在 assets/<modid>/lang/en_us.json 中,并使用 Component.translatable。在 Component.literal 中硬编码的英文无法被翻译。
  • Yarn 对应:Text.translatable、Text.literal、Formatting、textRenderer。

工具提示

无需 mixin 即可向任何物品的工具提示追加行:

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

ItemTooltipCallback 位于 net.fabricmc.fabric.api.client.item.v1。对于您自己的物品,请覆盖物品的工具提示方法或改用数据组件。使用 tooltipFlag.isAdvanced() 将调试信息限制在 F3+H 时显示。

自定义屏幕

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() {   // 在打开时和每次调整大小时调用 - 在此处重建组件
        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);   // 背景 + 组件
        graphics.centeredText(this.font, this.title, this.width / 2, 15, 0xFFFFFFFF);
    }

    @Override
    public void onClose() {
        this.minecraft.gui.setScreen(this.parent);   // 26.2+;更早:this.minecraft.setScreen(...)
    }
}
  • 在 init() 中创建组件,而不是在构造方法中;init() 在调整大小时会重新执行,并且组件列表每次都会被清空。
  • 始终保留一个 parent 并返回给它(Mod Menu 和选项屏幕依赖于此)。
  • 从客户端线程上的按键或 tick 处理器打开屏幕。从网络处理器或其他线程打开需要 client.execute(() -> ...)。
  • 在编写自定义组件之前,优先使用原版组件(Button、EditBox、CycleButton、AbstractSliderButton)。

性能 - 此代码每帧运行

HUD 和屏幕绘制方法每秒运行 60-500+ 次。将它们视为热循环:

  • 每帧避免分配:没有 new 对象,没有 String.format,没有字符串拼接,没有每调用创建的流/lambda,没有对未更改值进行的 Component.literal。缓存 Component 并仅当底层值更改时(例如在 tick 事件上,或当方块位置不同时)才重建它。
  • 不要在渲染中进行游戏状态工作:没有世界扫描、实体搜索、射线追踪、文件 I/O、从磁盘读取配置或注册表查找。在 ClientTickEvents.END_CLIENT_TICK(20 次/秒)中进行计算,并绘制缓存的结果。
  • 不要从渲染路径中记录日志。
  • 避免每帧上传一次性纹理;注册一次纹理。
  • 使用 F3 帧时间图或探查器(Spark)在操作前后进行测量。
// 良好:每 tick 最多重新计算一次,仅当更改时
public final class CoordsText {
    private static Component cached = Component.empty();
    private static BlockPos last = null;

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

世界渲染

在 3D 世界中绘制(轮廓、方框、线条)使用 LevelRenderEvents(26.1+;之前称为 WorldRenderEvents)和渲染管线,并且几乎每个版本都会随着 Mojang 重写渲染器而变化。请阅读 https://docs.fabricmc.net/develop/rendering/world 以了解您的确切版本并复制其结构;不要从另一个版本适配世界渲染片段。

常见错误

  • 在使用 Mojang 映射的项目中使用 DrawContext/MatrixStack/TextRenderer(Yarn),或者在 26.1+ 项目中使用 GuiGraphics(实际应为 GuiGraphicsExtractor)。
  • 在 26.1+ 的 Screen 中覆盖 render(...) - 方法名是 extractRenderState;旧名称只有在您同时忘记 @Override 时才会编译,但永远不会运行。始终使用 @Override。
  • 1.21.6 之后的 RGB 颜色(0xFFFFFF)-> 文字和填充不可见。
  • 在 1.21.6+ 上使用已弃用的 HudRenderCallback,或使用 mixin 覆盖原版 Gui 类以实现简单覆盖。
  • 每帧分配字符串/组件,或在 HUD 回调内部进行世界查询。
  • 未检查 mc.player == null(在标题屏幕上崩溃)或忽略 F1 隐藏 HUD 状态。
  • 在 Screen 构造方法而不是 init() 中构建组件。
  • 忘记调用 super.extractRenderState(...) / super.render(...),导致丢失背景和所有组件。
  • 硬编码英文字符串而不是使用翻译键。
  • 将这些代码中的任何一个放在 src/main 中。

Individual skills in this repo

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

相关技能