Communitygithub.com

vvs1off/fabric-client-rendering

Minecraft 클라이언트에서 Fabric을 사용하여 HUD 요소, GUI, 텍스트, 아이템 툴팁, 커스텀 화면 등을 그리는 방법을 제공합니다. HUD 오버레이, 화면, 툴팁 또는 프레임 기반 클라이언트 드로잉 코드를 추가하거나 수정할 때 사용하세요.

fabric-client-rendering란 무엇인가요?

이 스킬은 Fabric 기반 Minecraft 모드 개발에서 클라이언트 측 렌더링을 구현하기 위한 포괄적인 가이드입니다. HUD 요소 등록(HudElementRegistry), GUI 그리기(GuiGraphicsExtractor/GuiGraphics/DrawContext), 텍스트 및 컴포넌트, 아이템 툴팁, 커스텀 Screen과 위젯, 렌더 성능 최적화를 다룹니다. 다양한 Minecraft 버전(1.20~26.1+)에 따라 달라지는 API 이름과 구조를 표로 정리하여 개발자가 올바른 API를 사용할 수 있도록 돕습니다. 또한 모든 코드는 클라이언트 전용(src/client/java)이어야 하며, 공통 코드에서 참조하면 서버가 충돌한다는 중요한 주의사항을 포함합니다. 성능 측면에서는 프레임당 할당을 피하고, 게임 상태 작업을 렌더 콜백이 아닌 틱 이벤트에서 수행하도록 강조합니다. 이 스킬은 Fabric API의 HudElementRegistry, HudRenderCallback(구버전), ItemTooltipCallback 등을 실제 예제 코드와 함께 설명하며, 일반적인 실수(예: API 이름 혼동, RGB 색상 문제, 위젯을 init()이 아닌 생성자에서 생성)를 피하도록 안내합니다.

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

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

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 디버그 화면이 열려 있을 때 요소가 겹칠 수 있으면 숨기는 것을 고려하세요 (해당 버전 소스에서 디버그 오버레이 접근자를 확인하세요).
  • 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);   // pipeline, texture, 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.

툴팁

믹스인 없이 아이템 툴팁에 줄을 추가:

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() {   // 열 때마다 AND 크기 조정 시 호출됨 - 여기서 위젯 다시 빌드
        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 및 옵션 화면이 이에 의존합니다).
  • 클라이언트 스레드의 키 입력 또는 틱 핸들러에서 화면을 여세요. 네트워크 핸들러 또는 다른 스레드에서 열려면 client.execute(() -> ...)가 필요합니다.
  • 커스텀 위젯을 작성하기 전에 바닐라 위젯(Button, EditBox, CycleButton, AbstractSliderButton)을 선호하세요.

성능 - 이 코드는 매 프레임 실행됩니다

HUD 및 화면 드로잉 메서드는 초당 60~500+회 실행됩니다. 핫 루프로 취급하세요:

  • 프레임당 할당을 피하세요: 드로잉 경로에서 new 객체, String.format, 문자열 연결, 스트림/람다 생성, 변경되지 않은 값에 대한 Component.literal을 사용하지 마세요. Component를 캐시하고 기본 값이 변경될 때만 다시 빌드하세요 (예: 틱 이벤트에서, 또는 블록 위치가 달라졌을 때).
  • 렌더에서 게임 상태 작업을 수행하지 마세요: 월드 스캔, 엔티티 검색, 레이캐스트, 파일 I/O, 디스크에서 구성 읽기, 레지스트리 조회를 하지 마세요. ClientTickEvents.END_CLIENT_TICK (초당 20회)에서 계산하고 캐시된 결과를 그리세요.
  • 렌더 경로에서 로그를 남기지 마세요.
  • 매 프레임 업로드되는 일회성 텍스처를 피하세요. 텍스처는 한 번만 등록하세요.
  • F3 프레임 시간 그래프 또는 프로파일러(Spark)로 전후를 측정하세요.
// 좋은 예: 최대 한 틱에 한 번, 변경된 경우에만 다시 계산
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 를 읽고 그 구조를 복사하세요. 다른 버전의 월드 렌더링 스니펫을 적용하지 마세요.

일반적인 실수

  • Yarn 프로젝트에서 DrawContext/MatrixStack/TextRenderer를 사용하거나, 26.1+ 프로젝트에서 GuiGraphics를 GuiGraphicsExtractor여야 하는데 사용하는 경우.
  • 26.1+ Screen에서 render(...)를 재정의하는 경우 - 메서드는 extractRenderState입니다. 이전 이름은 @Override도 잊어버리면 컴파일만 되고 실행되지 않습니다. 항상 @Override를 사용하세요.
  • 1.21.6 이후 RGB 색상(0xFFFFFF) -> 텍스트와 채우기가 보이지 않음.
  • 1.21.6+에서 더 이상 사용되지 않는 HudRenderCallback을 사용하거나 간단한 오버레이를 위해 바닐라 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.

관련 스킬