NeoForge 26.1 Docs
  • 문서
  • 노트
NeoForge 26.1
NeoForge 26.1
v26.1
학습 진도
0 / 77 챕터 완료방문 0
BlockEntity 기초 — CounterBlockEntityBlockEntityRenderer 구현Entity 등록과 AttributeSupplierEntityRenderer와 EntityModelAI Goal 시스템CustomPacketPayload — 클라이언트/서버 패킷 통신AttachmentTypes — 엔티티·블록에 데이터 부착WorldGen Feature 기초 — 광물 생성
고급 시스템

BlockEntity 기초 — CounterBlockEntity

DeferredRegister로 BlockEntityType을 등록하고, BlockEntity 클래스에서 NBT 직렬화로 데이터를 저장·복원하는 전체 과정을 학습합니다.

BlockEntity 기초 — CounterBlockEntity

이 챕터에서는 블록에 데이터를 저장하는 BlockEntity를 처음으로 만들어 봅니다. 블록 자체(Block)는 상태(BlockState)만 저장할 수 있고, 임의의 데이터를 유지하려면 반드시 BlockEntity가 필요합니다. 카운터 값을 NBT로 저장하는 CounterBlockEntity를 예제로, 등록부터 직렬화까지 전체 흐름을 익힙니다.


1. BLOCK_ENTITY_TYPES DeferredRegister

BlockEntityType도 아이템·블록과 동일하게 DeferredRegister로 등록합니다. 레지스트리 키는 Registries.BLOCK_ENTITY_TYPE입니다.

// examplemod-template-26.1.2/src/main/java/com/example/examplemod/ExampleMod.java
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITY_TYPES =
    DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, "examplemod");

그리고 생성자에서 이벤트 버스에 등록합니다.

// ExampleMod 생성자
BLOCKS.register(modEventBus);
BLOCK_ENTITY_TYPES.register(modEventBus);
ITEMS.register(modEventBus);

2. BlockEntityType 등록

NeoForge 26.1.2에서는 BlockEntityType 생성자에 두 가지 정보를 직접 전달합니다. (이전의 BlockEntityType.Builder.of(...).build(null) 대신.)

인수설명
CounterBlockEntity::newBlockEntity 팩토리 (pos, state를 받아 인스턴스 생성)
COUNTER_BLOCK.get()이 BlockEntityType이 결합될 블록 목록
// examplemod-template-26.1.2/src/main/java/com/example/examplemod/ExampleMod.java
public static final Supplier<BlockEntityType<CounterBlockEntity>> COUNTER_BE =
    BLOCK_ENTITY_TYPES.register("counter",
        () -> new BlockEntityType<>(CounterBlockEntity::new, COUNTER_BLOCK.get()));
  • 26.1.2 이전에는 BlockEntityType.Builder.of(CounterBlockEntity::new, COUNTER_BLOCK.get()).build(null) 처럼 빌더를 사용했지만, 이제는 생성자에 팩토리와 블록을 직접 넘깁니다.
  • COUNTER_BLOCK.get()은 아직 초기화되지 않은 상태여도 람다 안이기 때문에 안전합니다. (실행은 RegisterEvent 시점)

3. CounterBlockEntity 클래스

3-1. 클래스 선언과 생성자

// examplemod-template-26.1.2/src/main/java/com/example/examplemod/blockentity/CounterBlockEntity.java
public class CounterBlockEntity extends BlockEntity {
 
    private int count = 0;
 
    public CounterBlockEntity(BlockPos pos, BlockState state) {
        super(ExampleMod.COUNTER_BE.get(), pos, state);
    }
  • extends BlockEntity — 모든 BlockEntity의 부모 클래스.
  • 생성자 인수 (BlockPos, BlockState) 는 BlockEntityType.Builder.of(팩토리, ...) 팩토리 시그니처와 일치해야 합니다.
  • super(COUNTER_BE.get(), pos, state) — 자신이 어떤 BlockEntityType인지 부모에 알립니다.

3-2. NBT 직렬화

게임 종료·청크 언로드 시 BlockEntity의 데이터를 NBT로 저장해야 세계 재진입 후에도 데이터가 유지됩니다.

    // 26.1.2: loadAdditional(CompoundTag, HolderLookup.Provider) 삭제 → ValueInput 기반
    @Override
    protected void loadAdditional(ValueInput input) {
        super.loadAdditional(input);
        // 저장된 값이 없으면 기본값 0 사용
        count = input.getIntOr("count", 0);
    }
 
    // 26.1.2: saveAdditional(CompoundTag, HolderLookup.Provider) 삭제 → ValueOutput 기반
    @Override
    protected void saveAdditional(ValueOutput output) {
        super.saveAdditional(output);
        output.putInt("count", count);
    }
메서드호출 시점역할
saveAdditional청크 저장·게임 종료필드 → ValueOutput 기록
loadAdditional청크 로드·세계 진입ValueInput → 필드 복원

NeoForge 26.1.2에서는 직렬화 시그니처가 바뀌었습니다. 26.1.2 이전의 loadAdditional(CompoundTag, HolderLookup.Provider) / saveAdditional(CompoundTag, HolderLookup.Provider)는 삭제되고, 추상화된 ValueInput / ValueOutput 기반 시그니처를 사용합니다. 값을 읽을 때는 input.getIntOr("count", 0)처럼 기본값을 함께 지정해 저장된 값이 없을 때도 안전하게 처리합니다. (net.minecraft.world.level.storage.ValueInput / ValueOutput 임포트가 필요합니다.)

3-3. 데이터 변경 알림

    public void increment() {
        count++;
        setChanged();
    }

setChanged()를 호출하면 이 청크가 "수정됨"으로 표시되어 다음 저장 사이클에 saveAdditional이 실행됩니다. 데이터를 바꾼 뒤 setChanged()를 빠뜨리면 서버 재시작 시 값이 초기화됩니다.


4. CounterBlock — BaseEntityBlock 구현

블록에 BlockEntity를 결합하려면 블록 클래스가 BaseEntityBlock을 상속하거나 EntityBlock 인터페이스를 구현해야 합니다. NeoForge 26에서는 BaseEntityBlock 상속을 권장합니다.

// examplemod-template-26.1.2/src/main/java/com/example/examplemod/block/CounterBlock.java
public class CounterBlock extends BaseEntityBlock {
 
    // 26.1.2: BaseEntityBlock 은 abstract codec() 구현을 요구한다.
    // Block.simpleCodec(생성자) 로 Properties 기반 MapCodec 을 만든다.
    public static final MapCodec<CounterBlock> CODEC = simpleCodec(CounterBlock::new);
 
    public CounterBlock(BlockBehaviour.Properties props) {
        super(props);
    }
 
    @Override
    protected MapCodec<? extends BaseEntityBlock> codec() {
        return CODEC;
    }
 
    @Override
    public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
        return new CounterBlockEntity(pos, state);
    }
}

newBlockEntity 메서드는 블록이 세계에 놓일 때 호출되어 해당 위치의 BlockEntity 인스턴스를 생성합니다. 반드시 @Override로 구현해야 합니다. 또한 BaseEntityBlock은 추상 codec() 구현을 요구하므로, Block.simpleCodec(CounterBlock::new)로 Properties 기반 MapCodec을 만들어 반환합니다.


5. 안티패턴 — EntityBlock 누락

⚠️ EntityBlock / BaseEntityBlock 구현 누락

// ❌ EntityBlock 구현 안 함 → BlockEntity 안 만들어짐
public class CounterBlock extends Block {
    // newBlockEntity 없음 → BlockEntityType.Builder.of(...) 가 호출 안 됨
}
 
// ✅ BaseEntityBlock 상속
public class CounterBlock extends BaseEntityBlock {
    @Override
    public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
        return new CounterBlockEntity(pos, state);
    }
}

블록 클래스에 extends BaseEntityBlock 또는 implements EntityBlock이 없으면 블록을 설치해도 BlockEntity 인스턴스가 생성되지 않습니다. COUNTER_BE 등록 자체는 성공해도 실제 BlockEntity가 없으므로 데이터가 저장·복원되지 않습니다.


6. ExampleMod.java — COUNTER_BLOCK 등록

CounterBlock도 BLOCKS에 등록해야 합니다.

// examplemod-template-26.1.2/src/main/java/com/example/examplemod/ExampleMod.java
public static final DeferredBlock<CounterBlock> COUNTER_BLOCK = BLOCKS.registerBlock("counter",
    CounterBlock::new,
    p -> p.strength(1.5f));

registerBlock(name, 생성자, Properties 변형)은 NeoForge 26.1.2 패턴입니다. 내부적으로 Properties.setId(ResourceKey)를 호출해 블록 id를 설정하므로, 람다 밖에서 new CounterBlock(BlockBehaviour.Properties.of()...)처럼 직접 생성하면 "id not set" 크래시가 발생합니다.

COUNTER_BE에서 COUNTER_BLOCK.get()을 참조하므로, 선언 순서는 COUNTER_BLOCK → COUNTER_BE 여야 합니다. Java 정적 필드는 선언 순서대로 초기화되기 때문입니다.


7. 인게임 검증

7-1. 블록 설치 후 NBT 확인

게임 클라이언트를 실행한 뒤 counter 블록을 설치하고, 우클릭으로 카운터를 증가시킵니다. 그런 다음 명령어로 NBT를 확인합니다.

/data get block ~ ~ ~

카운터 블록 위에서 실행하면 아래와 유사한 결과가 나타납니다.

The block at X, Y, Z has the following block entity data:
{count: 3, id: "examplemod:counter", ...}

7-2. 저장·재진입 후 값 보존 확인

  1. 카운터를 원하는 횟수만큼 증가시킵니다.
  2. /save-all 명령 또는 세계 종료로 저장합니다.
  3. 세계를 다시 열고 counter 블록 위에서 /data get block ~ ~ ~ 를 실행합니다.
  4. count 값이 이전과 동일하게 유지되면 NBT 직렬화가 정상입니다.

요약

단계파일핵심 내용
1ExampleMod.javaBLOCK_ENTITY_TYPES DeferredRegister 선언 + 생성자 등록
2ExampleMod.javaCOUNTER_BLOCK = BLOCKS.register("counter", ...)
3ExampleMod.javaCOUNTER_BE = BLOCK_ENTITY_TYPES.register("counter", ...)
4CounterBlockEntity.javaextends BlockEntity + loadAdditional / saveAdditional
5CounterBlock.javaextends BaseEntityBlock + newBlockEntity 구현

다음 챕터에서는 BlockEntityRenderer를 활용해 CounterBlockEntity의 값을 인게임 3D 공간에 렌더링하는 방법을 학습합니다.

파티클 등록과 스폰 — 시각 효과 직접 만들기

DeferredRegister로 커스텀 파티클 타입을 등록하고, 파티클 JSON과 텍스처를 구성한 뒤 서버에서 모든 클라이언트로 파티클을 전파하는 방법을 배웁니다.

BlockEntityRenderer 구현

NeoForge 26 Vulkan 백엔드 기준 BlockEntityRenderer를 작성해 CounterBlockEntity 위에 회전하는 아이콘을 렌더링합니다. PoseStack 변환·partialTick 보간·안전한 MultiBufferSource 사용법을 다룹니다.

On this page

BlockEntity 기초 — CounterBlockEntity1. BLOCK_ENTITY_TYPES DeferredRegister2. BlockEntityType 등록3. CounterBlockEntity 클래스3-1. 클래스 선언과 생성자3-2. NBT 직렬화3-3. 데이터 변경 알림4. CounterBlock — BaseEntityBlock 구현5. 안티패턴 — EntityBlock 누락6. ExampleMod.java — COUNTER_BLOCK 등록7. 인게임 검증7-1. 블록 설치 후 NBT 확인7-2. 저장·재진입 후 값 보존 확인요약
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