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

BlockEntity + 인벤토리 — ItemStackHandler

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

BlockEntity + 인벤토리 — ItemStackHandler

이 챕터에서는 분쇄기 블록에 상태 저장 기능을 추가합니다. NeoForge의 BlockEntity와 ItemStackHandler를 사용해 2슬롯 인벤토리(입력·출력)와 가공 진행률을 저장하고, Capability로 Hopper 자동 투입을 지원합니다.


1. CrusherBlockEntity 구현

BlockEntity를 상속하고 ItemStackHandler로 2슬롯(0: 입력, 1: 출력)을 선언합니다. 슬롯 변경이 발생하면 onContentsChanged에서 setChanged()를 호출해 청크 저장 플래그를 세웁니다.

// examplemod-master-projects/machine/src/main/java/com/example/master/machine/block/CrusherBlockEntity.java
package com.example.master.machine.block;
 
import com.example.master.machine.MasterMachineMod;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.world.level.storage.ValueOutput;
import net.neoforged.neoforge.items.IItemHandler;
import net.neoforged.neoforge.items.ItemStackHandler;
 
public class CrusherBlockEntity extends BlockEntity {
 
    /** 2슬롯 인벤토리: slot 0 = 입력, slot 1 = 출력 */
    private final ItemStackHandler inventory = new ItemStackHandler(2) {
        @Override
        protected void onContentsChanged(int slot) {
            // 내용이 바뀔 때마다 청크에 변경 사항 저장 요청
            setChanged();
        }
    };
 
    /** 가공 진행도 (0 ~ MAX_PROGRESS) */
    private int progress = 0;
 
    public CrusherBlockEntity(BlockPos pos, BlockState state) {
        super(MasterMachineMod.CRUSHER_BE.get(), pos, state);
    }
 
    // ── Capability용 공개 접근자 ──────────────────────────────────────
    public IItemHandler getInventory() {
        return inventory;
    }
 
    public int getProgress() {
        return progress;
    }
 
    // ── NBT 직렬화 ───────────────────────────────────────────────────
 
    @Override
    protected void loadAdditional(ValueInput input) {
        super.loadAdditional(input);
        inventory.deserialize(input.childOrEmpty("inventory"));
        progress = input.getIntOr("progress", 0);
    }
 
    @Override
    protected void saveAdditional(ValueOutput output) {
        super.saveAdditional(output);
        inventory.serialize(output.child("inventory"));
        output.putInt("progress", progress);
    }
}

주요 포인트

요소역할
ItemStackHandler(2)NeoForge 제공 인벤토리 구현체 — NBT 직렬화 내장
onContentsChanged슬롯이 변할 때마다 setChanged() → 청크 저장 보장
loadAdditional / saveAdditional블록이 로드·저장될 때 인벤토리와 진행도를 복구/기록
ValueInput / ValueOutputNeoForge 26.1.2에서 도입된 직렬화 추상화 — input.getIntOr("k", 기본값) / output.putInt("k", 값), 하위 데이터는 input.childOrEmpty(...) · output.child(...)

2. CrusherBlock에 EntityBlock 구현

CrusherBlock이 EntityBlock 인터페이스를 구현하면 Minecraft가 블록 자리에 CrusherBlockEntity를 자동으로 생성합니다.

// examplemod-master-projects/machine/src/main/java/com/example/master/machine/block/CrusherBlock.java
// (EntityBlock import 추가, newBlockEntity 메서드 추가)
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
 
public class CrusherBlock extends Block implements EntityBlock {
 
    // ... 기존 FACING 코드 유지 ...
 
    @Override
    public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
        return new CrusherBlockEntity(pos, state);
    }
}

왜 EntityBlock인가?

Minecraft는 블록 자체에 데이터를 저장하지 않습니다. EntityBlock을 구현하면 블록 위치마다 별도의 BlockEntity 인스턴스를 갖게 되어 인벤토리·에너지·진행도 같은 동적 데이터를 저장할 수 있습니다.


3. BLOCK_ENTITY_TYPES 등록

BlockEntityType은 NeoForge DeferredRegister로 등록합니다. new BlockEntityType<>(factory, blocks) 생성자에 생성 팩토리와 이 BlockEntity를 보유할 수 있는 블록을 지정합니다.

// examplemod-master-projects/machine/src/main/java/com/example/master/machine/MasterMachineMod.java
import net.minecraft.core.registries.Registries;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.neoforged.neoforge.registries.DeferredRegister;
import java.util.function.Supplier;
 
// 추가: BLOCK_ENTITY_TYPES 레지스터
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITY_TYPES =
        DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, MOD_ID);
 
// CRUSHER BlockEntity 등록
public static final Supplier<BlockEntityType<CrusherBlockEntity>> CRUSHER_BE =
        BLOCK_ENTITY_TYPES.register("crusher",
                () -> new BlockEntityType<>(CrusherBlockEntity::new, CRUSHER.get()));

생성자에서 BLOCK_ENTITY_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);  // ← 이 줄 추가
}

4. Capability 등록 — Hopper 자동 투입

NeoForge Capability 시스템에 Capabilities.ItemHandler.BLOCK을 등록하면 Hopper가 분쇄기의 인벤토리를 자동으로 인식합니다.

// MasterMachineMod.java — @EventBusSubscriber + @SubscribeEvent 추가
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent;
 
@EventBusSubscriber(modid = MasterMachineMod.MOD_ID)
public class MasterMachineMod {
 
    // ... 기존 코드 ...
 
    @SubscribeEvent
    public static void registerCapabilities(RegisterCapabilitiesEvent event) {
        event.registerBlockEntity(
                Capabilities.ItemHandler.BLOCK,       // Capability 종류
                MasterMachineMod.CRUSHER_BE.get(),    // 대상 BlockEntity 타입
                (be, side) -> be.getInventory()       // 인벤토리 반환 람다
        );
    }
}

5. 안티패턴 — 직접 ItemStack 배열 사용 금지

⚠️ 직접 ItemStack 배열 사용 금지

// ❌ 직접 ItemStack[] 관리 → Capability 시스템 활용 불가
private ItemStack[] items = new ItemStack[2];
 
// ✅ ItemStackHandler (NeoForge) 사용
private final ItemStackHandler inventory = new ItemStackHandler(2);

ItemStackHandler는 NBT 직렬화 + Hopper 호환 + Capability 자동 처리를 한 번에 제공합니다. 직접 배열을 사용하면 이 모든 기능을 직접 구현해야 합니다.


6. 인게임 검증 안내

이번 챕터에서는 GUI를 구현하지 않습니다. 인벤토리와 Capability가 올바르게 등록됐는지 확인하는 방법은 다음과 같습니다.

Hopper 자동 투입 테스트

  1. /give @p master_machine:crusher 로 분쇄기 블록 획득
  2. 분쇄기를 지면에 설치
  3. 분쇄기 위에 Hopper를 설치
  4. Hopper 인벤토리에 아이템(예: 조약돌)을 넣음
  5. 분쇄기 슬롯 0(입력)으로 아이템이 자동 이동되면 Capability 등록 성공

참고: 실제 GUI와 가공 로직은 03-menu-screen 챕터에서 구현합니다. 현재 단계에서는 Hopper 투입 경로만 확인합니다.


전체 파일 구조

이번 챕터 완료 후 machine 프로젝트의 주요 파일 구조는 다음과 같습니다.

examplemod-master-projects/machine/src/main/java/com/example/master/machine/
├── MasterMachineMod.java          ← BLOCK_ENTITY_TYPES 추가, registerCapabilities
└── block/
    ├── CrusherBlock.java          ← EntityBlock 구현, newBlockEntity 추가
    └── CrusherBlockEntity.java    ← ItemStackHandler + NBT 직렬화 (신규)

다음 단계

  1. 03-menu-screen — AbstractContainerMenu + Screen으로 분쇄기 GUI 구현
  2. 04-recipe-type — 커스텀 RecipeType으로 분쇄 레시피 데이터팩 지원
  3. 05-server-tick — ServerLevel.getBlockEntity() + tick으로 가공 로직 완성

분쇄기 블록 등록 — CrusherBlock + facing BlockState

DeferredRegister.Blocks/Items로 CrusherBlock을 등록하고, HorizontalDirectionalBlock.FACING 속성으로 플레이어가 바라보는 방향 반대로 블록이 설치되는 로직을 구현합니다.

Menu + Screen — GUI 구현

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

On this page

BlockEntity + 인벤토리 — ItemStackHandler1. CrusherBlockEntity 구현주요 포인트2. CrusherBlock에 EntityBlock 구현3. BLOCK_ENTITY_TYPES 등록4. Capability 등록 — Hopper 자동 투입5. 안티패턴 — 직접 ItemStack 배열 사용 금지6. 인게임 검증 안내Hopper 자동 투입 테스트전체 파일 구조다음 단계
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