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