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

플레이어 이벤트

PlayerLoggedInEvent, LivingHurtEvent, LivingDeathEvent 등 주요 플레이어/Living 이벤트를 구현하고, 사이드 분기 처리로 클라이언트-서버 동기화 문제를 예방합니다.

플레이어와 관련된 이벤트는 NeoForge 모딩에서 가장 자주 쓰는 카테고리입니다. 접속 환영 메시지부터 데미지 조정, 사망 처리까지 게임플레이의 핵심 흐름을 제어할 수 있습니다.

이 챕터에서는 네 가지 핵심 이벤트를 직접 구현하고, 양 사이드(클라이언트/서버)에서 호출되는 이벤트를 올바르게 처리하는 방법을 배웁니다.


이벤트 클래스 준비

모든 플레이어 이벤트 핸들러를 하나의 클래스에 모읍니다. @EventBusSubscriber로 자동 등록합니다.

package com.example.examplemod.events;
 
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
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.living.LivingDeathEvent;
import net.neoforged.neoforge.event.entity.living.LivingHurtEvent;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent;
 
@EventBusSubscriber(modid = "examplemod")
public class PlayerEventHandler {
    // 핸들러 메서드들이 여기 들어갑니다
}

@EventBusSubscriber는 클래스 내 static 핸들러를 자동으로 NeoForge 이벤트 버스에 등록합니다. modid는 반드시 mods.toml의 mod ID와 일치해야 합니다.


1. PlayerLoggedInEvent — 접속 환영 메시지

플레이어가 서버에 접속할 때 발생합니다. 환영 메시지, 초기 아이템 지급, 통계 초기화 등에 활용합니다.

@SubscribeEvent
public static void onLogin(PlayerEvent.PlayerLoggedInEvent event) {
    if (event.getEntity() instanceof ServerPlayer player) {
        player.sendSystemMessage(
            Component.literal("§a환영합니다, " + player.getName().getString() + "!")
        );
    }
}

instanceof ServerPlayer 패턴 매칭으로 서버 플레이어임을 확인합니다. PlayerLoggedInEvent는 서버에서만 발생하지만, 명시적 타입 확인으로 의도를 분명히 합니다.

§a는 초록색 텍스트 색상 코드입니다. 더 복잡한 스타일링이 필요하면 Component.literal(...).withStyle(ChatFormatting.GREEN) 방식을 권장합니다.

ℹ️

📷 스크린샷 자리 (직접 캡처해 추가하세요)

플레이어 접속 시 환영 메시지가 채팅창에 표시된 화면


2. LivingHurtEvent — 데미지 감쇄

엔티티가 피해를 받을 때 발생합니다. Cancelable 인터페이스를 구현하므로 취소하거나 데미지 양을 조정할 수 있습니다.

@SubscribeEvent
public static void onHurt(LivingHurtEvent event) {
    if (!event.getEntity().level().isClientSide()) {
        if (event.getEntity() instanceof Player) {
            event.setAmount(event.getAmount() * 0.5f);  // 데미지 50% 감소
        }
    }
}

isClientSide() 가드가 핵심입니다. LivingHurtEvent는 클라이언트와 서버 양쪽에서 호출됩니다. 데미지 수치 변경은 서버 권위 데이터이므로 서버에서만 처리해야 합니다.

⚠️ 사이드 분기 누락 — 이중 적용

// ❌ 양 사이드에서 데미지 감쇄 → 클라+서버 동기화 깨짐
public static void onHurt(LivingHurtEvent event) {
    event.setAmount(event.getAmount() * 0.5f);  // 가드 없이
}
 
// ✅ 서버에서만
public static void onHurt(LivingHurtEvent event) {
    if (!event.getEntity().level().isClientSide()) {
        event.setAmount(event.getAmount() * 0.5f);
    }
}

데미지/상태 변경은 서버 권위입니다. 클라이언트는 동기화만 담당합니다. 가드 없이 양 사이드에서 실행하면 클라이언트 예측값과 서버 실제값이 어긋나 체력 표시 버그가 발생합니다.

이벤트 취소

데미지를 완전히 막으려면 setCanceled(true)를 사용합니다.

@SubscribeEvent
public static void onHurt(LivingHurtEvent event) {
    if (!event.getEntity().level().isClientSide()) {
        if (event.getEntity() instanceof Player player) {
            // 특정 조건에서 무적 처리
            if (player.hasEffect(MobEffects.ABSORPTION)) {
                event.setCanceled(true);
            }
        }
    }
}

LivingHurtEvent는 ICancellableEvent를 구현합니다. 취소하면 데미지 계산 파이프라인 전체가 중단됩니다.


3. LivingDeathEvent — 사망 처리

엔티티가 사망할 때 발생합니다. 사망 직전 상태이므로 아이템 지급, 통계 기록, 특수 리스폰 로직 등을 처리할 수 있습니다.

@SubscribeEvent
public static void onDeath(LivingDeathEvent event) {
    if (!event.getEntity().level().isClientSide()) {
        if (event.getEntity() instanceof ServerPlayer player) {
            // 사망 시 부활 보너스 아이템 지급
            player.getInventory().add(
                new ItemStack(Items.GOLDEN_APPLE)
            );
        }
    }
}

LivingDeathEvent도 양 사이드에서 발생합니다. 인벤토리 조작은 서버에서만 해야 합니다.

이 이벤트는 취소 가능합니다. event.setCanceled(true)로 사망 자체를 막을 수 있지만, 체력이 0 이하인 상태가 유지되므로 즉시 체력을 회복시켜야 합니다.

@SubscribeEvent
public static void onDeath(LivingDeathEvent event) {
    if (!event.getEntity().level().isClientSide()) {
        if (event.getEntity() instanceof ServerPlayer player) {
            // 사망 취소 + 체력 회복 (부활 효과)
            event.setCanceled(true);
            player.setHealth(1.0f);
        }
    }
}

4. PlayerInteractEvent.RightClickItem — 아이템 우클릭

플레이어가 아이템을 우클릭할 때 발생합니다. 아이템 자체의 use() 메서드보다 먼저 호출되므로 기존 동작을 가로채거나 조건부로 막을 수 있습니다.

@SubscribeEvent
public static void onRightClickItem(PlayerInteractEvent.RightClickItem event) {
    if (!event.getEntity().level().isClientSide()) {
        Player player = event.getEntity();
        ItemStack stack = event.getItemStack();
 
        if (stack.is(Items.STICK)) {
            player.sendSystemMessage(
                Component.literal("막대기를 우클릭했습니다!")
            );
            // 기본 동작을 막으려면:
            // event.setCanceled(true);
        }
    }
}

PlayerInteractEvent는 여러 하위 이벤트로 나뉩니다.

이벤트발생 시점
RightClickItem아이템 우클릭 (공중)
RightClickBlock블록 우클릭
LeftClickBlock블록 좌클릭 (채굴 시작)
EntityInteract엔티티 우클릭

사이드 확인 요약

이벤트별 사이드 호출 여부를 정리합니다.

이벤트클라이언트서버권장 처리
PlayerLoggedInEvent❌✅서버 전용 — 가드 불필요
LivingHurtEvent✅✅isClientSide() 가드 필수
LivingDeathEvent✅✅isClientSide() 가드 필수
RightClickItem✅✅isClientSide() 가드 필수

서버 전용 이벤트라도 instanceof ServerPlayer 패턴 매칭으로 의도를 명확히 하는 습관을 들이세요. 코드 리뷰 시 사이드 처리 의도가 바로 보입니다.


전체 핸들러 클래스

지금까지 구현한 핸들러를 하나로 모읍니다.

package com.example.examplemod.events;
 
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.event.entity.living.LivingDeathEvent;
import net.neoforged.neoforge.event.entity.living.LivingHurtEvent;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent;
 
@EventBusSubscriber(modid = "examplemod")
public class PlayerEventHandler {
 
    @SubscribeEvent
    public static void onLogin(PlayerEvent.PlayerLoggedInEvent event) {
        if (event.getEntity() instanceof ServerPlayer player) {
            player.sendSystemMessage(
                Component.literal("§a환영합니다, " + player.getName().getString() + "!")
            );
        }
    }
 
    @SubscribeEvent
    public static void onHurt(LivingHurtEvent event) {
        if (!event.getEntity().level().isClientSide()) {
            if (event.getEntity() instanceof Player) {
                event.setAmount(event.getAmount() * 0.5f);
            }
        }
    }
 
    @SubscribeEvent
    public static void onDeath(LivingDeathEvent event) {
        if (!event.getEntity().level().isClientSide()) {
            if (event.getEntity() instanceof ServerPlayer player) {
                player.getInventory().add(new ItemStack(Items.GOLDEN_APPLE));
            }
        }
    }
 
    @SubscribeEvent
    public static void onRightClickItem(PlayerInteractEvent.RightClickItem event) {
        if (!event.getEntity().level().isClientSide()) {
            Player player = event.getEntity();
            ItemStack stack = event.getItemStack();
 
            if (stack.is(Items.STICK)) {
                player.sendSystemMessage(
                    Component.literal("막대기를 우클릭했습니다!")
                );
            }
        }
    }
}

다음 챕터

다음 챕터에서는 블록과 월드 관련 이벤트를 다룹니다. 블록 파괴, 청크 로드, 날씨 변경 등 월드 레벨 이벤트를 처리하는 방법을 배웁니다.

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

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

틱 이벤트 — 매 tick 실행되는 로직 작성

ServerTickEvent, ClientTickEvent, EntityTickEvent, LevelTickEvent의 종류와 사용법을 배우고, 카운터 패턴으로 TPS를 지키는 성능 최적화 기법을 익힙니다.

On this page

이벤트 클래스 준비1. PlayerLoggedInEvent — 접속 환영 메시지2. LivingHurtEvent — 데미지 감쇄이벤트 취소3. LivingDeathEvent — 사망 처리4. PlayerInteractEvent.RightClickItem — 아이템 우클릭사이드 확인 요약전체 핸들러 클래스다음 챕터
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