NeoForge 26.1 Docs
  • 문서
  • 노트
NeoForge 26.1
NeoForge 26.1
v26.1
학습 진도
0 / 77 챕터 완료방문 0
캡스톤 통합 설계 + 의존 구조차원에 보석 광물 배치 (WorldGen)기계 가공 통합 — Crusher로 광물 → 보석 정제차원 전용 새 몹 — GemGuardian이벤트로 모듈 연결사운드·파티클 폴리시마무리와 통합 빌드
마스터캡스톤

이벤트로 모듈 연결

NeoForge 이벤트 시스템을 활용해 GemGuardian 처치 시 MagicCore를 드롭하고, Ruby 아이템 우클릭으로 PURITY 기반 차원 입장 강화를 표시하는 CapstoneEventHandler를 구현합니다.

이벤트로 모듈 연결

master_capstone은 세 마스터 모드(master_tools, master_dimension, master_machine)를 직접 수정하지 않고 이벤트로 연결합니다. 이번 챕터에서는 CapstoneEventHandler를 작성해 두 이벤트를 처리합니다.

  • GemGuardian 처치 → LivingDeathEvent → MagicCore 드롭
  • Ruby 우클릭 → PlayerInteractEvent → PURITY 검사 → 차원 입장 강화 표시

1. 모듈 연결 흐름

다이어그램 렌더링 중…

캡스톤 모드는 이벤트를 수신하는 역할만 합니다. GemGuardianEntity와 Ruby 아이템은 각자의 모드가 소유하며, 캡스톤은 이를 수정하지 않습니다.


2. MagicCore 아이템 등록

이벤트 핸들러에서 드롭할 MagicCore 아이템을 먼저 등록합니다.

examplemod-master-projects/capstone/src/main/java/com/example/master/capstone/MasterCapstoneMod.java

@Mod(MasterCapstoneMod.MOD_ID)
public class MasterCapstoneMod {
    public static final String MOD_ID = "master_capstone";
    public static final Logger LOGGER = LogUtils.getLogger();
 
    public static final DeferredRegister.Items ITEMS =
            DeferredRegister.createItems(MOD_ID);
 
    /**
     * MagicCore — GemGuardian 처치 시 드롭되는 특별 아이템.
     * 희귀도 EPIC, 최대 16개 스택.
     */
    public static final Supplier<Item> MAGIC_CORE = ITEMS.registerItem("magic_core",
            props -> new Item(props
                    .rarity(Rarity.EPIC)
                    .stacksTo(16)));
 
    public MasterCapstoneMod(IEventBus modEventBus, ModContainer container) {
        LOGGER.info("Master Capstone Mod 로드 — 4개 모드 통합");
        ITEMS.register(modEventBus);
    }
}

DeferredRegister.Items는 아이템 레지스트리를 위한 전용 팩토리 메서드입니다. DeferredRegister.create(ForgeRegistries.ITEMS, MOD_ID)와 달리 타입 안전성이 높습니다.


3. CapstoneEventHandler 구현

examplemod-master-projects/capstone/src/main/java/com/example/master/capstone/CapstoneEventHandler.java

@EventBusSubscriber(modid = MasterCapstoneMod.MOD_ID)
public class CapstoneEventHandler {
 
    @SubscribeEvent
    public static void onMobDeath(LivingDeathEvent event) {
        if (!(event.getEntity() instanceof GemGuardianEntity guardian)) {
            return;
        }
 
        Level level = guardian.level();
        if (level.isClientSide()) {
            return;  // 서버에서만 처리
        }
 
        // 특별 드롭 — MagicCore 아이템
        ItemEntity drop = new ItemEntity(
                level,
                guardian.getX(),
                guardian.getY(),
                guardian.getZ(),
                new ItemStack(MasterCapstoneMod.MAGIC_CORE.get())
        );
        level.addFreshEntity(drop);
 
        // 경험치 사운드 (사운드·파티클 폴리시는 05 챕터에서 확장)
        level.playSound(
                null,
                guardian.getX(), guardian.getY(), guardian.getZ(),
                SoundEvents.EXPERIENCE_ORB_PICKUP,
                SoundSource.NEUTRAL, 1.0f, 0.5f
        );
 
        // 처치한 플레이어에게 채팅 알림
        if (event.getSource().getEntity() instanceof ServerPlayer player) {
            player.sendSystemMessage(Component.literal("§b마법의 코어를 획득했습니다!"));
        }
    }
 
    @SubscribeEvent
    public static void onUseGem(PlayerInteractEvent.RightClickItem event) {
        ItemStack stack = event.getItemStack();
 
        // master_tools Ruby 아이템 태그 확인
        if (!stack.is(MasterCapstoneMod.RUBY_ITEM_TAG)) return;
        if (!(event.getEntity() instanceof ServerPlayer player)) return;
 
        // PURITY DataComponent 확인
        if (!stack.has(MasterCapstoneMod.PURITY_COMPONENT)) return;
 
        int purity = stack.get(MasterCapstoneMod.PURITY_COMPONENT);
        if (purity >= 75) {
            player.sendSystemMessage(Component.literal("§a고순도 보석 — 차원 입장 강화"));
        }
    }
}

4. 코드 분석 — 핵심 패턴

4-1. isClientSide() 가드

Level level = guardian.level();
if (level.isClientSide()) {
    return;
}

LivingDeathEvent는 클라이언트와 서버 양쪽에서 발생합니다. 아이템 드롭은 서버에서만 처리해야 합니다. 클라이언트에서 level.addFreshEntity(drop)을 호출하면 서버와 데이터 불일치가 생깁니다.

4-2. 패턴 매칭 instanceof

if (!(event.getEntity() instanceof GemGuardianEntity guardian)) {
    return;
}

Java 16+의 패턴 매칭 instanceof를 사용합니다. instanceof 체크와 캐스팅을 한 줄에 처리하며, 타입이 맞지 않으면 즉시 반환합니다.

4-3. 이벤트 버스 구분

26.1.2부터 @EventBusSubscriber의 bus 속성과 EventBusSubscriber.Bus enum이 삭제되었습니다. 더 이상 어노테이션에서 버스를 지정하지 않으며, 각 이벤트는 자신이 게시되는 버스가 정해져 있습니다.

이벤트 종류게시 버스예시
모드 로딩 이벤트모드 버스 (mod event bus)RegisterEvent, FMLCommonSetupEvent
게임 플레이 이벤트게임 버스 (NeoForge.EVENT_BUS)LivingDeathEvent, PlayerInteractEvent

LivingDeathEvent와 PlayerInteractEvent는 모두 게임 버스 이벤트이고, @EventBusSubscriber(modid = MasterCapstoneMod.MOD_ID)로 등록한 정적 핸들러가 자동으로 게임 버스에 연결됩니다. 레지스트리 이벤트(RegisterEvent, FMLCommonSetupEvent)는 모드 버스에 게시됩니다.


5. 교차 모듈 참조 전략

CapstoneEventHandler는 master_tools의 RUBY 아이템과 PURITY DataComponent를 직접 import하지 않습니다. 대신 태그와 런타임 레지스트리 조회를 사용합니다.

// ✅ 태그로 느슨하게 연결 — master_tools 내부 클래스 import 없음
public static final TagKey<Item> RUBY_ITEM_TAG =
        ItemTags.create(Identifier.fromNamespaceAndPath("master_tools", "ruby_items"));
 
// ✅ 런타임 조회 — DataComponentType 직접 import 없음
@SuppressWarnings("unchecked")
public static final DataComponentType<Integer> PURITY_COMPONENT =
        (DataComponentType<Integer>) BuiltInRegistries.DATA_COMPONENT_TYPE
                .getValue(Identifier.fromNamespaceAndPath("master_tools", "purity"));

이렇게 하면 master_tools JAR 없이 캡스톤 모드가 컴파일됩니다. 런타임에 master_tools가 없으면 PURITY_COMPONENT는 null이 되어 has() 검사를 통과하지 못하므로 안전합니다.

💡 더 견고한 방법
master_tools가 공개 API 클래스를 제공한다면 직접 참조하는 것이 더 명확합니다.
예: MasterToolsMod.RUBY.get(), MasterToolsMod.PURITY.get()
이 챕터는 JAR 간 느슨한 연결 패턴을 보여주기 위해 태그/레지스트리 조회 방식을 택했습니다.


6. 안티패턴 — 이벤트 중복 등록

⚠️ 이벤트 중복 등록 → 이중 적용

// ❌ 두 EventBusSubscriber에서 같은 이벤트 처리
@EventBusSubscriber(modid = MOD_ID)
public class Handlers1 {
    @SubscribeEvent
    public static void onDeath(LivingDeathEvent e) { /* 드롭 1회 */ }
}
 
@EventBusSubscriber(modid = MOD_ID)
public class Handlers2 {
    @SubscribeEvent
    public static void onDeath(LivingDeathEvent e) { /* 드롭 또 1회 */ }
}

→ 이벤트 두 번 호출 → 드롭 2배, 메시지 중복

핸들러는 한 클래스에 정리하거나 책임을 명확히 분리하세요. CapstoneEventHandler처럼 모든 캡스톤 이벤트를 한 파일에 모으는 것을 권장합니다.


7. neoforge.mods.toml에서 이벤트 버스 선언

@EventBusSubscriber는 NeoForge가 모드 로드 시 자동으로 스캔합니다. 단, neoforge.mods.toml에서 해당 모드가 올바르게 선언되어야 합니다.

[[mods]]
modId = "master_capstone"
# ... 기타 선언
 
[[dependencies.master_capstone]]
modId = "master_tools"
type = "required"
ordering = "AFTER"
side = "BOTH"

ordering = "AFTER"가 누락되면 master_tools의 레지스트리가 아직 초기화되지 않은 상태에서 캡스톤 이벤트 핸들러가 실행되어 NullPointerException이 발생할 수 있습니다.


8. 이 챕터의 완료 기준

  • CapstoneEventHandler가 게임 버스(NeoForge.EVENT_BUS)에 등록됩니다.
  • GemGuardian 처치 시 MagicCore 아이템이 드롭됩니다.
  • 서버 사이드 가드(isClientSide())가 적용됩니다.
  • 처치자가 플레이어인 경우 §b마법의 코어를 획득했습니다! 메시지가 표시됩니다.
  • Ruby 우클릭 시 PURITY ≥ 75이면 §a고순도 보석 — 차원 입장 강화 메시지가 표시됩니다.
  • 다음 챕터(05-sound-particle-polish)에서 사운드·파티클 효과를 보강합니다.

차원 전용 새 몹 — GemGuardian

Magic Realm 전용 적대 몹 GemGuardian을 Monster 상속으로 구현합니다. EntityType 등록, AttributeSupplier, 4큐브 GemGuardianModel, MobRenderer 설정까지 단계별로 다룹니다.

사운드·파티클 폴리시

SoundEvent와 ParticleType을 DeferredRegister로 등록하고, 포탈 진입·기계 완료·보스 처치·보석 사용 4가지 시점에 효과를 트리거하는 방법을 다룹니다.

On this page

이벤트로 모듈 연결1. 모듈 연결 흐름2. MagicCore 아이템 등록3. CapstoneEventHandler 구현4. 코드 분석 — 핵심 패턴4-1. isClientSide() 가드4-2. 패턴 매칭 instanceof4-3. 이벤트 버스 구분5. 교차 모듈 참조 전략6. 안티패턴 — 이벤트 중복 등록7. neoforge.mods.toml에서 이벤트 버스 선언8. 이 챕터의 완료 기준
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