Chapter 5. Event Bus Pattern

개발하는 운동인·2024년 11월 8일
0

⭐ Obserever Pattern의 확장형이다.

  • 현 문제
  • ⭐ 잘 생성은 되었지만, 버튼을 눌러도 플레이어의 상태 변화가 일어나지 않는다. -> Observer Pattern의 확장형인 Event Bus Pattern을 이용한다.

✅ Event Bus 클래스의 싱글톤 -> 이 클래스가 알아서 하이라키창에 붙게 하기 위해 아래 처럼 구현한다.

public class EventBus : MonoBehaviour
{
   

    private static EventBus eventbus;

    public static EventBus Instance
    {
        get
        {
            if(eventbus == null)
            {
                GameObject go = new GameObject("EventBus"); //EventBus라는 빈 객체를 만들고
                eventbus = go.AddComponent<EventBus>(); //EventBus 빈 객체에 EventBus 스크립트(컴포넌트)을 추가
                Debug.Log("instance");
                DontDestroyOnLoad(go);
            }
            return eventbus;
            
        }
    }

    //go.AddComponent<EventBus>();  하는 순간 Awake가 실행. 왜? 오브젝트(go)에  클래스(스크립트)을 추가하는 순간 해당 클래스의 인스턴스가 만들어지므로, 만들어지는 순간
    //Awake가 실행된다.
    private void Awake() //EventBus 스크립트(클래스)의 인스턴스가 만들어지는 순간 제일 먼저 실행. Awake는 생성자와 같은 타입
    {
        Debug.Log("Awake");

        if(eventbus != null)
        {
            Destroy(gameObject);
        }
        else // 1. go.AddComponent<EventBus>(); -> 2. Awake 실행이므로 evetbus가 아직 null이다. 그래서 eventbus = this를 해준다. 
        {
            eventbus = this;
        }
    }
 }

⭐ 중요한 점은 하이라키창에 있는 오브젝트에 스크립트(클래스)을 추가하면 실행 시 , 클래스의 인스턴스화가 진행된다.

⭐ Event Bus Pattern 생성 방법

    1. EventBus는 싱글톤으로 구현한채 시작하고, enum 타입으로 해당 상태에 대한 것들을 적는다.
    1. 딕셔너리로 enum타입과 Action 이벤트타입을 갖는 객체를 만든다.(Action이므로, 매개변수 목록이 없다.)
    1. 구독하는 메서드를 만든다. <- 메서드의 매개변수 목록을 딕셔너리의 key와 value 동일하게 한다. 또한, 이 메서드를 다른 누군가가 구독을 해야 하므로 딕셔너리의 Key와 Value의 타입과 매개변수 목록을 맞춰줘야 한다.
  • 딕셔너리의 Key -> GlobalEvent의 enum , 딕셔너리의 value -> Action 이벤트
    1. 구독해지하는 메서드를 만든다. - Unsubscribe 메서드에서 특정 GlobalEvent 타입에 연결된 listener(구독자)를 제거
  • 만약 listener를 제거한 후 해당 evetType이 null이 된다면, 그 GlobalEvent 타입에 더 이상 구독자가 남아있지 않다는 뜻이므로, 해당 이벤트를 삭제하여 불필요한 참조를 없애고 메모리를 최적화한다..
    1. 구독한 사람들에게 이벤트를 뿌리는 메서드를 추가한다.
  • 매개변수가 GloablEvent 하나 인 이유는 GloablEvent는 eventDictionary의 Key를 의미하고 Key에 저장된 것이 null이 아니라면 Key에 맞는 이벤트를 뿌리기 위해서이다.
    1. 플레이어 컨트롤러로 돌아가서, 활성화 할 때 구독 신청하고 비활성화 될 때 구독 해지한다.
  • 해당 메서드의 상태이다.
    1. 버튼은 MenuManager에 들어있는 메서드를 통해 OnClick 이벤트를 호출했었다. 해당 메서드에 이벤트가 딕셔너리에 있다면 구독자에게 이벤트를 뿌리는 기능이다.
  • 딕셔너리에 해당 이벤트 타입(Key,상태)이 존재한다면 해당 이벤트 타입을 뿌린다.

⭐ 하지만, 콘솔창에 오류가 있다. -> 비활성화 할 때 무엇인가 남아있어서 Instance를 프로그램 종료 전에 한번더 접근해서 EventBus가 더 생겨 난 것이다. (EventBusInstantiate 로그가 두 번 출력된 것을 보고 EventBus가 두 번 생성된 것을 확인할 수 있다.)

  • PlayerController 부분에서 OnDisable메서드가 실행 될 때 Instance을 누가 접근하려고 해서 EventBus를 한번 더 만들고 종료하고 있다.
    1. 프로그램이 종료 될 때 실행 되는 OnApplicationQuit 메서드에서 만들어논 bool 변수를 true로 한다.
    1. EventBus 프로퍼티를 읽는 과정에서 EventBus가 만들어지는 조건에 bool 변수가 false일때 도 추가한다.

⭐ 최종 실행 결과

  • 상태 변화가 잘 일어나는 것을 볼 수 있다.

✅이벤트 버스 - 간단한 예제 코드

    1. EventBus 작성
using System;
using System.Collections.Generic;
using UnityEngine;

public static class EventBus 
{
    private static Dictionary<string, Action> eventDictionary = new Dictionary<string, Action>();

    public static void Subscribe(string eventName,Action listener)
    {
        if(eventDictionary.ContainsKey(eventName))
        {
            eventDictionary[eventName] += listener; 
        }
        else
        {
            eventDictionary[eventName] = listener;
        }
    }

    public static void UnSubscribe(string eventName, Action listener)
    {
        if (eventDictionary.ContainsKey(eventName))
        {
            eventDictionary[eventName] -= listener;

            if (eventDictionary[eventName] == null)
            {
                eventDictionary.Remove(eventName);
            }
        }
    }

    public static void Publish(string eventName)
    {

        if (eventDictionary.ContainsKey(eventName))
        {
            eventDictionary[eventName]?.Invoke();
        }
        else
        {
            Debug.Log($"해당 {eventName}는 eventDictionary의 존재하지 않는다.");
        }
           
    }
}
    1. TimeManager 클래스 작성
using UnityEngine;

public class TimeManager : SingleTonGeneric<TimeManager>
{
    public static string OnDayStarted = "OnDayStartEvent"; //하루 일과 시작 될 때
    public static string OnSlotChanged = "OnSlotChangeEvent"; //
    public static string OnMiniGameEnded = "OnMiniGameEndEvent"; //하나의 미니게임이 끝났을 때
    public static string OnDialogueEnded = "OnDialogueEndEvent"; //

    protected override void Awake()
    {
        base.Awake();
    }

    private void Start() 
    {
        StartDay();
    }
    public void StartDay() //하루 일과 시작
    {
        Debug.Log("TimeManager: 하루 일과 시작 - UI 업데이트");
        EventBus.Publish(OnDayStarted); 
    }
}
    1. QuestManager 스크립트 작성
using UnityEngine;

public class QuestManager : SingleTonGeneric<QuestManager>
{
    protected override void Awake()
    {
        base.Awake();
    }

    private void OnEnable() 
    {
        EventBus.Subscribe(TimeManager.OnDayStarted, OnDayStartHandler); //1.
    }
    private void OnDisable()
    {
        EventBus.UnSubscribe(TimeManager.OnDayStarted, OnDayStartHandler); //1.
    }

    private void OnDayStartHandler()
    {
        Debug.Log("QuestManager: 하루 시작에 대한 처리 - 미니게임 할당 부여");
    }


}
    1. 하이라키에 각 매니저 컴포넌트를 추가할 오브젝트 추가

실행 시,

  • QuestManager의 OnEnable에서 구독, TimeManager의 Start에서 발행.

주의 할 점

  • 구독자 스크립트에서 발행자의 클래스에 발행자 메서드를 호출하면 안된다.

    1. 기존 TimeManager에서 OnDayStartEvent를 이벤트로 하여 발행 하는 메서드가 있고,
  • QuestManager에서 TimeManager의 OnDayStartEvent를 구독하고 있다.

    1. 만약 구독하는 클래스 안에서 발행하는 발행자의 메서드를 호출하면 EventBus를 쓰는 의미가 줄어든다.

0개의 댓글