[2025_11_03]EventBus

2025. 11. 3. 20:53TIL

개요

Event Bus는 중앙 집중식 이벤트 관리 시스템으로, 객체들이 직접 참조 없이 이벤트로 통신할 수 있게 해주는 패턴이다.

기존 방식 (강한 결합)

 
csharp
public class Player : MonoBehaviour
{
    public UIManager uiManager;
    public SoundManager soundManager;
    
    private void Die()
    {
        uiManager.ShowGameOverUI();
        soundManager.PlayDeathSound();
        // 모든 매니저를 알고 있어야 함
    }
}

Event Bus 방식 (약한 결합)

 
csharp
public class Player : MonoBehaviour
{
    private void Die()
    {
        EventBus<PlayerDeathEvent>.Publish(new PlayerDeathEvent());
    }
}

public class UIManager : MonoBehaviour
{
    private void OnEnable()
    {
        EventBus<PlayerDeathEvent>.Subscribe(OnPlayerDeath);
    }
    
    private void OnDisable()
    {
        EventBus<PlayerDeathEvent>.Unsubscribe(OnPlayerDeath);
    }
    
    private void OnPlayerDeath(PlayerDeathEvent e)
    {
        ShowGameOverUI();
    }
}

구현

Event Bus 클래스

 
csharp
using System;
using System.Collections.Generic;
using UnityEngine;

public static class EventBus<T> where T : IEvent
{
    private static readonly HashSet<Action<T>> _subscribers = new HashSet<Action<T>>();

    public static void Subscribe(Action<T> subscriber)
    {
        _subscribers.Add(subscriber);
    }

    public static void Unsubscribe(Action<T> subscriber)
    {
        _subscribers.Remove(subscriber);
    }

    public static void Publish(T eventData)
    {
        foreach (var subscriber in _subscribers)
        {
            try
            {
                subscriber?.Invoke(eventData);
            }
            catch (Exception ex)
            {
                Debug.LogError($"이벤트 처리 중 오류: {ex.Message}");
            }
        }
    }
}

public interface IEvent { }

이벤트 정의

 
csharp
public struct PlayerDeathEvent : IEvent
{
    public Vector3 DeathPosition;
    public string CauseOfDeath;
}

public struct PlayerHealthChangedEvent : IEvent
{
    public float CurrentHealth;
    public float MaxHealth;
}

public struct CoinCollectedEvent : IEvent
{
    public int Amount;
}

사용 예시

발행자

 
 
csharp
public class Player : MonoBehaviour
{
    public void TakeDamage(float damage)
    {
        currentHealth -= damage;
        
        EventBus<PlayerHealthChangedEvent>.Publish(new PlayerHealthChangedEvent
        {
            CurrentHealth = currentHealth,
            MaxHealth = maxHealth
        });

        if (currentHealth <= 0)
        {
            EventBus<PlayerDeathEvent>.Publish(new PlayerDeathEvent());
        }
    }
}

구독자

 
csharp
public class HealthBarUI : MonoBehaviour
{
    private void OnEnable()
    {
        EventBus<PlayerHealthChangedEvent>.Subscribe(OnHealthChanged);
    }

    private void OnDisable()
    {
        EventBus<PlayerHealthChangedEvent>.Unsubscribe(OnHealthChanged);
    }

    private void OnHealthChanged(PlayerHealthChangedEvent e)
    {
        healthSlider.value = e.CurrentHealth / e.MaxHealth;
    }
}

public class SoundManager : MonoBehaviour
{
    private void OnEnable()
    {
        EventBus<PlayerDeathEvent>.Subscribe(OnPlayerDeath);
    }

    private void OnDisable()
    {
        EventBus<PlayerDeathEvent>.Unsubscribe(OnPlayerDeath);
    }

    private void OnPlayerDeath(PlayerDeathEvent e)
    {
        audioSource.PlayOneShot(deathSound);
    }
}

주의사항

1. 반드시 구독 해제

 
 
csharp
// OnEnable에서 구독
private void OnEnable()
{
    EventBus<PlayerDeathEvent>.Subscribe(OnPlayerDeath);
}

// OnDisable에서 구독 해제 필수 (메모리 누수 방지)
private void OnDisable()
{
    EventBus<PlayerDeathEvent>.Unsubscribe(OnPlayerDeath);
}

2. struct 사용 권장

 
csharp
// struct 권장 (GC 부담 적음)
public struct PlayerDeathEvent : IEvent { }

// class는 GC 발생
public class PlayerDeathEvent : IEvent { }

3. 순환 참조 방지

 
csharp
// 이벤트 처리 중 같은 이벤트 발행 금지 (무한 루프)
private void OnHealthChanged(PlayerHealthChangedEvent e)
{
    // 같은 이벤트 발행하지 말 것
    EventBus<PlayerHealthChangedEvent>.Publish(new PlayerHealthChangedEvent());
}

장단점

장점단점

낮은 결합도 디버깅 어려움
높은 확장성 약간의 성능 오버헤드
테스트 용이 메모리 누수 위험

사용 가이드

Event Bus 사용이 적합한 경우:

  • 여러 시스템이 같은 이벤트에 반응해야 할 때
  • 시스템 간 결합도를 낮추고 싶을 때
  • 동적으로 리스너를 추가/제거해야 할 때

직접 참조가 나은 경우:

  • 단순한 부모-자식 관계
  • 성능이 매우 중요한 경우
  • 명확한 1:1 관계

핵심 정리

  1. Subscribe는 OnEnable, Unsubscribe는 OnDisable에서 처리
  2. 이벤트는 struct로 정의
  3. 구독 해제를 절대 잊지 말 것
  4. 순환 참조 주의

Event Bus는 확장 가능하고 유지보수하기 쉬운 시스템 구조를 만들 때 유용한 패턴이다.

 

다음 프로젝트 때는 꼭 Event Bus패턴을 사용해야겠다고 다짐했다.