Development/Minecraft Plugin

[Plugin] #4 이벤트 리스너 구현하기

Jeyeon 2025. 11. 5. 15:37
반응형

지난 글에서는 명령어를 등록하고 실행하는 방법, 그리고 자동완성 기능을 구현하는 방법을 살펴보았다.  
이번에는 마인크래프트 서버 내부에서 발생하는 이벤트(Event)를 감지하고 처리하는 방법을 알아보자.  
이벤트 시스템은 Bukkit API의 핵심 개념 중 하나이며, 플러그인 개발에서 가장 자주 사용되는 기능 중 하나다.


이벤트 시스템의 개념과 구조

마인크래프트 서버는 단순한 루프 기반 프로그램이 아니라, 내부적으로 **이벤트 기반(Event-Driven Architecture)** 으로 동작한다.
이 구조의 핵심은 “서버에서 일어나는 모든 행동을 이벤트로 감지하고, 그에 대한 반응을 개발자가 정의할 수 있다”는 점이다.  


예를 들어 다음과 같은 상황을 생각해 보자.

  • 플레이어가 서버에 접속한다 → PlayerJoinEvent
  • 플레이어가 블록을 부순다 → BlockBreakEvent
  • 몹이 플레이어를 공격한다 → EntityDamageByEntityEvent
  • 플레이어가 채팅을 입력한다 → AsyncPlayerChatEvent

이 모든 동작은 서버 내부에서 이벤트 객체(Event Object)로 생성되어 이를 감지하는 리스너(Listener)에게 전달된다.  

즉, 이벤트 리스너는 서버와 플러그인 사이의 '센서' 같은 역할을 한다. 서버가 특정 행동을 감지하면 그에 대응하는 이벤트를 발생시키고 리스너는 그 이벤트를 수신하여 우리가 작성한 로직을 수행하게 된다.  


이벤트 리스너의 기본 구조

이벤트를 감지할 클래스는 org.bukkit.event.Listener 인터페이스를 구현해야 한다.

실제로 Listener 인터페이스에는 구현해야 하는 메서드 리스트는 없는 Flag 인터페이스의 역할을 한다.

그리고 Listener 인터페이스를  구현하는 클래스 아래에 이벤트 메서드를 작성하게 된다.

package io.github.jy2694.tistoryseries.event;

import org.bukkit.event.Listener;
import org.bukkit.event.EventHandler;

public class ExampleListener implements Listener {

    @EventHandler
    public void 메소드명(이벤트클래스 event) {
        // 이벤트 발생 시 수행할 로직
    }
}


이처럼 매우 간단한 구조로 되어 있다.  

메서드명은 별도의 제약은 없고 메서드에는 @EventHandler 어노테이션이 있어야 한다는 점과 매개변수로 이벤트 클래스(Event를 상속하는 클래스)가 하나만 작성되어야 한다는 규칙이 있다.

 

이번 예제에서는 Bukkit API에서 제공하는 PlayerJoinEvent를 사용해 보자.

PlayerJoinEvent는 클래스 이름에서도 직관적으로 알 수 있는 사용자가 서버에 들어오면 발생하는 이벤트이다.

 

package io.github.jy2694.tistoryseries.event;

import org.bukkit.event.Listener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;

public class ExampleListener implements Listener {

    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent event) {
        System.out.println("Player Joined!");
    }
}

이벤트 리스너 등록하기

이제 작성한 리스너를 서버에 등록해야 한다. 리스너는 단순히 클래스를 선언했다고 자동으로 감지되지 않는다. Bukkit의 PluginManager를 통해 등록해야만 동작한다.

package io.github.jy2694.tistoryseries;

import io.github.jy2694.tistoryseries.event.ExampleListener;
import org.bukkit.plugin.java.JavaPlugin;

public class TistorySeries extends JavaPlugin {

    @Override
    public void onEnable() {
        getServer().getPluginManager().registerEvents(new ExampleListener(), this);
    }
}

 

이 코드를 실행하면, 이제 서버가 구동될 때 `ExampleListener` 가 등록되고 플레이어가 접속할 때마다 해당 리스너가 이벤트를 수신하게 된다.


이벤트 객체의 속성과 활용법

각 이벤트 객체에는 상황에 맞는 다양한 정보가 포함되어 있다.  
PlayerJoinEvent의 경우 다음과 같은 주요 메서드가 존재한다.

  • event.getPlayer() → 접속한 플레이어 객체 반환
  • event.setJoinMessage(String message) → 서버에 출력될 접속 메시지 변경

예를 들어 다음처럼 작성하면 기본 접속 메시지를 변경할 수 있다.

@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
    event.setJoinMessage("✨ " + event.getPlayer().getName() + " 님이 서버에 입장하셨습니다!");
}


이런 방식으로 이벤트 객체가 제공하는 정보를 조합하면 매우 다양한 상황에 대응할 수 있다.

 

예를 들어 BlockBreakEvent를 사용하면 특정 블록만 부술 수 있게 만들거나 EntityDamageEvent를 사용해 특정 조건에서 피해를 무효화하는 등 게임플레이를 완전히 바꿔버리는 수준의 로직도 구현할 수 있다.


이벤트의 흐름과 처리 우선순위

이벤트는 서버 내에서 여러 플러그인이 동시에 감지할 수 있다.

예를 들어 BlockBreakEvent가 발생했을 때 다른 플러그인도 같은 이벤트를 수신할 수 있다.
이때는 이벤트의 우선순위(Priority)를 지정하여 호출 순서를 제어할 수 있다.

공식 문서상 해당 우선운위가 절대적으로 보장된다고 장담하지 않는다.
의도대로 최대한 맞추기 위한 수단이라고 설명하고 있다.

 

Bukkit은 다음 6단계의 우선순위를 제공한다.

  1. EventPriority.LOWEST
  2. EventPriority.LOW
  3. EventPriority.NORMAL
  4. EventPriority.HIGH
  5. EventPriority.HIGHEST
  6. EventPriority.MONITOR

보통 NORMAL 이 기본값이며 가장 나중에 감지하고 싶다면 MONITOR를 가장 먼저 감지하고 싶다면 LOWEST를 사용한다.

 

다음 예시를 보자.

@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockBreak(BlockBreakEvent event) {
    event.getPlayer().sendMessage("블록을 부쉈습니다!");
}

 

 

여기서 priority는 호출 순서를 의미하고 ignoreCancelled = true는 이미 취소된 이벤트는 무시하겠다는 뜻이다.  
또한 event.setCancelled(true)를 호출하면 그 이벤트를 완전히 막을 수 있다.

예를 들어 블록이 실제로 부서지지 않게 만들 수도 있다.


다른 이벤트 예시 및 참고 자료

Bukkit API에는 수많은 이벤트가 존재한다.

그중 자주 사용되는 몇 가지를 표로 정리하면 다음과 같다.

이벤트 설명
PlayerJoinEvent 플레이어가 서버에 접속하였을 때 발생
BlockBreakEvent 블록이 부서졌을 때 발생
BlockPlaceEvent 블록이 설치되었을 때 발생
EntityDamageByEntityEvent 엔티티가 다른 엔티티에게 공격받았을 때 발생
AsyncPlayerChatEvent 플레이어가 채팅을 입력할 때 발생
InventoryClickEvent 인벤토리 슬롯이 클릭될 때 발생
PlayerMoveEvent 플레이어가 이동할 때 발생
PlayerInteractEvent 플레이어가 블록을 클릭하거나 아이템을 사용할 때 발생


이 외에도 날씨 변화, 포션 효과, 인벤토리 열기, 월드 로딩 등 게임 내의 거의 모든 사건을 감지할 수 있다.

더 많은 이벤트 목록은 Bukkit 공식 문서에서 확인할 수 있다.

 

org.bukkit.event (Spigot-API 1.21.10-R0.1-SNAPSHOT API)

package org.bukkit.event Classes dedicated to handling triggered code executions. Copyright © 2025. All rights reserved.

hub.spigotmc.org

이 문서에는 모든 이벤트 클래스가 정리되어 있으며 각 이벤트마다 어떤 정보를 제공하는지도 확인할 수 있다.

반응형