ModConfigSpec과 설정 GUI
NeoForge 26의 ModConfigSpec.Builder로 모드 설정을 정의하고 Mods 메뉴의 Config GUI에서 변경하는 방법을 학습합니다.
ModConfigSpec과 설정 GUI
모드를 배포하면 플레이어마다 원하는 동작 방식이 다릅니다. 어떤 플레이어는 특정 기능을 끄고 싶고, 다른 플레이어는 수치를 조정하고 싶습니다. NeoForge 26은 ModConfigSpec 시스템으로 이 문제를 해결합니다. TOML 파일로 값을 저장하고, 인게임 Mods 메뉴의 Config GUI에서 편집할 수 있습니다.
이 챕터에서는 examplemod-template-26.1.2의 Config.java를 라인 단위로 분석하면서 설정 시스템의 핵심 패턴을 학습합니다.
ModConfigSpec이란
NeoForge 26이 제공하는 TOML 기반 설정 시스템입니다. 핵심 구성 요소는 세 가지입니다.
| 구성 요소 | 역할 |
|---|---|
ModConfigSpec.Builder | 설정 항목을 선언하는 빌더 |
ModConfigSpec | 빌더로 완성된 사양(Spec) — FML이 TOML 파일과 연결 |
ModConfigSpec.*Value | 런타임에 값을 읽는 핸들 (BooleanValue, IntValue, …) |
빌드 타임(클래스 로딩 시)에 Builder로 항목을 선언하고, build()로 ModConfigSpec을 완성합니다. FML은 이 Spec을 받아 run/config/<modid>-<type>.toml 파일을 자동 생성·로드합니다.
Config.java 전체 코드
examplemod-template-26.1.2/src/main/java/com/example/examplemod/Config.java
package com.example.examplemod;
import java.util.List;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.Identifier;
import net.neoforged.neoforge.common.ModConfigSpec;
public class Config {
private static final ModConfigSpec.Builder BUILDER = new ModConfigSpec.Builder(); // (1)
public static final ModConfigSpec.BooleanValue LOG_DIRT_BLOCK = BUILDER // (2)
.comment("Whether to log the dirt block on common setup")
.define("logDirtBlock", true);
public static final ModConfigSpec.IntValue MAGIC_NUMBER = BUILDER // (3)
.comment("A magic number")
.defineInRange("magicNumber", 42, 0, Integer.MAX_VALUE);
public static final ModConfigSpec.ConfigValue<String> MAGIC_NUMBER_INTRODUCTION = BUILDER // (4)
.comment("What you want the introduction message to be for the magic number")
.define("magicNumberIntroduction", "The magic number is... ");
public static final ModConfigSpec.ConfigValue<List<? extends String>> ITEM_STRINGS = BUILDER // (5)
.comment("A list of items to log on common setup.")
.defineListAllowEmpty("items", List.of("minecraft:iron_ingot"), () -> "", Config::validateItemName);
static final ModConfigSpec SPEC = BUILDER.build(); // (6)
private static boolean validateItemName(final Object obj) { // (7)
return obj instanceof String itemName && BuiltInRegistries.ITEM.containsKey(Identifier.parse(itemName));
}
}라인 단위 해설
(1) Builder 인스턴스 생성
private static final ModConfigSpec.Builder BUILDER = new ModConfigSpec.Builder();static final— 클래스 로딩 시 단 한 번만 생성됩니다.private— 외부에서 직접 접근하지 못하도록 숨깁니다. 외부에는*Value핸들과SPEC만 공개합니다.- 이후의
BUILDER.comment(...).define(...)체이닝이 모두 이 인스턴스를 사용합니다.
(2) boolean 설정 — define()
public static final ModConfigSpec.BooleanValue LOG_DIRT_BLOCK = BUILDER
.comment("Whether to log the dirt block on common setup")
.define("logDirtBlock", true);| 메서드 | 역할 |
|---|---|
.comment("...") | TOML 파일에 주석으로 삽입됩니다. Config GUI에서 툴팁으로도 표시됩니다. |
.define("logDirtBlock", true) | TOML 키 logDirtBlock을 boolean으로 선언. 기본값 true. |
런타임에 값 읽기:
if (Config.LOG_DIRT_BLOCK.getAsBoolean()) {
LOGGER.info("DIRT BLOCK >> {}", ...);
}(3) 정수 범위 설정 — defineInRange()
public static final ModConfigSpec.IntValue MAGIC_NUMBER = BUILDER
.comment("A magic number")
.defineInRange("magicNumber", 42, 0, Integer.MAX_VALUE);| 파라미터 | 값 | 의미 |
|---|---|---|
| key | "magicNumber" | TOML 키 |
| defaultValue | 42 | 기본값 |
| min | 0 | 허용 최솟값 |
| max | Integer.MAX_VALUE | 허용 최댓값 |
범위를 벗어나는 값은 Config GUI에서 즉시 에러로 표시하고 저장을 막습니다.
런타임에 값 읽기:
LOGGER.info("{}{}", Config.MAGIC_NUMBER_INTRODUCTION.get(), Config.MAGIC_NUMBER.getAsInt());(4) 문자열 설정 — define() + 제네릭
public static final ModConfigSpec.ConfigValue<String> MAGIC_NUMBER_INTRODUCTION = BUILDER
.comment("What you want the introduction message to be for the magic number")
.define("magicNumberIntroduction", "The magic number is... ");ConfigValue<String>— 제네릭 타입으로 어떤 타입의 값이든 저장할 수 있습니다.- 런타임에는
.get()으로 값을 읽습니다.
(5) 리스트 설정 — defineListAllowEmpty()
public static final ModConfigSpec.ConfigValue<List<? extends String>> ITEM_STRINGS = BUILDER
.comment("A list of items to log on common setup.")
.defineListAllowEmpty("items", List.of("minecraft:iron_ingot"), () -> "", Config::validateItemName);| 파라미터 | 의미 |
|---|---|
"items" | TOML 키 |
List.of("minecraft:iron_ingot") | 기본값 리스트 |
() -> "" | 새 원소 추가 시 기본값 공급자 |
Config::validateItemName | 원소 유효성 검사기 |
defineList()은 빈 리스트를 허용하지 않습니다. 빈 리스트도 허용하려면 반드시 defineListAllowEmpty()를 사용합니다.
런타임에 값 읽기:
Config.ITEM_STRINGS.get().forEach((item) -> LOGGER.info("ITEM >> {}", item));(6) Spec 완성 — build()
static final ModConfigSpec SPEC = BUILDER.build();- 모든
.define*()선언이 끝난 뒤 호출합니다. BUILDER를package-private(static final)으로 두어 같은 패키지의ExampleMod만 접근합니다.- 이
SPEC을modContainer.registerConfig()에 전달해야 FML이 파일을 생성합니다.
(7) 유효성 검사 메서드
private static boolean validateItemName(final Object obj) {
return obj instanceof String itemName
&& BuiltInRegistries.ITEM.containsKey(Identifier.parse(itemName));
}- 리스트의 각 원소가 실제로 등록된 아이템 ID인지 검사합니다.
- 잘못된 값(
"minecraft:notanitem")이 TOML에 있으면 NeoForge가 시작 시 경고를 출력하고 해당 원소를 기본값으로 대체합니다.
SPEC 등록 — ExampleMod.java
Config.SPEC을 FML에 등록해야 파일이 생성됩니다. ExampleMod.java 생성자의 마지막 줄입니다.
// ExampleMod.java 생성자 마지막 줄
modContainer.registerConfig(ModConfig.Type.COMMON, Config.SPEC);ModConfig.Type은 세 가지입니다.
| 타입 | 파일 위치 | 적용 대상 |
|---|---|---|
COMMON | run/config/<modid>-common.toml | 서버·클라이언트 공용 |
CLIENT | run/config/<modid>-client.toml | 클라이언트 전용 |
SERVER | run/saves/<world>/serverconfig/<modid>-server.toml | 월드별 서버 전용 |
예제 모드는 COMMON을 사용하므로 run/config/examplemod-common.toml이 생성됩니다.
타입별 메서드 한눈에 비교
| 타입 | 메서드 | 반환 타입 |
|---|---|---|
| boolean | define("key", true) | BooleanValue |
| int (범위) | defineInRange("key", 10, 0, 100) | IntValue |
| long (범위) | defineInRange("key", 100L, 0L, Long.MAX_VALUE) | LongValue |
| double (범위) | defineInRange("key", 1.0, 0.0, 10.0) | DoubleValue |
| 문자열·객체 | define("key", "default") | ConfigValue<T> |
| Enum | defineEnum("key", MyEnum.A) | EnumValue<T> |
| 리스트 (비어있지 않음) | defineList("key", List.of("a"), v -> v instanceof String) | ConfigValue<List<? extends T>> |
| 리스트 (빈 리스트 허용) | defineListAllowEmpty("key", List.of(), ...) | ConfigValue<List<? extends T>> |
⚠️ Enum 사용 주의
defineEnum()은 Enum 상수의name()값을 TOML에 저장합니다. Enum 상수명을 리네임하면 기존 설정 파일에서 파싱 오류가 발생합니다.
생성된 TOML 파일
gradlew runClient 실행 후 examplemod-template-26.1.2/run/config/examplemod-common.toml이 생성됩니다.
#Whether to log the dirt block on common setup
#Default: true
logDirtBlock = true
#A magic number
#Range: 0 ~ 2147483647
#Default: 42
magicNumber = 42
#What you want the introduction message to be for the magic number
#Default: The magic number is...
magicNumberIntroduction = "The magic number is... "
#A list of items to log on common setup.
items = ["minecraft:iron_ingot"].comment() 내용이 # 주석으로 그대로 삽입된 것을 확인할 수 있습니다. 플레이어가 이 파일을 직접 편집하거나 Config GUI를 통해 수정할 수 있습니다.
인게임 Config GUI 진입
NeoForge 26은 Mods 메뉴에 Config 버튼을 자동으로 추가합니다. ModConfigSpec을 등록한 모드라면 별도 코드 없이 GUI가 제공됩니다.
진입 경로: 타이틀 화면 → Mods → 좌측 목록에서 Example Mod 선택 → 우측 상단 Config 버튼 클릭
Config GUI에서 할 수 있는 일:
- boolean 토글 버튼
- 숫자 슬라이더 및 텍스트 입력 (범위 표시 포함)
- 문자열 텍스트 입력
- 리스트 원소 추가·삭제
- 변경값 저장 (게임 재시작 없이
ModConfigEvent.Reloading발생)
ℹ️ 참고용 안내
Minecraft 타이틀 화면 → Mods 메뉴의 캡처는
gradlew runClient실행 후 인게임에서만 확인 가능합니다. 04-run-client 챕터에서 게임을 실행한 뒤 위 경로로 직접 확인해보세요.
설정 변경 이벤트 처리 (선택 사항)
플레이어가 Config GUI에서 값을 저장하면 ModConfigEvent가 발생합니다. 캐시된 값을 갱신해야 할 때 사용합니다.
// Config.java에 추가 (선택 사항)
@EventBusSubscriber(modid = ExampleMod.MODID)
public static class ConfigEvents {
@SubscribeEvent
static void onLoad(ModConfigEvent.Loading event) {
ExampleMod.LOGGER.info("설정 로드됨");
}
@SubscribeEvent
static void onReload(ModConfigEvent.Reloading event) {
ExampleMod.LOGGER.info("설정 변경됨 — 캐시 갱신 필요 시 처리");
}
}⚠️ 런타임 set 금지
ConfigValue의 값을 코드에서 직접 바꾸는 방법은 NeoForge 26에서 지원하지 않습니다. 값은 TOML 파일 또는 Config GUI를 통해서만 변경해야 합니다.
defineInRange 범위 초과 시 동작
TOML 파일에 범위를 벗어나는 값(magicNumber = -1)을 직접 입력하면:
- FML이 시작 시 콘솔에 경고를 출력합니다.
- 해당 항목을 기본값(
42)으로 강제 교체합니다. - 게임은 정상 시작됩니다.
Config GUI에서는 범위를 벗어나는 값 입력 자체를 빨간 테두리로 표시하고 저장 버튼을 비활성화합니다.
실습: commonSetup에서 값 읽기
ExampleMod.java의 commonSetup 메서드가 세 가지 Config 값을 어떻게 읽는지 확인하세요.
// ExampleMod.java — commonSetup() 메서드
private void commonSetup(FMLCommonSetupEvent event) {
LOGGER.info("HELLO FROM COMMON SETUP");
// (1) boolean 읽기
if (Config.LOG_DIRT_BLOCK.getAsBoolean()) {
LOGGER.info("DIRT BLOCK >> {}", BuiltInRegistries.BLOCK.getKey(Blocks.DIRT));
}
// (2) int + String 연결 읽기
LOGGER.info("{}{}", Config.MAGIC_NUMBER_INTRODUCTION.get(), Config.MAGIC_NUMBER.getAsInt());
// (3) 리스트 순회 읽기
Config.ITEM_STRINGS.get().forEach((item) -> LOGGER.info("ITEM >> {}", item));
}gradlew runClient로 게임을 시작하고 콘솔 로그를 보면 위 세 줄이 출력되는 것을 확인할 수 있습니다.
[main/INFO] [examplemod/]: HELLO FROM COMMON SETUP
[main/INFO] [examplemod/]: DIRT BLOCK >> minecraft:dirt
[main/INFO] [examplemod/]: The magic number is... 42
[main/INFO] [examplemod/]: ITEM >> minecraft:iron_ingot
정리
- NeoForge 26의
ModConfigSpec.Builder는 TOML 파일과 인게임 Config GUI를 모두 자동 생성합니다. define()/defineInRange()/defineListAllowEmpty()/defineEnum()으로 타입별 설정을 선언합니다..comment()는 TOML 주석 + GUI 툴팁으로 표시됩니다. 반드시 작성하세요.Builder.build()로ModConfigSpec을 완성한 뒤modContainer.registerConfig()에 전달합니다.- 값을 읽을 때는
getAsBoolean()/getAsInt()/.get()등 타입별 접근자를 사용합니다. - 다음 챕터에서는
Logging을 심층적으로 다룹니다.