사운드 등록과 재생
NeoForge 26에서 커스텀 사운드를 DeferredRegister로 등록하고, sounds.json과 OGG 파일을 구성한 뒤 양 사이드에서 올바르게 재생하는 방법을 설명합니다.
Minecraft의 사운드 시스템은 단순히 파일을 재생하는 것 이상입니다. 등록, 메타데이터 선언, 클라이언트/서버 분기까지 세 단계를 모두 올바르게 처리해야 소리가 제대로 납니다. 이 챕터에서는 커스텀 사운드를 처음부터 끝까지 구현합니다.
사운드 시스템 개요
NeoForge의 사운드 파이프라인은 세 요소로 구성됩니다.
| 요소 | 역할 |
|---|---|
SoundEvent 레지스트리 | 사운드 식별자 등록 (서버/클라 공유) |
sounds.json | 실제 OGG 파일 매핑 + 카테고리 선언 |
| OGG 파일 | assets/<modid>/sounds/ 아래 실제 오디오 |
세 요소 중 하나라도 빠지면 사운드가 재생되지 않거나 콘솔에 경고가 쌓입니다.
1단계: SoundEvent DeferredRegister
SoundEvent도 다른 게임 오브젝트와 동일하게 DeferredRegister로 등록합니다.
// src/main/java/com/example/examplemod/ModSounds.java
public class ModSounds {
public static final DeferredRegister<SoundEvent> SOUND_EVENTS =
DeferredRegister.create(Registries.SOUND_EVENT, ExampleMod.MODID);
// 26.1.2: ResourceLocation 삭제 → Identifier.fromNamespaceAndPath 사용.
public static final Supplier<SoundEvent> MAGIC_CHIME = SOUND_EVENTS.register(
"magic_chime",
() -> SoundEvent.createVariableRangeEvent(
Identifier.fromNamespaceAndPath(ExampleMod.MODID, "magic_chime")
)
);
}createVariableRangeEvent는 거리에 따라 볼륨이 자동으로 감쇠하는 사운드를 만듭니다. 고정 범위가 필요하면 createFixedRangeEvent(Identifier, float)를 사용하세요.
메인 모드 클래스에서 버스에 등록합니다.
// ExampleMod.java
@Mod("examplemod")
public class ExampleMod {
public ExampleMod(IEventBus modEventBus) {
ModSounds.SOUND_EVENTS.register(modEventBus);
}
}2단계: sounds.json 작성
SoundEvent를 등록했다고 해서 실제 파일이 연결되지는 않습니다. sounds.json이 그 다리 역할을 합니다.
// src/main/resources/assets/examplemod/sounds.json
{
"magic_chime": {
"category": "block",
"subtitle": "subtitles.examplemod.magic_chime",
"sounds": [
{
"name": "examplemod:magic_chime",
"stream": false
}
]
}
}각 필드의 의미:
| 필드 | 설명 |
|---|---|
category | 게임 설정의 볼륨 슬라이더 분류 |
subtitle | 자막 표시용 번역 키 |
sounds[].name | <modid>:<경로> 형식. assets/<modid>/sounds/<경로>.ogg 파일을 가리킴 |
stream | true면 스트리밍 재생 (긴 음악용). 짧은 효과음은 false |
사운드 카테고리 목록
Minecraft가 제공하는 카테고리는 다음과 같습니다.
| 카테고리 | 용도 |
|---|---|
master | 전체 볼륨 (모든 사운드에 영향) |
music | 배경 음악 |
record | 음반 (주크박스) |
weather | 날씨 효과음 |
block | 블록 상호작용 |
hostile | 적대적 몹 |
neutral | 중립 몹 |
player | 플레이어 동작 |
ambient | 환경음 |
voice | 대화/내레이션 |
3단계: OGG 파일 배치
파일 경로는 sounds.json의 name 필드와 정확히 일치해야 합니다.
src/main/resources/
└── assets/
└── examplemod/
├── sounds.json
└── sounds/
└── magic_chime.ogg ← 여기
"name": "examplemod:magic_chime"이면 assets/examplemod/sounds/magic_chime.ogg를 찾습니다. 하위 폴더도 가능합니다. "name": "examplemod:ui/click"이면 sounds/ui/click.ogg입니다.
⚠️ OGG 외 포맷 사용 금지
Minecraft는 OGG Vorbis만 지원합니다.
- MP3, WAV 파일을 넣으면 로드 자체가 실패합니다.
- OGG 파일의 샘플레이트는 44100Hz 또는 22050Hz를 권장합니다.
다른 포맷을 변환할 때는 ffmpeg를 사용하세요.
ffmpeg -i input.wav -c:a libvorbis output.ogg
4단계: 자막 번역 키 추가
sounds.json에 subtitle을 지정했다면 lang 파일에도 추가해야 자막이 표시됩니다.
// assets/examplemod/lang/ko_kr.json
{
"subtitles.examplemod.magic_chime": "마법 종소리"
}// assets/examplemod/lang/en_us.json
{
"subtitles.examplemod.magic_chime": "Magic Chime"
}자막은 게임 설정에서 "자막 표시"를 켜야 화면에 나타납니다.
5단계: 사운드 재생
양 사이드 재생 (권장)
Level.playSound는 서버에서 호출하면 주변 클라이언트에 자동으로 패킷을 전송합니다. 첫 번째 인자를 null로 넘기면 호출한 플레이어를 포함한 모든 주변 플레이어에게 들립니다.
// 서버 사이드에서 호출 — 클라이언트에 자동 동기화
level.playSound(
null, // 제외할 플레이어 (null = 모두에게)
pos, // BlockPos 또는 double x, y, z
ModSounds.MAGIC_CHIME.get(), // SoundEvent
SoundSource.BLOCKS, // 카테고리
1.0f, // 볼륨 (0.0 ~ 1.0+)
1.0f // 피치 (0.5 = 낮음, 2.0 = 높음)
);이벤트 핸들러 안에서 사용하는 예시입니다.
@SubscribeEvent
public static void onBlockActivated(UseItemOnBlockEvent event) {
Level level = event.getLevel();
BlockPos pos = event.getPos();
if (!level.isClientSide()) {
level.playSound(null, pos, ModSounds.MAGIC_CHIME.get(),
SoundSource.BLOCKS, 1.0f, 1.0f);
}
}⚠️ 클라이언트 전용 재생 함수 혼용 주의
Minecraft.getInstance().getSoundManager().play(...)는 클라이언트 사이드에서만 호출할 수 있습니다. 서버 스레드에서 호출하면 크래시가 발생합니다.// 클라이언트 전용 — @OnlyIn(Dist.CLIENT) 또는 isClientSide() 확인 필수 if (level.isClientSide()) { Minecraft.getInstance().getSoundManager().play( SimpleSoundInstance.forUI(ModSounds.MAGIC_CHIME.get(), 1.0f) ); }멀티플레이어에서 모든 플레이어에게 들려야 한다면
Level.playSound를 서버 사이드에서 호출하는 것이 올바른 방법입니다.
볼륨과 피치 조절
// 랜덤 피치로 자연스러운 변주 효과
float pitch = 0.8f + level.random.nextFloat() * 0.4f; // 0.8 ~ 1.2
level.playSound(null, pos, ModSounds.MAGIC_CHIME.get(),
SoundSource.BLOCKS, 1.0f, pitch);전체 흐름 요약
ModSounds.java sounds.json OGG 파일
DeferredRegister → 파일 경로 매핑 → 실제 오디오 데이터
(레지스트리 등록) (카테고리/자막) (assets/.../sounds/)
↓
level.playSound() 호출
↓
서버가 주변 클라이언트에 패킷 전송
↓
클라이언트 SoundManager가 OGG 재생
정리
DeferredRegister<SoundEvent>로 사운드 식별자를 등록한다.sounds.json에서 OGG 파일 경로와 카테고리를 선언한다.- OGG 파일은
assets/<modid>/sounds/아래에 배치한다. MP3/WAV는 지원하지 않는다. - 멀티플레이어 동기화가 필요하면 서버 사이드에서
Level.playSound(null, ...)를 호출한다. - 클라이언트 전용
SoundManager.play()는 반드시isClientSide()확인 후 사용한다.