NeoForge 26.1 Docs
  • 문서
  • 노트
NeoForge 26.1
NeoForge 26.1
v26.1
학습 진도
0 / 77 챕터 완료방문 0
마스터 트랙 #3: 기계 모드 프로젝트 셋업분쇄기 블록 등록 — CrusherBlock + facing BlockStateBlockEntity + 인벤토리 — ItemStackHandlerMenu + Screen — GUI 구현화면 위젯 — 진행 바와 슬롯 시각화커스텀 레시피 타입 — Crusher Recipe서버↔클라이언트 동기화 패킷기계 모드 마무리 + JAR 빌드
마스터기계

화면 위젯 — 진행 바와 슬롯 시각화

CrusherScreen의 extractContents에 진행 바를 그리고, 슬롯 호버 강조와 텍스트 렌더링으로 GUI를 완성합니다.

화면 위젯 — 진행 바와 슬롯 시각화

이 챕터에서는 분쇄기 GUI에 시각적 피드백을 추가합니다. 진행 바로 가공 상태를 표시하고, 슬롯 호버 강조와 텍스트 렌더링으로 플레이어가 GUI 상태를 한눈에 파악할 수 있게 만듭니다.

03-menu-screen에서 만든 기본 CrusherScreen은 extractContents에서 배경 텍스처만 그립니다. 이 챕터의 진행 바·호버 강조·텍스트는 그 위에 얹는 확장 예시입니다.


1. 텍스처 좌표 시스템 이해

NeoForge 26.1.2의 GuiGraphicsExtractor.blit은 텍스처 영역을 **정규화 UV(0.0~1.0)**로 지정합니다. 픽셀 좌표가 아니라 픽셀 / 텍스처크기 비율을 넘깁니다.

blit(texture, screenX, screenY, width, height, u0, v0, u1, v1)
         │         │       │       │      │     └─────┬─────┘
         │      화면 좌표           그릴 크기      정규화 UV (0.0~1.0)
         └── Identifier (텍스처 경로)

256×256 텍스처에서 픽셀 (u, v)는 정규화하면 u/256, v/256 입니다.

영역텍스처 픽셀정규화 UV
GUI 배경(0, 0) ~ (176, 166)0.0, 0.0 ~ 1.0, 1.0 (텍스처 전체)
진행 바 (채워진 상태)(176, 14) ~ (200, 30)0.6875, 0.0547 ~ 0.7813, 0.1172

왜 176부터 시작하나?

GUI 배경이 imageWidth = 176을 차지하므로, 진행 바 화살표 텍스처는 같은 시트의 오른쪽(u = 176)에 배치하면 배경과 겹치지 않습니다. (배경 전용 텍스처를 쓴다면 화살표용 시트를 별도로 확장하세요.)


2. extractContents — 진행 바 렌더링

26.1.2에서 배경·위젯을 그리는 메서드는 extractContents(GuiGraphicsExtractor gg, int mx, int my, float partialTick) 입니다(구버전 renderBg가 이 메서드로 바뀌었습니다). 배경 텍스처를 먼저 그린 뒤, progress 값에 비례한 너비로 진행 바를 덧그립니다.

// CrusherScreen.java — 위젯 확장 예시 (기본 extractContents 에 진행 바를 더한 버전)
@Override
public void extractContents(GuiGraphicsExtractor gg, int mx, int my, float partialTick) {
    super.extractContents(gg, mx, my, partialTick);
 
    // 1. GUI 배경 텍스처 전체 출력 (정규화 UV 0.0~1.0)
    gg.blit(TEXTURE, leftPos, topPos, imageWidth, imageHeight, 0.0f, 0.0f, 1.0f, 1.0f);
 
    // 2. 진행 바 (가로 길이를 progress에 비례)
    int progress = menu.blockEntity.getProgress();  // 0 ~ 200
    int maxProgress = 200;
    int barWidth = 24;
    int filledWidth = (progress * barWidth) / maxProgress;
 
    // 채워진 화살표 영역(텍스처 u=176, v=14)을 filledWidth 만큼만 — UV는 256px 기준 정규화
    gg.blit(TEXTURE,
        leftPos + 79, topPos + 35,                   // 화면 좌표 (GUI 중앙 부근)
        filledWidth, 16,                              // 동적 너비 × 고정 높이
        176f / 256f, 14f / 256f,                      // u0, v0 (채워진 화살표 시작)
        (176f + filledWidth) / 256f, 30f / 256f);     // u1, v1
}

좌표 계산 방법

leftPos와 topPos는 AbstractContainerScreen이 자동으로 계산하는 GUI 좌상단 절대 좌표입니다. 진행 바 위치는 항상 이 값에 상대 오프셋을 더해 지정합니다.

화면 절대 좌표 = leftPos + 상대 X
                topPos  + 상대 Y

3. render — 슬롯 호버 강조

render는 extractContents보다 나중에 호출됩니다. 마우스 아래 슬롯을 감지해 반투명 오버레이를 그립니다. 26.1.2에서는 렌더 파라미터 타입도 GuiGraphicsExtractor 입니다.

// CrusherScreen.java — 위젯 확장 예시
@Override
public void render(GuiGraphicsExtractor gg, int mx, int my, float partialTick) {
    renderBackground(gg, mx, my, partialTick);
    super.render(gg, mx, my, partialTick);
    renderTooltip(gg, mx, my);
 
    // 호버 슬롯 강조 overlay
    Slot slot = getSlotUnderMouse();
    if (slot != null && !slot.getItem().isEmpty()) {
        gg.fill(
            slot.x + leftPos,      slot.y + topPos,
            slot.x + leftPos + 16, slot.y + topPos + 16,
            0x80FFFFFF             // ARGB: 알파 50%, 흰색
        );
    }
}

0x80FFFFFF는 ARGB 형식입니다. 앞의 0x80이 알파값(약 50% 투명)이고, 뒤의 FFFFFF가 흰색입니다.

ℹ️

📷 스크린샷 자리 (직접 캡처해 추가하세요)

CrusherScreen — 슬롯 호버 강조 및 툴팁 렌더링


4. drawString — 텍스트 렌더링

진행률을 숫자로 표시하려면 drawString을 사용합니다. (gg는 extractContents/render가 받는 GuiGraphicsExtractor 입니다.)

// extractContents 안에서 호출 (배경 위, 아이템 아래)
int progress = menu.blockEntity.getProgress();
int percent = progress * 100 / 200;
gg.drawString(font, percent + "%", leftPos + 8, topPos + 5, 0x404040);
파라미터설명
fontAbstractContainerScreen이 제공하는 폰트 렌더러
텍스트표시할 문자열
leftPos + 8화면 X 좌표
topPos + 5화면 Y 좌표
0x404040텍스트 색상 (어두운 회색)

5. 안티패턴 — 매 프레임 객체 생성 금지

⚠️ 매 프레임 새 객체 생성 → GC 압박

// 매 렌더마다 String 생성 (60fps × String.format)
gg.drawString(font, "Progress: " + String.format("%.2f", progress / 200.0 * 100) + "%", ...);

60fps 기준으로 초당 60번 String.format이 호출되고, 매번 새 String 객체가 힙에 쌓입니다. GC가 자주 발동하면 프레임 드롭이 생깁니다.

// progress 변경 시에만 갱신 + 캐시
private String progressLabel = "";
private int lastProgress = -1;
 
// extractContents 또는 tick에서
if (lastProgress != progress) {
    progressLabel = (progress * 100 / 200) + "%";
    lastProgress = progress;
}
gg.drawString(font, progressLabel, leftPos + 8, topPos + 5, 0x404040);

progress가 바뀔 때만 문자열을 새로 만들고, 나머지 프레임에서는 캐시된 값을 재사용합니다.


6. 렌더 호출 순서

Minecraft GUI 렌더링은 레이어 순서가 중요합니다.

render()
  ├── renderBackground()       ← 어두운 배경 오버레이
  ├── super.render()
  │     ├── extractContents()  ← 텍스처 + 진행 바 (이 챕터)
  │     └── 슬롯 아이템 렌더링
  ├── 슬롯 호버 강조            ← 아이템 위에 오버레이 (이 챕터)
  └── renderTooltip()          ← 툴팁 (최상단)

extractContents에서 그린 진행 바는 슬롯 아이템보다 아래에 위치합니다. 슬롯 강조 오버레이는 super.render() 이후에 그려야 아이템 위에 올라옵니다.


7. 기본 CrusherScreen (검증된 소스)

다음은 03-menu-screen에서 만든 실제 빌드되는 기본 화면입니다. 위의 진행 바·호버 강조·텍스트는 이 extractContents 위에 얹는 확장입니다.

// examplemod-master-projects/machine/src/main/java/com/example/master/machine/client/CrusherScreen.java
package com.example.master.machine.client;
 
import com.example.master.machine.menu.CrusherMenu;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.world.entity.player.Inventory;
 
public class CrusherScreen extends AbstractContainerScreen<CrusherMenu> {
 
    private static final Identifier TEXTURE =
            Identifier.fromNamespaceAndPath("master_machine", "textures/gui/crusher.png");
 
    public CrusherScreen(CrusherMenu menu, Inventory inv, Component title) {
        super(menu, inv, title, 176, 166);
    }
 
    @Override
    public void extractContents(GuiGraphicsExtractor gg, int mx, int my, float partialTick) {
        super.extractContents(gg, mx, my, partialTick);
        gg.blit(TEXTURE, leftPos, topPos, imageWidth, imageHeight, 0.0f, 0.0f, 1.0f, 1.0f);
    }
}

위젯(진행 바·호버 강조·텍스트)을 더하려면 2~4절의 코드를 이 extractContents/render에 통합하고, Slot import(net.minecraft.world.inventory.Slot)와 진행률 캐시 필드를 추가합니다.


전체 파일 구조

이번 챕터 완료 후 machine 프로젝트의 주요 파일 구조입니다.

examplemod-master-projects/machine/src/main/java/com/example/master/machine/
├── MasterMachineMod.java
├── block/
│   ├── CrusherBlock.java
│   └── CrusherBlockEntity.java
├── menu/
│   └── CrusherMenu.java          ← 03-menu-screen에서 구현
└── client/
    └── CrusherScreen.java        ← 이번 챕터에서 완성

다음 단계

  1. 05-recipe-type — 커스텀 RecipeType으로 분쇄 레시피 데이터팩 지원
  2. 06-server-tick — ServerLevel.getBlockEntity() + tick으로 실제 가공 로직 완성

Menu + Screen — GUI 구현

CrusherMenu로 컨테이너 슬롯을 선언하고 CrusherScreen으로 GUI 텍스처를 렌더링합니다. MENU_TYPES DeferredRegister와 클라이언트 사이드 Screen 등록까지 완성합니다.

커스텀 레시피 타입 — Crusher Recipe

MapCodec + StreamCodec으로 CrusherRecipe 커스텀 레시피 타입을 설계하고, RecipeSerializer·RecipeType을 DeferredRegister로 등록하여 데이터팩 JSON에서 레시피를 정의합니다.

On this page

화면 위젯 — 진행 바와 슬롯 시각화1. 텍스처 좌표 시스템 이해2. extractContents — 진행 바 렌더링좌표 계산 방법3. render — 슬롯 호버 강조4. drawString — 텍스트 렌더링5. 안티패턴 — 매 프레임 객체 생성 금지6. 렌더 호출 순서7. 기본 CrusherScreen (검증된 소스)전체 파일 구조다음 단계
NeoForge 26.1 Docs

NeoForge 26.1 모딩 개발 문서 사이트

GitHubDiscord

문서

  • 문서
  • 노트

GitHub

  • GitHub
  • Discord

© 2026 NeoForge 26.1 Docs. 콘텐츠는 MIT 라이선스로 제공됩니다.

Built with Next.js · Tailwind CSS · shadcn/ui