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, 未混淆) | GuiGraphicsExtractor | extractRenderState(graphics, mouseX, mouseY, delta) | graphics.text(font, ...) | HudElementRegistry |
| 1.21.6 - 1.21.11, 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 | 同上(根据您的映射) | 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中。