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 / ValueOutput | NeoForge 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 자동 투입 테스트
/give @p master_machine:crusher로 분쇄기 블록 획득- 분쇄기를 지면에 설치
- 분쇄기 위에 Hopper를 설치
- Hopper 인벤토리에 아이템(예: 조약돌)을 넣음
- 분쇄기 슬롯 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 직렬화 (신규)
다음 단계
- 03-menu-screen —
AbstractContainerMenu+Screen으로 분쇄기 GUI 구현 - 04-recipe-type — 커스텀
RecipeType으로 분쇄 레시피 데이터팩 지원 - 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 등록까지 완성합니다.