NeoForge 26.1 Docs
  • 문서
  • 노트
NeoForge 26.1
NeoForge 26.1
v26.1
학습 진도
0 / 77 챕터 완료방문 0
이벤트 핸들러와 @SubscribeEventModBus vs NeoForge EventBus — 두 버스 완전 정복플레이어 이벤트틱 이벤트 — 매 tick 실행되는 로직 작성커맨드 등록 — Brigadier로 /명령어 만들기사운드 등록과 재생파티클 등록과 스폰 — 시각 효과 직접 만들기
이벤트

이벤트 핸들러와 @SubscribeEvent

NeoForge 26의 이벤트 핸들러 3가지 패턴(@EventBusSubscriber, addListener, register)과 올바른 핸들러 시그니처 규칙을 설명합니다.

NeoForge의 이벤트 시스템은 모드가 게임 로직에 끼어들 수 있는 핵심 통로입니다. 플레이어가 로그인할 때, 블록이 파괴될 때, 엔티티가 스폰될 때 — 이 모든 순간에 이벤트가 발생하고, 핸들러가 그 이벤트를 받아 처리합니다.

이 챕터에서는 핸들러를 등록하는 세 가지 방법과 각각의 적합한 사용 시나리오를 다룹니다.


이벤트 버스 두 가지

핸들러를 작성하기 전에 버스 구분부터 짚고 넘어갑니다. NeoForge에는 이벤트 버스가 두 개 있습니다.

버스접근 방법용도
MOD 버스생성자 인자 IEventBus modEventBus모드 초기화 라이프사이클 (레지스트리, 설정, 클라이언트 셋업)
GAME 버스NeoForge.EVENT_BUS게임 플레이 이벤트 (플레이어, 블록, 엔티티, 월드)

NeoForge 26.1.2부터 @EventBusSubscriber로 등록할 때는 핸들러가 받는 이벤트 타입을 보고 어느 버스인지 자동으로 결정됩니다 (bus 속성과 EventBusSubscriber.Bus enum은 삭제됨). 직접 등록할 때는 위 표의 접근 방법을 사용하며, 잘못된 버스에 핸들러를 등록하면 이벤트가 아예 발생하지 않습니다. 에러도 없이 조용히 무시됩니다. 버스 선택은 항상 이벤트 클래스의 Javadoc을 확인하세요.


패턴 A: @EventBusSubscriber + static 메소드 (권장)

가장 간결하고 NeoForge 공식 예제에서 가장 많이 쓰는 방식입니다. 클래스에 @EventBusSubscriber를 붙이면 NeoForge가 모드 로딩 시 자동으로 해당 클래스를 스캔해 @SubscribeEvent가 붙은 static 메소드를 등록합니다.

// examplemod-template-26.1.2/src/main/java/com/example/examplemod/events/GameEvents.java
package com.example.examplemod.events;
 
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Player;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
 
@EventBusSubscriber(modid = "examplemod")
public class GameEvents {
 
    @SubscribeEvent
    public static void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) {
        Player player = event.getEntity();
        player.sendSystemMessage(Component.literal("환영합니다, " + player.getName().getString() + "!"));
    }
}

이 방식의 장점:

  • 별도 등록 코드가 필요 없습니다. 어노테이션만으로 완결됩니다.
  • 클래스 단위로 이벤트를 묶어 관리하기 좋습니다.
  • 테스트 시 클래스를 직접 참조하기 쉽습니다.

주의: @EventBusSubscriber를 붙인 클래스의 핸들러 메소드는 반드시 static이어야 합니다. 인스턴스 메소드를 쓰면 NeoForge가 등록을 거부합니다.


패턴 B: addListener로 람다 등록

모드 메인 클래스 생성자에서 직접 리스너를 추가하는 방식입니다. 람다나 메소드 참조를 쓸 수 있어 간단한 핸들러에 적합합니다.

// examplemod-template-26.1.2/src/main/java/com/example/examplemod/ExampleMod.java
package com.example.examplemod;
 
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.common.Mod;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
 
@Mod("examplemod")
public class ExampleMod {
 
    public ExampleMod(IEventBus modEventBus) {
        // GAME 버스에 인스턴스 메소드 참조로 등록
        NeoForge.EVENT_BUS.addListener(this::onPlayerLogin);
    }
 
    private void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) {
        // 핸들러 로직
    }
}

이 방식의 장점:

  • 핸들러가 인스턴스 상태에 접근해야 할 때 유용합니다.
  • 등록 시점이 명확합니다 (생성자 실행 순서대로).
  • 람다로 짧게 쓸 수 있습니다.

단점: 핸들러가 많아지면 생성자가 길어집니다. 이 경우 패턴 A나 C로 분리하는 게 낫습니다.


패턴 C: register(instance)로 인스턴스 등록

핸들러 클래스를 별도로 만들고 인스턴스를 버스에 등록하는 방식입니다. 패턴 A와 달리 static이 아닌 인스턴스 메소드를 쓸 수 있습니다.

// 핸들러 클래스 (static 불필요)
package com.example.examplemod.events;
 
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Player;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
 
public class GameEventHandler {
 
    @SubscribeEvent
    public void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) {
        Player player = event.getEntity();
        player.sendSystemMessage(Component.literal("환영합니다!"));
    }
}
// 모드 메인 클래스에서 등록
@Mod("examplemod")
public class ExampleMod {
 
    public ExampleMod(IEventBus modEventBus) {
        NeoForge.EVENT_BUS.register(new GameEventHandler());
    }
}

이 방식의 장점:

  • 핸들러 클래스가 생성자 인자를 받을 수 있습니다 (의존성 주입 친화적).
  • 인스턴스 상태를 유지하는 복잡한 핸들러에 적합합니다.

단점: 패턴 A보다 코드가 많습니다. 단순한 경우엔 과한 구조입니다.


세 패턴 비교

패턴 A패턴 B패턴 C
등록 방식어노테이션 자동addListener 수동register 수동
메소드 타입static 필수인스턴스 가능인스턴스 가능
코드량최소중간중간
추천 상황대부분의 경우간단한 핸들러 1~2개상태 있는 핸들러

핸들러 시그니처 규칙

NeoForge는 핸들러 메소드 시그니처를 엄격하게 검사합니다. 규칙은 세 가지입니다.

  1. 반환 타입은 void — 다른 타입은 허용하지 않습니다.
  2. 인자는 정확히 1개 — 이벤트 타입 하나만 받습니다.
  3. 패턴에 맞는 static/instance — @EventBusSubscriber에서는 static, register(instance)에서는 instance.

⚠️ 잘못된 핸들러 시그니처

// ❌ 반환 타입이 void가 아님
@SubscribeEvent
public static int onLogin(PlayerEvent.PlayerLoggedInEvent e) { return 0; }
 
// ❌ 인자가 2개
@SubscribeEvent
public static void onLogin(PlayerEvent.PlayerLoggedInEvent e, Player p) { }
 
// ❌ @EventBusSubscriber 클래스에서 static 누락
@EventBusSubscriber(modid = "examplemod")
public class Events {
    @SubscribeEvent
    public void onLogin(PlayerEvent.PlayerLoggedInEvent e) { } // static 빠짐
}

위 경우 NeoForge가 "Method @SubscribeEvent is invalid" 에러를 던지며 모드 로딩이 실패합니다.


이벤트 우선순위

같은 이벤트를 여러 핸들러가 받을 때 실행 순서를 제어할 수 있습니다. @SubscribeEvent의 priority 속성을 씁니다.

@SubscribeEvent(priority = EventPriority.HIGH)
public static void onPlayerLoginFirst(PlayerEvent.PlayerLoggedInEvent event) {
    // 다른 핸들러보다 먼저 실행
}
 
@SubscribeEvent(priority = EventPriority.LOW)
public static void onPlayerLoginLast(PlayerEvent.PlayerLoggedInEvent event) {
    // 다른 핸들러보다 나중에 실행
}

우선순위 순서: HIGHEST > HIGH > NORMAL (기본값) > LOW > LOWEST

대부분의 경우 기본값(NORMAL)으로 충분합니다. 다른 모드와 상호작용이 필요한 경우에만 우선순위를 조정하세요.


취소 가능한 이벤트

일부 이벤트는 취소할 수 있습니다. Cancelable 어노테이션이 붙은 이벤트 클래스가 대상입니다.

@SubscribeEvent
public static void onBlockBreak(BlockEvent.BreakEvent event) {
    // 특정 블록은 파괴 불가
    if (event.getState().is(Blocks.BEDROCK)) {
        event.setCanceled(true);
    }
}

취소 불가능한 이벤트에 setCanceled(true)를 호출하면 런타임 예외가 발생합니다. 이벤트 클래스 Javadoc에서 @Cancelable 여부를 먼저 확인하세요.


정리

  • 패턴 A (@EventBusSubscriber + static): 대부분의 경우 이걸 씁니다.
  • 패턴 B (addListener): 간단한 핸들러를 생성자에서 바로 등록할 때.
  • 패턴 C (register(instance)): 상태를 가진 핸들러 클래스가 필요할 때.
  • 핸들러 시그니처: void 반환, 인자 1개, static/instance 규칙 준수.
  • 버스 선택: MOD 버스는 초기화, GAME 버스는 게임플레이.

다음 챕터에서는 자주 쓰는 게임 이벤트들을 실제로 다뤄봅니다.

루트 테이블 — 블록 드롭 JSON 직접 작성

data/<modId>/loot_table/blocks/ 경로에 JSON을 직접 작성해 블록 채굴 드롭을 정의합니다. 단순 드롭, 변동 드롭, Fortune 보너스, 올바른 도구 조건까지 다룹니다.

ModBus vs NeoForge EventBus — 두 버스 완전 정복

모드 라이프사이클 이벤트를 처리하는 ModBus와 게임 런타임 이벤트를 처리하는 NeoForge EventBus의 차이를 이벤트 매핑 표와 안티패턴으로 명확히 정리합니다.

On this page

이벤트 버스 두 가지패턴 A: @EventBusSubscriber + static 메소드 (권장)패턴 B: addListener로 람다 등록패턴 C: register(instance)로 인스턴스 등록세 패턴 비교핸들러 시그니처 규칙이벤트 우선순위취소 가능한 이벤트정리
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