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 빌드
마스터기계

Menu + Screen — GUI 구현

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

Menu + Screen — GUI 구현

이 챕터에서는 분쇄기 블록에 인터랙티브 GUI를 붙입니다. NeoForge의 AbstractContainerMenu와 AbstractContainerScreen을 사용해 서버-클라이언트 분리 구조를 올바르게 구성하고, 플레이어가 분쇄기를 우클릭했을 때 인벤토리 창이 열리도록 합니다.


1. MENU_TYPES DeferredRegister 선언

MasterMachineMod에 MenuType 전용 DeferredRegister를 추가합니다. BuiltInRegistries.MENU.key()를 사용해 레지스트리 키를 지정하고, IMenuTypeExtension.create로 클라이언트 사이드에서 BlockPos를 읽어 CrusherMenu를 생성하는 팩토리를 등록합니다.

// examplemod-master-projects/machine/src/main/java/com/example/master/machine/MasterMachineMod.java
public static final DeferredRegister<MenuType<?>> MENU_TYPES =
        DeferredRegister.create(BuiltInRegistries.MENU.key(), MOD_ID);
 
public static final Supplier<MenuType<CrusherMenu>> CRUSHER_MENU = MENU_TYPES.register(
        "crusher",
        () -> IMenuTypeExtension.create((id, inv, buf) -> {
            BlockPos pos = buf.readBlockPos();
            BlockEntity be = inv.player.level().getBlockEntity(pos);
            return new CrusherMenu(id, inv, (CrusherBlockEntity) be);
        })
);

생성자에서 MENU_TYPES.register(modEventBus)를 호출해야 합니다.

public MasterMachineMod(IEventBus modEventBus, ModContainer container) {
    LOGGER.info("Master Machine Mod 로드");
    BLOCKS.register(modEventBus);
    ITEMS.register(modEventBus);
    BLOCK_ENTITY_TYPES.register(modEventBus);
    MENU_TYPES.register(modEventBus); // 추가
}

2. CrusherMenu 구현

AbstractContainerMenu를 상속해 슬롯 레이아웃을 선언합니다. 슬롯은 두 종류입니다:

  • 머신 슬롯: NeoForge의 SlotItemHandler로 ItemStackHandler에 직접 연결
  • 플레이어 슬롯: 바닐라 Slot으로 플레이어 인벤토리 연결
// examplemod-master-projects/machine/src/main/java/com/example/master/machine/menu/CrusherMenu.java
package com.example.master.machine.menu;
 
import com.example.master.machine.MasterMachineMod;
import com.example.master.machine.block.CrusherBlockEntity;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import net.neoforged.neoforge.items.SlotItemHandler;
 
public class CrusherMenu extends AbstractContainerMenu {
 
    public final CrusherBlockEntity blockEntity;
 
    public CrusherMenu(int id, Inventory playerInv, CrusherBlockEntity be) {
        super(MasterMachineMod.CRUSHER_MENU.get(), id);
        this.blockEntity = be;
 
        // 머신 슬롯 (input/output)
        this.addSlot(new SlotItemHandler(be.getInventory(), 0, 56, 35));
        this.addSlot(new SlotItemHandler(be.getInventory(), 1, 116, 35));
 
        // 플레이어 인벤토리 슬롯 (9×3)
        for (int row = 0; row < 3; row++)
            for (int col = 0; col < 9; col++)
                addSlot(new Slot(playerInv, col + row * 9 + 9, 8 + col * 18, 84 + row * 18));
 
        // 플레이어 핫바 (1×9)
        for (int col = 0; col < 9; col++)
            addSlot(new Slot(playerInv, col, 8 + col * 18, 142));
    }
 
    @Override
    public ItemStack quickMoveStack(Player player, int index) {
        // Shift+Click 슬롯 이동 로직 (생략 가능)
        return ItemStack.EMPTY;
    }
 
    @Override
    public boolean stillValid(Player player) {
        return true;
    }
}

슬롯 좌표 기준: 텍스처 이미지(176×166) 기준 픽셀 좌표입니다. 입력 슬롯은 (56, 35), 출력 슬롯은 (116, 35)에 위치합니다.


3. CrusherScreen (클라이언트 사이드)

AbstractContainerScreen을 상속해 GUI 텍스처를 렌더링합니다. extractContents에서 GuiGraphicsExtractor.blit으로 배경 텍스처를 그립니다.

ℹ️

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

CrusherScreen — GUI 텍스처 렌더링

// 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);
    }
}

텍스처 파일은 src/main/resources/assets/master_machine/textures/gui/crusher.png에 위치해야 합니다 (176×166 PNG).


4. 클라이언트 사이드 Screen 등록

CrusherScreen은 클라이언트 전용 클래스입니다. @EventBusSubscriber(value = Dist.CLIENT)로 서버 사이드 로딩을 방지하고, RegisterMenuScreensEvent에서 Screen을 등록합니다.

// examplemod-master-projects/machine/src/main/java/com/example/master/machine/client/ClientSetup.java
package com.example.master.machine.client;
 
import com.example.master.machine.MasterMachineMod;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent;
 
@EventBusSubscriber(modid = MasterMachineMod.MOD_ID, value = Dist.CLIENT)
public class ClientSetup {
 
    @SubscribeEvent
    public static void registerScreens(RegisterMenuScreensEvent event) {
        event.register(MasterMachineMod.CRUSHER_MENU.get(), CrusherScreen::new);
    }
}

⚠️ Screen 사이드 분리 누락 CrusherScreen은 @OnlyIn(Dist.CLIENT) 또는 클라이언트 setup에서만 등록해야 합니다. 서버 사이드에서 Screen 클래스를 참조하면 NoClassDefFoundError가 발생합니다.


5. CrusherBlock — openMenu 호출

CrusherBlock의 useItemOn을 오버라이드해서 우클릭 시 분쇄기 GUI를 엽니다. 서버 사이드에서만 실행하고, BlockPos를 buf.writeBlockPos(pos)로 직렬화해 클라이언트 MenuType 팩토리로 전달합니다.

// examplemod-master-projects/machine/src/main/java/com/example/master/machine/block/CrusherBlock.java
@Override
protected InteractionResult useItemOn(ItemStack stack, BlockState state, Level level,
                                      BlockPos pos, Player player, InteractionHand hand,
                                      BlockHitResult hitResult) {
    if (!level.isClientSide() && level.getBlockEntity(pos) instanceof CrusherBlockEntity be) {
        player.openMenu(new SimpleMenuProvider(
                (id, inv, p) -> new CrusherMenu(id, inv, be),
                Component.translatable("container.master_machine.crusher")),
                buf -> buf.writeBlockPos(pos));
    }
    return InteractionResult.SUCCESS;
}

Component.translatable("container.master_machine.crusher")는 lang/en_us.json의 번역 키와 일치해야 합니다:

{
  "block.master_machine.crusher": "Crusher",
  "container.master_machine.crusher": "Crusher"
}

6. 동작 흐름 요약

다이어그램 렌더링 중…

정리

클래스사이드역할
CrusherMenu서버 + 클라이언트슬롯 레이아웃, 아이템 이동 로직
CrusherScreen클라이언트 전용GUI 텍스처 렌더링
ClientSetup클라이언트 전용Screen 등록 (RegisterMenuScreensEvent)
MasterMachineMod.MENU_TYPES서버 + 클라이언트MenuType 등록, 클라이언트 팩토리
CrusherBlock.useItemOn서버openMenu 트리거

BlockEntity + 인벤토리 — ItemStackHandler

CrusherBlockEntity에 ItemStackHandler로 2슬롯 인벤토리를 구현하고, NBT 직렬화와 Capability 등록으로 Hopper 자동 투입을 지원합니다.

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

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

On this page

Menu + Screen — GUI 구현1. MENU_TYPES DeferredRegister 선언2. CrusherMenu 구현3. CrusherScreen (클라이언트 사이드)4. 클라이언트 사이드 Screen 등록5. CrusherBlock — openMenu 호출6. 동작 흐름 요약정리
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