블록 엔티티와 메뉴
블록 정의에 blockEntity(...)를 붙이면 블록마다 저장 데이터, 매 틱 실행되는 코드, 인벤토리를 둘 수 있습니다.
데이터와 틱
BlockDefinition.builder().blockEntity(BlockEntityDefinition.builder()
.inventory(3, "container.my_mod.crate") // 27칸, 사용하면 상자 메뉴가 열림
.ticker(entity -> entity.data().putInt("ticks", entity.data().getInt("ticks", 0) + 1))
.syncToClients()
.build());
world.blockEntityAt(pos).flatMap(BlockEntityView::inventory).ifPresent(inv -> inv.add(item, 10));
- 데이터:
entity.data()는DataTree입니다. 정수, long, 실수, 문자열, 참/거짓, 바이트 배열, 하위 트리(child)를 담을 수 있고, 월드와 함께 저장됩니다. 값을 바꾼 뒤에는entity.markChanged()를 호출하세요. - 틱:
ticker는 서버에서 매 틱 실행됩니다. - 클라이언트 동기화:
syncToClients()를 켜면markChanged()를 호출할 때 데이터가 클라이언트로 전송됩니다. 블록 엔티티 그리기에서 이 데이터가 필요하면 켜 두세요. BlockEntityView:world(),pos(),data(),inventory(),markChanged()
상자형 인벤토리
inventory(rows, title)은 게임의 상자 메뉴(1–6줄)를 사용합니다.
- 게임의 컨테이너로 동작하므로 깔때기와 비교기가 작동하고, 블록을 부수면 내용물이 떨어집니다.
InventoryView:size,get(slot),set(slot, item, count),add(item, count)(넣지 못한 개수 반환),count(item),stackAt(slot),clear
칸 배치 메뉴와 진행 막대
상자 모양이 아닌 메뉴는 MenuLayout으로 칸을 직접 배치합니다.
BlockDefinition.builder()
.blockEntity(BlockEntityDefinition.builder()
.menu(MenuLayout.builder(176, 166)
.slot(56, 35).outputSlot(116, 35) // 넣는 칸, 꺼내기만 하는 칸
.progressBar(79, 34, MenuLayout.ProgressStyle.ARROW, "progress", "total")
.build(), "container.my_mod.grinder")
.ticker(entity -> { /* 매 틱: entity.data(), entity.inventory() */ })
.syncToClients()
.build())
.build();
MenuLayout.Builder | 뜻 |
|---|---|
slot(x, y) / outputSlot(x, y) | 넣는 칸 / 꺼내기만 하는 칸 |
grid(x, y, columns, rows) | 칸 격자 |
playerInventory(x, y) | 플레이어 인벤토리 위치 |
background(texture) | 배경 텍스처 |
progressBar(x, y, style, valueKey, maxKey) | 진행 막대(ARROW, FLAME) |
- 인벤토리 크기는 레이아웃의 칸 수와 같고, 블록 ID와 같은 이름으로 메뉴 타입이 등록됩니다.
- 배경 텍스처를 지정하지 않으면 Poly 모드 로더가 게임과 비슷한 회색 패널과 칸 테두리를 그립니다.
- 진행 막대는 블록 엔티티 데이터의 두 정수 키(값, 최대값)로 채워집니다. 이 값은 메뉴가 열려 있는 동안 클라이언트로 전달되며, 범위는
-32768..32767입니다. - 상자 행(
inventory(rows, …))과는 함께 쓸 수 없습니다.
메뉴 열기
PlayerView.openMenu(pos)는 모드 메뉴와 게임 메뉴(화로 등)를 모두 열 수 있습니다. 서버 스레드에서 호출하세요.
player.openMenu(pos);
예: 분쇄기 틱
examples/native-showcase-mod의 분쇄기는 40틱마다 조약돌 하나를 자갈로 바꿉니다.
private static void grind(BlockEntityView entity) {
InventoryView inventory = entity.inventory().orElseThrow();
DataTree data = entity.data();
ItemStackView input = inventory.get(0);
ItemStackView output = inventory.get(1);
ResourceId gravel = ResourceId.minecraft("gravel");
boolean canGrind = input.item().equals(ResourceId.minecraft("cobblestone"))
&& (output.isEmpty() || (output.item().equals(gravel) && output.count() < 64));
if (!canGrind) {
return;
}
int progress = data.getInt("progress", 0) + 1;
if (progress >= 40) {
inventory.set(0, input.item(), input.count() - 1);
inventory.set(1, gravel, output.count() + 1);
progress = 0;
}
data.putInt("progress", progress).putInt("total", 40);
entity.markChanged();
}
데이터 버전
블록 엔티티 데이터의 형식을 바꿨다면 dataVersion(version, migration)으로 옛 데이터를 변환하세요. 자세한 내용은 저장 데이터와 버전을 참고하세요.