유니티에서 이벤트 버스(Event Bus) 패턴을 활용한 유연한 시스템 만들기

2025. 3. 29. 10:20유니티 unity/디자인패턴

게임을 만들다 보면, 시스템 간 소통이 복잡해지는 순간이 찾아옵니다. 처음에는 단순한 함수 호출로 충분했던 의사소통이, 프로젝트가 커질수록 꼬이기 시작하죠.

  • 플레이어가 아이템을 먹으면 → UI 갱신, 사운드 재생, 퀘스트 진행 업데이트?
  • 적이 죽으면 → 점수 증가, 드롭 아이템 생성, 미션 조건 확인?
  • 스테이지 클리어 → 저장, 씬 전환, 클리어 UI, 음악 정지?

이런 흐름을 매번 직접 연결하면 결국 시스템 간 의존도가 높아지고, 수정이 어려워지며, 디버깅도 힘들어집니다.

이 문제를 해결하기 위해 등장하는 구조가 바로 **이벤트 버스(Event Bus)**입니다.


이벤트 버스란?

이벤트 버스는 말 그대로 모든 이벤트를 중계하는 중앙 허브 역할을 하는 구조입니다.

  • 이벤트를 발생시키는 쪽은 ‘버스’에 신호만 보냅니다.
  • 이벤트를 듣고 싶은 쪽은 ‘버스’에서 구독합니다.

발신자와 수신자는 서로를 전혀 몰라도 됩니다. 둘 다 이벤트 버스만 알고 있으면 소통이 가능한 구조입니다.

즉, 전역적으로 사용 가능한 느슨한 이벤트 시스템을 만드는 방식입니다.


왜 옵저버 패턴보다 더 나은가요?

옵저버 패턴은 단일 객체에 여러 옵저버가 붙는 구조라면, 이벤트 버스는 게임 전체 범위의 옵저버 시스템이라고 생각하시면 됩니다.

옵저버 패턴 이벤트 버스

특정 객체에 종속됨 전역으로 이벤트 중계
주로 한 주체에 다수의 옵저버 다수의 주체와 다수의 수신자
범위가 제한적 범위가 광범위함 (게임 전체 이벤트 관리 가능)

옵저버 패턴은 로컬 이벤트 처리에 적합하고, 이벤트 버스는 게임 전체의 흐름 제어중앙 통신 구조에 적합합니다.


유니티에서 이벤트 버스 만들기

1. 이벤트 정의 (예: GameEvent)

public class GameEvent { }

public class ItemPickedEvent : GameEvent
{
    public string itemId;
    public ItemPickedEvent(string id) { itemId = id; }
}

public class EnemyKilledEvent : GameEvent
{
    public int enemyId;
    public EnemyKilledEvent(int id) { enemyId = id; }
}

2. 이벤트 버스 클래스

public static class EventBus
{
    private static Dictionary<Type, Action<GameEvent>> _eventTable = new();

    public static void Subscribe<T>(Action<T> callback) where T : GameEvent
    {
        Type type = typeof(T);
        if (!_eventTable.ContainsKey(type))
            _eventTable[type] = delegate { };

        _eventTable[type] += (e) => callback((T)e);
    }

    public static void Unsubscribe<T>(Action<T> callback) where T : GameEvent
    {
        Type type = typeof(T);
        if (_eventTable.ContainsKey(type))
            _eventTable[type] -= (e) => callback((T)e);
    }

    public static void Publish(GameEvent e)
    {
        Type type = e.GetType();
        if (_eventTable.ContainsKey(type))
            _eventTable[type]?.Invoke(e);
    }
}

3. 사용 예시 - 아이템 획득 시 이벤트 발생

public class Item : MonoBehaviour
{
    public string itemId;

    public void PickUp()
    {
        EventBus.Publish(new ItemPickedEvent(itemId));
        Destroy(gameObject);
    }
}

4. 사용 예시 - UI에서 아이템 이벤트 반응

public class InventoryUI : MonoBehaviour
{
    private void OnEnable()
    {
        EventBus.Subscribe<ItemPickedEvent>(OnItemPicked);
    }

    private void OnDisable()
    {
        EventBus.Unsubscribe<ItemPickedEvent>(OnItemPicked);
    }

    private void OnItemPicked(ItemPickedEvent e)
    {
        Debug.Log($"아이템 획득: {e.itemId}");
        // UI 업데이트 로직
    }
}

주의할 점

  • 메모리 누수 방지: Subscribe한 이벤트는 반드시 Unsubscribe 해주셔야 합니다.
  • 디버깅용 로그 추가 추천: 어떤 이벤트가 발생했고 누가 반응했는지 추적하기 쉽도록 로그 출력 구조를 함께 두는 것이 좋습니다.
  • 이벤트 타입 관리: 이벤트 타입이 많아지면 체계적인 네이밍과 분류가 중요합니다.

 

이벤트 버스는 유니티에서 복잡한 시스템 간의 의존성을 줄이고, 전체 이벤트 흐름을 하나의 중심 축으로 모을 수 있게 도와주는 강력한 설계 패턴입니다.

단일 이벤트보다는 게임 전반의 흐름을 제어하거나, 여러 시스템이 동시에 반응해야 할 때 더욱 큰 힘을 발휘합니다.

이벤트 버스를 통해 확장성과 유지보수성이 높은 구조를 경험해보시길 추천드립니다.