NeoForge 26.1 Docs
  • 문서
  • 노트
NeoForge 26.1
NeoForge 26.1
v26.1
학습 진도
0 / 77 챕터 완료방문 0
첫 아이템 등록 — first_itemItem.Properties — stacksTo, rarity, fireResistant, food, durability모드 전용 크리에이티브 탭 만들기음식 아이템과 FoodProperties도구 아이템 — Tier 시스템과 4종 도구 등록방어구와 ArmorItemDataComponents — NBT를 대체하는 타입 안전 데이터 저장툴팁 커스터마이즈아이템 태그 시스템
아이템

DataComponents — NBT를 대체하는 타입 안전 데이터 저장

NeoForge 26(Minecraft 1.20.5+)에서 도입된 DataComponentType<T>로 ItemStack에 타입 안전하게 데이터를 저장하고 읽는 방법을 학습합니다.

DataComponents — NBT를 대체하는 타입 안전 데이터 저장

Minecraft 1.20.5부터 아이템 데이터를 저장하는 방식이 근본적으로 바뀌었습니다. 기존의 CompoundTag(NBT) 직접 조작 대신 DataComponents 시스템을 씁니다. 타입이 명확하고, 직렬화와 네트워크 동기화가 자동으로 처리됩니다.


1. 왜 DataComponents인가

기존 NBT 방식의 문제는 세 가지였습니다.

첫째, 타입 안전성 없음. CompoundTag에서 꺼낸 값은 항상 int, String 등 원시 타입이라 잘못된 키를 써도 컴파일 에러가 나지 않습니다.

둘째, 직렬화 수동 처리. 저장할 때 tag.putInt("charge", value), 읽을 때 tag.getInt("charge") — 키 문자열을 두 곳에서 맞춰야 합니다. 오타 한 번이면 데이터가 사라집니다.

셋째, 네트워크 동기화 수동 처리. 서버에서 바꾼 값을 클라이언트에 보내려면 패킷을 직접 작성해야 했습니다.

DataComponents는 이 세 가지를 모두 해결합니다.

항목구식 NBTDataComponents
타입untyped (CompoundTag)typed (T)
직렬화수동 NBT 처리Codec<T> 자동
네트워크수동 buf.read/writeStreamCodec<T> 자동
APIgetOrCreateTag()set / get / remove
컴파일 안전성없음 (런타임 오류)있음 (컴파일 오류)

2. DataComponentType<T> 등록

컴포넌트 타입은 DeferredRegister.DataComponents로 등록합니다. 아이템 등록과 같은 패턴입니다.

// examplemod-template-26.1.2/src/main/java/com/example/examplemod/ModDataComponents.java
public class ModDataComponents {
 
    // 26.1: createDataComponents 는 (레지스트리 키, modid) 2-인자. 단일 String 오버로드 없음.
    public static final DeferredRegister.DataComponents COMPONENTS =
        DeferredRegister.createDataComponents(Registries.DATA_COMPONENT_TYPE, ExampleMod.MODID);
 
    // 정수형 충전량 컴포넌트
    public static final Supplier<DataComponentType<Integer>> CHARGE =
        COMPONENTS.registerComponentType(
            "charge",
            builder -> builder
                .persistent(Codec.INT)                      // 저장/로드용 Codec
                .networkSynchronized(ByteBufCodecs.INT)     // 네트워크 동기화용 StreamCodec
        );
 
    // 문자열형 소유자 컴포넌트
    public static final Supplier<DataComponentType<String>> OWNER =
        COMPONENTS.registerComponentType(
            "owner",
            builder -> builder
                .persistent(Codec.STRING)
                .networkSynchronized(ByteBufCodecs.STRING_UTF8)
        );
}

그리고 메인 모드 클래스에서 버스에 등록합니다.

// examplemod-template-26.1.2/src/main/java/com/example/examplemod/ExampleMod.java
@Mod("examplemod")
public class ExampleMod {
    public ExampleMod(IEventBus modEventBus) {
        ModDataComponents.COMPONENTS.register(modEventBus);
        // ... 나머지 등록
    }
}

3. ItemStack 조작 — set / getOrDefault / remove

컴포넌트를 등록했으면 ItemStack에서 세 가지 메소드로 조작합니다.

// examplemod-template-26.1.2/src/main/java/com/example/examplemod/item/MagicWandItem.java
ItemStack stack = ...;
 
// 설정 — 충전량을 50으로
stack.set(ModDataComponents.CHARGE.get(), 50);
 
// 읽기 — 없으면 기본값 0 반환
int charge = stack.getOrDefault(ModDataComponents.CHARGE.get(), 0);
 
// 삭제 — 컴포넌트 제거
stack.remove(ModDataComponents.CHARGE.get());

getOrDefault의 두 번째 인자가 기본값입니다. 컴포넌트가 없는 새 아이템을 집어도 NPE 없이 안전하게 처리됩니다.


4. 실전 예시 — 마법 막대 충전량

마법 막대를 우클릭할 때마다 충전량이 오르고, 충전량이 가득 차면 방전되는 아이템을 만들어 봅니다.

// examplemod-template-26.1.2/src/main/java/com/example/examplemod/item/MagicWandItem.java
public class MagicWandItem extends Item {
 
    private static final int MAX_CHARGE = 5;
 
    public MagicWandItem(Properties properties) {
        super(properties);
    }
 
    @Override
    public InteractionResult use(Level level, Player player, InteractionHand hand) {
        // 손에 든 스택을 직접(in-place) 수정한다. 26.1 에서 InteractionResultHolder 가 삭제되어
        // 더 이상 새 스택을 래핑해 반환하지 않는다.
        ItemStack stack = player.getItemInHand(hand);
 
        int current = stack.getOrDefault(ModDataComponents.CHARGE.get(), 0);
 
        if (current >= MAX_CHARGE) {
            // 방전
            stack.set(ModDataComponents.CHARGE.get(), 0);
            player.sendSystemMessage(Component.literal("방전!"));
        } else {
            // 충전
            stack.set(ModDataComponents.CHARGE.get(), current + 1);
            player.sendSystemMessage(
                Component.literal("충전량: " + (current + 1) + "/" + MAX_CHARGE)
            );
        }
 
        return InteractionResult.SUCCESS;
    }
}

아이템을 등록할 때는 26.1 의 registerItem 을 씁니다. 람다는 NeoForge 가 id 를 미리 채운 Properties 를 넘겨주므로, 그 안에서 변형(.component(), .durability(), .stacksTo() 등)을 적용합니다. 람다 밖에서 new Item.Properties() 를 직접 만들면 id 가 설정되지 않아 크래시합니다. 기본 컴포넌트 값이 필요하면 람다 안에서 p -> p.component(ModDataComponents.CHARGE.get(), 0) 처럼 붙이면 됩니다.

// examplemod-template-26.1.2/src/main/java/com/example/examplemod/ExampleMod.java
// 아이템 등록 — registerItem 의 props 람다 안에서 변형을 적용한다.
public static final DeferredItem<MagicWandItem> MAGIC_WAND =
    ITEMS.registerItem("magic_wand", MagicWandItem::new, p -> p.stacksTo(1));

5. 복합 타입 컴포넌트 — Record 활용

단순 int나 String이 아닌 사용자 정의 값 타입을 저장할 때는 record로 정의하고, Codec과 StreamCodec을 함께 제공합니다. 아래 예제는 0~100 범위의 신선도 값 하나를 갖는 record로, Codec.intRange(...).xmap(...)으로 범위를 검증하고 ByteBufCodecs.VAR_INT.map(...)으로 네트워크 동기화합니다.

// examplemod-template-26.1.2/src/main/java/com/example/examplemod/data/FoodFreshness.java
public record FoodFreshness(int freshness) {
 
    // 저장/로드용 Codec — 0~100 범위로 검증한다.
    public static final Codec<FoodFreshness> CODEC =
        Codec.intRange(0, 100).xmap(FoodFreshness::new, FoodFreshness::freshness);
 
    // 네트워크 동기화용 StreamCodec. VAR_INT 기반이라 버퍼 타입은 ByteBuf 이다.
    public static final StreamCodec<ByteBuf, FoodFreshness> STREAM_CODEC =
        ByteBufCodecs.VAR_INT.map(FoodFreshness::new, FoodFreshness::freshness);
}
// ModDataComponents.java에 추가
public static final Supplier<DataComponentType<FoodFreshness>> FRESHNESS =
    COMPONENTS.registerComponentType(
        "freshness",
        builder -> builder
            .persistent(FoodFreshness.CODEC)
            .networkSynchronized(FoodFreshness.STREAM_CODEC)
    );

사용할 때는 동일하게 set / getOrDefault를 씁니다.

stack.set(ModDataComponents.FRESHNESS.get(), new FoodFreshness(100));
FoodFreshness freshness = stack.getOrDefault(
    ModDataComponents.FRESHNESS.get(),
    new FoodFreshness(0)
);

6. 안티패턴 — NBT 직접 조작 금지

⚠️ NBT 구식 패턴 금지

// ❌ deprecated — 1.20.5+ 에서 권장되지 않음
CompoundTag tag = stack.getOrCreateTag();
tag.putInt("charge", 50);
int charge = tag.getInt("charge");
 
// ✅ NeoForge 26 — DataComponents 사용
stack.set(ModDataComponents.CHARGE.get(), 50);
int charge = stack.getOrDefault(ModDataComponents.CHARGE.get(), 0);

getOrCreateTag()는 1.20.5부터 deprecated 처리되었습니다. 타입 안전성이 없고, 직렬화 오류가 런타임에만 드러납니다. 새 코드에서는 반드시 DataComponents를 씁니다.


요약

  • DataComponents는 1.20.5+에서 NBT를 대체하는 타입 안전 데이터 저장 시스템입니다.
  • DeferredRegister.DataComponents로 DataComponentType<T>를 등록합니다.
  • Codec<T>는 저장/로드, StreamCodec<T>는 네트워크 동기화를 자동 처리합니다.
  • stack.set(), stack.getOrDefault(), stack.remove()로 조작합니다.
  • 복합 데이터는 record + RecordCodecBuilder로 정의합니다.
  • getOrCreateTag() 직접 조작은 deprecated — 절대 쓰지 않습니다.

다음 챕터에서는 아이템에 커스텀 렌더링을 적용하는 방법을 다룹니다.

방어구와 ArmorItem

ArmorMaterials enum과 4종 슬롯을 이해하고, 커스텀 방어구 세트를 등록하는 방법을 학습합니다.

툴팁 커스터마이즈

appendHoverText 오버라이드로 아이템 툴팁에 커스텀 텍스트를 추가하고, ChatFormatting으로 색상을 입히며, F3+H 어드밴스드 툴팁을 구현하는 방법을 학습합니다.

On this page

DataComponents — NBT를 대체하는 타입 안전 데이터 저장1. 왜 DataComponents인가2. DataComponentType<T> 등록3. ItemStack 조작 — set / getOrDefault / remove4. 실전 예시 — 마법 막대 충전량5. 복합 타입 컴포넌트 — Record 활용6. 안티패턴 — NBT 직접 조작 금지요약
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