DevDino

[게임 디자인 패턴]이벤트 버스(Event Bus) 본문

Unity/디자인 패턴

[게임 디자인 패턴]이벤트 버스(Event Bus)

데브다이노 2026. 7. 14. 14:48

Event Bus

 

이벤트 버스(Event Bus)란?

이벤트 버스는 시스템 내의 다양한 컴포넌트들이 서로 직접적인 참조 없이 이벤트를 통해 데이터를 주고받을 수 있게 해주는 중앙 집중형 통신 메커니즘입니다. 

 

복잡한 시스템에서 객체들이 서로를 직접 참조(의존)하게 되면 복잡하게 얽히는 스파게티 코드가 되기 쉽습니다. 이벤트 버스는 중간에 '버스(Bus)'라는 통로를 두어 이벤트를 발행(Publish)하는 쪽과 이벤트를 구독(Subscribe)하는 쪽을 완전히 분리해 줍니다. 

 

특정 오브젝트가 어떤 사건(아이템 획득, 캐릭터 레벨업)이 일어났다는 신호를 시스템(이벤트 버스)에 브로드캐스트 하면, 그 신호를 구독한 오브젝트들만 알림을 받아 각자 맡은 기능을 수행하게 됩니다. 서로가 서로를 직접 알지 못해도 완벽하게 소통할 수 있는 구조가 되는 것입니다. 

 

이벤트 버스의 장단점

장점

- 결합도 감소(Decoupling): 발행자와 구독자가 서로의 존재를 알 필요가 없어 클래스 간 의존성이 크게 낮아집니다.

- 확장성 및 유연성: 새로운 기능을 추가할 때 기존 코드를 수정할 필요 없이 이벤트 버스에만 연결하면 됩니다. UI 업데이트, 도전과제 달성 등 여러 시스템이 동시에 반응해야 할 때 특히 유용합니다. 

 

단점

- 흐름 추적의 어려움: 이벤트가 어디서 발생해서 어디로 가는지 코드 흐름만으로 한눈에 파악하기 힘들어 디버깅이 까다로울 수 있습니다.

- 메모리 누수 위험: 이벤트를 구독한 뒤 적절한 시점에 해제(Unsubscribe)를 해주지 않으면 객체가 파괴된 이후에도 참조가 남아 에러나 메모리 누수가 발생할 수 있습니다. 

 

구현 예제

1. 이벤트 버스 구현하기

 

게임 내 어디서든 이벤트 버스에 접근할 수 있도록 싱글턴 패턴을 결합한 형태의 이벤트 버스입니다. 

using System;
using System.Collections.Generic;

// 이벤트 타입 정의
public enum EventType
{
    OnGameOver,
    OnLevelUp
}

public class EventBus
{
    // 싱글턴(Singleton) 구현
    private static EventBus _instance;
    public static EventBus Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new EventBus();
            }
            return _instance;
        }
    }

    private EventBus() { }


    // 이벤트를 관리할 딕셔너리
    private Dictionary<EventType, Action<object>> eventDictionary = new Dictionary<EventType, Action<object>>();

    // 구독 (Subscribe)
    public void Subscribe(EventType eventType, Action<object> listener)
    {
        if (eventDictionary.ContainsKey(eventType))
        {
            eventDictionary[eventType] += listener;
        }
        else
        {
            eventDictionary.Add(eventType, listener);
        }
    }

    // 구독 해제 (Unsubscribe)
    public void Unsubscribe(EventType eventType, Action<object> listener)
    {
        if (eventDictionary.ContainsKey(eventType))
        {
            eventDictionary[eventType] -= listener;
        }
    }

    // 발행 (Publish)
    public void Publish(EventType eventType, object param = null)
    {
        if (eventDictionary.ContainsKey(eventType))
        {
            eventDictionary[eventType]?.Invoke(param);
        }
    }
}

 

2. 이벤트 발행(Publish)하기

플레이어는 레벨 업을 하면 이벤트 버스에 레벨 업 했다는 방송을 합니다. 이때, 갱신된 현재 레벨 값을 데이터로 함께 실어 보냅니다.

using UnityEngine;

public class Player : MonoBehaviour
{
    public int currentLevel = 1;

    // 레벨 업
    public void LevelUp()
    {
        currentLevel++;
        Debug.Log($"[Player] 레벨 업! 현재 레벨: {currentLevel}");

        // 이벤트 버스에 OnLevelUp 이벤트를 발행하고, 현재 레벨(currentLevel)을 함께 보냄
        EventBus.Instance.Publish(EventType.OnLevelUp, currentLevel);
    }
}

 

3. 이벤트 구독(Subscribe)하기

게임이 시작될 때(Start) 이벤트를 구독하고 대기합니다. 알림이 도착하면 이벤트에 따른 행동을 취합니다. 

using UnityEngine;
using UnityEngine.UI;

public class UIManager : MonoBehaviour
{
    public Text levelText; // 화면에 표시될 레벨 텍스트 UI

    private void Start()
    {
        // 이벤트 구독
        EventBus.Instance.Subscribe(EventType.OnLevelUp, UpdateLevelUI);
    }

    // 알림(신호)을 받으면 실행될 함수
    private void UpdateLevelUI(object param)
    {
        // 전달받은 데이터(param)를 원래 자료형(int)으로 박싱 해제(Unboxing)하여 사용
        int newLevel = (int)param;
        
        levelText.text = $"Lv. {newLevel}";
        Debug.Log($"[UIManager] UI 업데이트 완료! 표시되는 레벨: {newLevel}");
    }

    private void OnDestroy()
    {
    	// 이벤트 구독 해제
        EventBus.Instance.Unsubscribe(EventType.OnLevelUp, UpdateLevelUI);
    }
}

 

오브젝트가 파괴되거나 씬이 넘어갈 때는 반드시 구독을 해제해야 에러나 메모리 누수를 막을 수 있습니다. 

 

이벤트 버스는 객체 간의 강한 결합을 끊어내고 모듈화를 돕는 훌륭한 디자인 패턴입니다.

다만 무분별한 남용은 코드 추적을 어렵게 만들 수 있으므로, 전역적으로 소통해야 하는 핵심 이벤트에 한해 제한적으로 도입하는 것을 권장합니다. 

 

관심사가 적고 명확한 좁은 범위의 지역적인 연결은 옵저버 패턴을 추천드립니다!

(예: 개별 캐릭터의 HP바 UI/무기 및 스킬 쿨타임 UI 슬롯 등)