[2025_11_03]EventBus
2025. 11. 3. 20:53ㆍTIL
개요
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 관계
핵심 정리
- Subscribe는 OnEnable, Unsubscribe는 OnDisable에서 처리
- 이벤트는 struct로 정의
- 구독 해제를 절대 잊지 말 것
- 순환 참조 주의
Event Bus는 확장 가능하고 유지보수하기 쉬운 시스템 구조를 만들 때 유용한 패턴이다.
다음 프로젝트 때는 꼭 Event Bus패턴을 사용해야겠다고 다짐했다.
'TIL' 카테고리의 다른 글
| [2025_11_05]개인프로젝트 준비 (0) | 2025.11.05 |
|---|---|
| [2025_11_04]프로젝트 구현 마무리 및 회상 (0) | 2025.11.04 |
| [2025_10_31]Unity로딩(코루틴과 async/await방식) (0) | 2025.10.31 |
| [2025_10_30]Unity UI관리 기법 (0) | 2025.10.30 |
| [2025_10_29]오늘 배운것.. 회의... 새프로젝트 기획 (0) | 2025.10.29 |