TOOLS GAMES DEVLOG DOCS ABOUT CONTACT
HOME > DOCS > Unity / C# > 컴포넌트 간 결합도를 끊어내는 제네릭 타입 기반 EventBus 시스템
Unity / C# Intermediate Unity 2021.3+ / Unity 6

컴포넌트 간 결합도를 끊어내는 제네릭 타입 기반 EventBus 시스템

문자열 식별자나 직접 참조 없이 구조체 이벤트 타입만으로 구독/발행이 이루어지는 무할당(Zero-Allocation) 유니티 C# EventBus.

#Unity #C# #EventBus #Decoupling #Architecture
핵심 개념플레이어가 사망했을 때 UI, 사운드, 세이브, 업적 시스템이 서로를 직접 참조하면 스파게티 코드가 됩니다. PlayerDiedEvent라는 순수 데이터 구조체(struct)를 정의하고 이를 EventBus를 통해 브로드캐스트하면 송신자와 수신자가 완벽히 분리(Decoupling)됩니다.

1. 타입 세이프 EventBus 구현

EventBus.cs
using System;
using System.Collections.Generic;

public interface IEvent { }

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

    public static void Subscribe(Action<T> listener)
    {
        if (listener != null) _listeners.Add(listener);
    }

    public static void Unsubscribe(Action<T> listener)
    {
        if (listener != null) _listeners.Remove(listener);
    }

    public static void Raise(T evt)
    {
        foreach (var listener in _listeners)
        {
            listener.Invoke(evt);
        }
    }

    public static void Clear() => _listeners.Clear();
}

2. 이벤트 정의 및 사용법

GameplayEvents.cs
// 1. 이벤트 데이터 정의
public readonly struct PlayerDamagedEvent : IEvent
{
    public readonly int Amount;
    public readonly int CurrentHp;

    public PlayerDamagedEvent(int amount, int currentHp)
    {
        Amount = amount;
        CurrentHp = currentHp;
    }
}

// 2. 수신처 (UI 컴포넌트)
public class HealthBarUI : UnityEngine.MonoBehaviour
{
    private void OnEnable() => EventBus<PlayerDamagedEvent>.Subscribe(OnPlayerDamaged);
    private void OnDisable() => EventBus<PlayerDamagedEvent>.Unsubscribe(OnPlayerDamaged);

    private void OnPlayerDamaged(PlayerDamagedEvent e)
    {
        UpdateHpGauge(e.CurrentHp);
    }
}

// 3. 발송처 (플레이어 컴포넌트)
// EventBus<PlayerDamagedEvent>.Raise(new PlayerDamagedEvent(15, 85));

3. 실무 주의점

  • 구독 해제 누락 방지: OnEnable에서 구독했다면 반드시 OnDisable에서 해제해야 합니다. 해제하지 않으면 이미 파괴된 컴포넌트의 델리게이트가 남아 MissingReferenceException이 터집니다.
  • 구조체(struct) 이벤트 사용: 이벤트를 클래스가 아닌 readonly struct로 선언하여 힙 할당(Garbage Collection)을 0으로 유지합니다.

참고자료 & 관련 링크