NeoForge 26.1 Docs
  • 문서
  • 노트
NeoForge 26.1
NeoForge 26.1
v26.1
학습 진도
0 / 77 챕터 완료방문 0
DataGen 통합 가이드 — 코드로 JSON 생성하기
부록: 데이터 생성

DataGen 통합 가이드 — 코드로 JSON 생성하기

NeoForge DataGen 시스템을 소개합니다. GatherDataEvent, 주요 Provider 5종, build.gradle runData 태스크, DataGenHandler 예시까지 한 챕터에서 다룹니다.

DataGen이란?

모드를 만들다 보면 JSON 파일을 손으로 작성하는 일이 많습니다. 레시피, 블록 태그, 아이템 태그, 루트 테이블, blockstate, 아이템 모델. 파일 하나하나는 단순하지만, 아이템이 수십 개로 늘어나면 관리가 금방 힘들어집니다.

NeoForge DataGen은 이 문제를 Java 코드로 해결합니다. JSON을 직접 쓰는 대신, Provider 클래스를 작성하면 빌드 시 JSON이 자동 생성됩니다.

손작성 JSON 대비 DataGen의 장점:

  • 재현성: 코드가 있으면 언제든 동일한 JSON을 다시 생성할 수 있습니다.
  • 타입 안전: 오타나 잘못된 키 이름을 컴파일 타임에 잡습니다.
  • 리팩터링 용이: 아이템 ID를 바꾸면 관련 JSON이 모두 자동 갱신됩니다.
  • 중복 제거: 비슷한 패턴의 레시피를 루프로 처리할 수 있습니다.

GatherDataEvent

DataGen의 진입점은 GatherDataEvent입니다. 이 이벤트는 Mod EventBus에서 발생하며, ./gradlew runData 태스크 실행 시 트리거됩니다.

@EventBusSubscriber(modid = "examplemod")
public class DataGenHandler {
    @SubscribeEvent
    public static void onGatherData(GatherDataEvent event) {
        DataGenerator gen = event.getGenerator();
        PackOutput output = gen.getPackOutput();
 
        // 여기에 Provider를 등록합니다
    }
}

event.includeServer()와 event.includeClient()로 서버 데이터(레시피, 태그, 루트 테이블)와 클라이언트 데이터(모델, blockstate)를 구분해서 등록합니다.


주요 Provider 5종

1. RecipeProvider — 레시피 JSON

조합 레시피, 제련 레시피, 돌 절단기 레시피 등을 생성합니다.

gen.addProvider(event.includeServer(), new RecipeProvider(output, event.getLookupProvider()) {
    @Override
    protected void buildRecipes(RecipeOutput out, HolderLookup.Provider lookup) {
        // 3x3 조합 레시피
        ShapedRecipeBuilder.shaped(RecipeCategory.MISC, ExampleMod.EXAMPLE_ITEM.get())
            .pattern("###")
            .pattern("###")
            .pattern("###")
            .define('#', Items.IRON_INGOT)
            .unlockedBy("has_iron", has(Items.IRON_INGOT))
            .save(out);
 
        // 무형 조합 레시피
        ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, Items.STICK, 4)
            .requires(Items.OAK_PLANKS)
            .unlockedBy("has_planks", has(Items.OAK_PLANKS))
            .save(out, Identifier.fromNamespaceAndPath("examplemod", "stick_from_planks"));
    }
});

2. BlockTagsProvider — 블록 태그

minecraft:mineable/pickaxe 같은 바닐라 태그나 커스텀 태그에 블록을 추가합니다.

gen.addProvider(event.includeServer(), new BlockTagsProvider(output, event.getLookupProvider(), "examplemod", event.getExistingFileHelper()) {
    @Override
    protected void addTags(HolderLookup.Provider lookup) {
        // 곡괭이로 채굴 가능
        tag(BlockTags.MINEABLE_WITH_PICKAXE)
            .add(ExampleMod.EXAMPLE_BLOCK.get());
 
        // 커스텀 태그
        tag(ExampleMod.MY_SPECIAL_BLOCKS)
            .add(ExampleMod.EXAMPLE_BLOCK.get());
    }
});

3. ItemTagsProvider — 아이템 태그

블록 태그와 같은 방식으로 아이템 태그를 관리합니다. BlockTagsProvider를 의존성으로 받아 블록 태그를 아이템 태그로 복사할 수 있습니다.

gen.addProvider(event.includeServer(), new ItemTagsProvider(output, event.getLookupProvider(), blockTagsProvider.contentsGetter(), "examplemod", event.getExistingFileHelper()) {
    @Override
    protected void addTags(HolderLookup.Provider lookup) {
        tag(ItemTags.SWORDS)
            .add(ExampleMod.RUBY_SWORD.get().asItem());
    }
});

4. LootTableProvider — 루트 테이블

블록 파괴 시 드롭, 엔티티 처치 시 드롭 등을 정의합니다.

gen.addProvider(event.includeServer(), new LootTableProvider(output, Set.of(), List.of(
    new LootTableProvider.SubProviderEntry(
        ExampleBlockLootTables::new,
        LootContextParamSets.BLOCK
    )
), event.getLookupProvider()));

ExampleBlockLootTables는 BlockLootSubProvider를 상속해서 각 블록의 드롭을 정의합니다.

public class ExampleBlockLootTables extends BlockLootSubProvider {
    protected ExampleBlockLootTables(HolderLookup.Provider lookup) {
        super(Set.of(), FeatureFlags.REGISTRY.allFlags(), lookup);
    }
 
    @Override
    protected void generate() {
        // 블록 자체를 드롭
        dropSelf(ExampleMod.EXAMPLE_BLOCK.get());
    }
 
    @Override
    protected Iterable<Block> getKnownBlocks() {
        return ExampleMod.BLOCKS.getEntries().stream()
            .map(DeferredHolder::get)
            .toList();
    }
}

5. BlockStateProvider + ItemModelProvider — 모델

BlockStateProvider는 blockstate JSON과 블록 모델을 함께 생성합니다. ItemModelProvider는 아이템 모델을 생성합니다.

// BlockState + 블록 모델
gen.addProvider(event.includeClient(), new BlockStateProvider(output, "examplemod", event.getExistingFileHelper()) {
    @Override
    protected void registerStatesAndModels() {
        // 단순 큐브 블록
        simpleBlock(ExampleMod.EXAMPLE_BLOCK.get());
    }
});
 
// 아이템 모델
gen.addProvider(event.includeClient(), new ItemModelProvider(output, "examplemod", event.getExistingFileHelper()) {
    @Override
    protected void registerModels() {
        // 블록 아이템 (블록 모델 참조)
        withExistingParent(
            ExampleMod.EXAMPLE_BLOCK_ITEM.getId().getPath(),
            modLoc("block/example_block")
        );
    }
});

build.gradle runData 태스크

examplemod-template-26.1.2/build.gradle에 이미 data 런 설정이 포함되어 있습니다.

// examplemod-template-26.1.2/build.gradle (71번 줄)
data {
    clientData()
 
    // DataGen 출력 경로와 기존 리소스 경로를 지정합니다
    programArguments.addAll '--mod', project.mod_id, '--all',
        '--output', file('src/generated/resources/').getAbsolutePath(),
        '--existing', file('src/main/resources/').getAbsolutePath()
}

./gradlew runData를 실행하면 src/generated/resources/ 아래에 JSON 파일이 생성됩니다. sourceSets.main.resources에 이 경로가 이미 포함되어 있으므로 빌드 시 자동으로 포함됩니다.


DataGenHandler 전체 예시

한 챕터에서 손으로 작성했던 JSON을 DataGen으로 재작성하는 예시입니다.

// src/main/java/com/example/examplemod/datagen/DataGenHandler.java
@EventBusSubscriber(modid = "examplemod")
public class DataGenHandler {
 
    @SubscribeEvent
    public static void onGatherData(GatherDataEvent event) {
        DataGenerator gen = event.getGenerator();
        PackOutput output = gen.getPackOutput();
        CompletableFuture<HolderLookup.Provider> lookupProvider = event.getLookupProvider();
 
        // 서버 데이터
        BlockTagsProvider blockTags = new BlockTagsProvider(
            output, lookupProvider, "examplemod", event.getExistingFileHelper()
        ) {
            @Override
            protected void addTags(HolderLookup.Provider lookup) {
                tag(BlockTags.MINEABLE_WITH_PICKAXE)
                    .add(ExampleMod.EXAMPLE_BLOCK.get());
            }
        };
        gen.addProvider(event.includeServer(), blockTags);
 
        gen.addProvider(event.includeServer(), new ItemTagsProvider(
            output, lookupProvider, blockTags.contentsGetter(),
            "examplemod", event.getExistingFileHelper()
        ) {
            @Override
            protected void addTags(HolderLookup.Provider lookup) {
                tag(ItemTags.SWORDS)
                    .add(ExampleMod.RUBY_SWORD.get().asItem());
            }
        });
 
        gen.addProvider(event.includeServer(), new RecipeProvider(output, lookupProvider) {
            @Override
            protected void buildRecipes(RecipeOutput out, HolderLookup.Provider lookup) {
                ShapedRecipeBuilder.shaped(RecipeCategory.MISC, ExampleMod.EXAMPLE_ITEM.get())
                    .pattern("###")
                    .pattern("###")
                    .pattern("###")
                    .define('#', Items.IRON_INGOT)
                    .unlockedBy("has_iron", has(Items.IRON_INGOT))
                    .save(out);
            }
        });
 
        gen.addProvider(event.includeServer(), new LootTableProvider(
            output, Set.of(),
            List.of(new LootTableProvider.SubProviderEntry(
                ExampleBlockLootTables::new, LootContextParamSets.BLOCK
            )),
            lookupProvider
        ));
 
        // 클라이언트 데이터
        gen.addProvider(event.includeClient(), new BlockStateProvider(
            output, "examplemod", event.getExistingFileHelper()
        ) {
            @Override
            protected void registerStatesAndModels() {
                simpleBlock(ExampleMod.EXAMPLE_BLOCK.get());
            }
        });
    }
}

안티패턴: runData 출력을 손으로 수정하지 말 것

⚠️ runData 출력을 손으로 수정하면 안 됩니다

src/generated/resources/data/examplemod/recipe/<file>.json을 직접 편집한 뒤 다시 ./gradlew runData를 실행하면, DataGen이 파일을 덮어씁니다. 손수정 내용은 사라집니다.

DataGen을 사용하는 경우 JSON은 항상 코드에서만 수정하세요. 데이터팩 작성자처럼 JSON을 직접 편집하고 싶다면 DataGen을 쓰지 않고 src/main/resources/에 직접 파일을 두는 방식을 선택하세요.


다음 단계

DataGen은 각 챕터에서 다루는 기능(레시피, 태그, 루트 테이블, 모델)과 1:1로 대응합니다. 이 가이드를 참고해서 각 챕터에서 손으로 작성한 JSON을 DataGen으로 전환해 보세요.

  • 레시피: RecipeProvider + ShapedRecipeBuilder / ShapelessRecipeBuilder
  • 태그: BlockTagsProvider + ItemTagsProvider
  • 루트 테이블: LootTableProvider + BlockLootSubProvider
  • 모델: BlockStateProvider + ItemModelProvider

마무리와 통합 빌드

마스터 트랙 캡스톤을 README로 정리하고, tools·dimension·machine·capstone 4개 JAR을 함께 로드해 End-to-End 흐름을 검증합니다.

다음 단계 — 추가 리소스와 커뮤니티

NeoForge 26 마스터 학습 코스 완주 축하. 공식 문서, 커뮤니티, 심화 라이브러리, 모드 배포 방법까지 향후 학습 방향을 안내합니다.

On this page

DataGen이란?GatherDataEvent주요 Provider 5종1. RecipeProvider — 레시피 JSON2. BlockTagsProvider — 블록 태그3. ItemTagsProvider — 아이템 태그4. LootTableProvider — 루트 테이블5. BlockStateProvider + ItemModelProvider — 모델build.gradle runData 태스크DataGenHandler 전체 예시안티패턴: runData 출력을 손으로 수정하지 말 것다음 단계
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