Communitygithub.com

vvs1off/fabric-client-rendering

在 Minecraft 用戶端使用 Fabric 繪製圖形 — 包含 HUD 元素 (HudElementRegistry 及舊版 HUD 回呼)、GUI 繪圖 (依版本使用 GuiGraphicsExtractor / GuiGraphics / DrawContext)、文字與元件、物品提示框、自訂螢幕與小部件,以及渲染效能最佳化。適用於新增或修正 HUD 疊層、螢幕、提示框或任何每影格執行的用戶端繪圖程式碼。

fabric-client-rendering 是什麼?

這項技能專門處理 Minecraft Fabric 模組的用戶端繪圖功能,涵蓋 HUD 疊層、自訂螢幕、物品提示框以及 3D 世界渲染。它詳盡列舉了不同 Minecraft 版本與對映 (Mojang/Yarn) 下繪圖 API 的差異,透過表格對照 `GuiGraphicsExtractor`、`GuiGraphics` 與 `DrawContext` 等類別名稱,協助開發者快速選用正確的 API。技能提供了完整的 Java 程式碼範例,包括使用 `HudElementRegistry` 註冊 HUD 元素、`ItemTooltipCallback` 擴充提示框、以及實作自訂 `Screen` 類別,並特別強調必須將所有程式碼置於 `src/client/java` 目錄下,以避免在專用伺服器上當機。此外,技能深入說明了關鍵的效能最佳化原則,例如避免在每影格繪製路徑中進行物件配置、字串串接與遊戲狀態查詢,並建議透過用戶端 tick 事件預先計算快取結果。文中也列舉了常見錯誤,如使用錯誤的繪圖類別名稱、覆寫錯誤的方法簽章、忽略 ARGB 色彩格式變更、以及未檢查 `mc.player == null` 等,適合所有需要撰寫或維護 Fabric 用戶端繪圖程式的開發者參考。

相容平台~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 的 sources 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、HUD 被 F1 隱藏時(在 26.3 為 mc.gui.hud.isHidden(),較舊版本為 mc.options.hideGui — Fabric 不會自動為你隱藏元素),以及你的功能被停用時,請及早返回。此外,如果你的元素可能與 F3 除錯畫面重疊,也應考慮在除錯畫面開啟時隱藏(請查閱你所使用版本的原始碼中的除錯疊層存取器)。
  • 相對於 graphics.guiWidth() / graphics.guiHeight()(縮放後的尺寸)進行定位,切勿對視窗大小使用硬編碼的像素假設。
  • 使用 DeltaTracker#getGameTimeDeltaPartialTick(false) 進行以 tick 插值的動畫,使用 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,或使用 ARGB.opaque(rgb) 包裹 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.

相關技能