Evnet Manager3

씬 전체에서 MonoBehaviour를 상속해 싱글톤처럼 관리하면서도 성능 최적화된 버전으로 만들어 보겠습니다.

특징은 다음과 같습니다.

  • 씬 전체 싱글톤 -> 어디서든 접근 가능
  • Generic 타입 이벤트 지원 -> 타입 안전성 유지
  • WeakReference 기반 구독자 관리 -> MonoBehaviour가 파괴되면 자동 해제
  • 발행시 Alive 구독자만 호출 -> 불필요한 호출 제거, 성능 최적화
SafeEventBus와 비슷한 기능입니다.
MonoBehaviour를 상속 했기 때문에, Update, Coroutine 기능 활용 가능

1. EventBus 구현

EventBus.cs 파일
using System;
using System.Collections.Generic;
using UnityEngine;

public class EventBus : MonoBehaviour
{
    private static EventBus _instance;
    private static bool _isQuitting = false;
    public static EventBus Instance
    {
        get
        {
            if (_isQuitting)
                return null; // 종료 중에는 생성 금지

            if (_instance == null)
            {
                var go = new GameObject("EventBus");
                _instance = go.AddComponent<EventBus>();
                DontDestroyOnLoad(go);
            }
            return _instance;
        }
    }
    private void OnApplicationQuit()
    {
        _isQuitting = true;
    }

    private class Subscription
    {
        public WeakReference TargetRef;
        public Delegate Callback;
    }

    private readonly Dictionary<Type, List<Subscription>> events = new();

    #region Subscribe / Unsubscribe

    public void Subscribe<T>(MonoBehaviour subscriber, Action<T> callback)
    {
        if (subscriber == null || callback == null) throw new ArgumentNullException();

        var type = typeof(T);
        if (!events.TryGetValue(type, out var list))
        {
            list = new List<Subscription>();
            events[type] = list;
        }

        list.Add(new Subscription
        {
            TargetRef = new WeakReference(subscriber),
            Callback = callback
        });
    }

    public void Unsubscribe<T>(MonoBehaviour subscriber, Action<T> callback)
    {
        if (subscriber == null || callback == null) return;

        var type = typeof(T);
        if (!events.TryGetValue(type, out var list)) return;

        list.RemoveAll(sub =>
            (object)sub.TargetRef.Target == subscriber &&
            sub.Callback == (Delegate)callback
        );

        if (list.Count == 0)
            events.Remove(type);
    }

    #endregion

    #region Publish

    public void Publish<T>(T evt)
    {
        var type = typeof(T);
        if (!events.TryGetValue(type, out var list)) return;

        for (int i = list.Count - 1; i >= 0; i--)
        {
            var sub = list[i];
            if (sub.TargetRef.IsAlive)
            {
                ((Action<T>)sub.Callback)?.Invoke(evt);
            }
            else
            {
                list.RemoveAt(i); // 이미 파괴된 구독자 제거
            }
        }

        if (list.Count == 0)
            events.Remove(type);
    }

    #endregion
}


2. Event 정의

PlayerScoreEvent.cs 파일
public class PlayerScoredEvent
{
    public int Score { get; private set; }

    public PlayerScoredEvent(int score)
    {
        Score = score;
    }
}

3. 이벤트 구독

ScoreManager.cs 파일
using UnityEngine;

public class ScoreManager : MonoBehaviour
{
    void OnEnable()
    {
        EventBus.Instance.Subscribe<PlayerScoredEvent>(this, OnPlayerScored);
    }
    void OnDisable()
    {
        EventBus.Instance?.Unsubscribe<PlayerScoredEvent>(this, OnPlayerScored);
    }

    void OnPlayerScored(PlayerScoredEvent e)
    {
        Debug.Log("EventBus: Score: " + e.Score);
    }
}

4. 이벤트 발행

EventBus.Instance.Publish(new PlayerScoredEvent(10));