이벤트 버스
객체가 구독하거나 게시하고 있는 특정한 전역 이벤트 허브 역할이다
이벤트 관리와 관련된 가장 간단한 패턴이고
빠른 결과를 필요할때 유용하다.
이벤트 버스로 게임을 할때 경기 상태를 플레이어 객체에 전역으로 전달 할수있다는 느낌으로 보면 될꺼같다
한 객체가 이벤트 신호를 발생하면 거기에 등록된 다른 객체들에서 신호를 받는다.
이벤트 버스 시스템은 메시징 시스템과 발행/구독 패턴과 가깝다.
위에 이미지 보면 이벤트 버스 주위로 화살표가 있다.
위에 예시로 준 상황을 설명 하자면
오토로 싸우는 배틀에서
GameManager는 Start 구독 한상태
Timer는 CountDown를 구독 한상태라고 가정 하고
PlayerController를 Start 구독 한 상태라고 하면
여기서 GameManager가 EventBus에게 CountDown을 호출하면
이번트 버스 안에 CountDown를 구독한 Timer안에 함수 실행하게 되고 여기서 카운트 다운 진행후 Start를 호출하면
다시 GameManager와 PlayerController를 호출 하면서 등록한 함수를 실행 하게 된다.
즉 여기서 중효한건 객체 들이 자신이 필요한 신호를 등록하고 그 신호가 울리면
Event Bus 가 돌면서 등록된 객체를 찾아 함수를 실행시켜 준다.
시자 이벤트 버스에서 특정한 종류의 이벤트게시를 한다
이벤트 버스 게시자와 구독자 사이에서 이벤트 발생한걸 전달해준다
구독자 발생한 이벤트를 받아서 처리하거나 이벤트 신호를 받을수있게 구독한다
장점
분리: 각자 객체들 끼리 직접 참조가 아닌 이벤트 버스를 통해서 참조한다
단순성: 이벤트 버스는 이벤트의 구독 또는 게시 메커니즘을 추상화하여 제공한다.
단점
성능:모든 이벤트 시스템의 오브젝트간 메세지를 관리하는 저수준 메커니즘을 가지고있다.
따라서 이벤트 버스를 사용할때 약간의 성능 비용이 발생한다.
전역성: 전역으로 사용하기 떄문에 역시 싱글턴 패턴과 비슷한 문제가 있다.
디버깅과 유닛 각자 테스트를 조금 불편하게 한다는것이다.
보통 이벤트 버스 사용하는 시기는 빠른 프로토 타입이필요한 기능 즉 새로운 메커니즘또는 기능을 테스트할때 유용하게 사용된다.
게임 이벤트를 간단하게만 할꺼라면 이벤트 타입이나 구조체 없이 이벤트 버스를 통해서 쉽게 구현한다.
구현 을 하고싶을때는 전역 범위가 없는 이벤트 버스는 효율이 떨어지기 때문에 전역으로 구현 한다.
예시로 플레이어가 사물을 공격했을떄 데미지 표시와 ui 표시 까지하는걸 이벤트 버스로 구현하는건
너무 효율이 떨어진다.
이런 플레이어와 사물에 충돌 이벤트는 둘만에 지역 이벤트 이기때문에 둘이 하는게 좋다.
대신에 옵저버 패턴이 ui와 플레이어 나 사물에 상태는 볼수 있긴하다.
결국 이런 이벤트들의 패턴을 사용할때 뭐가 가장 적합한지 고르고 구현하자.
그러면 이제 이벤트 버스패턴 예시를 보자
보통 게임이라고하면 단계를 나눠야하는데
카운트다운 준비 시간
시작
끝
정도로 볼수있다.
이러면 이러한 경우들을 다른 ai나 다른객체 ui 등등 에게 보내줘야하는데
게임 상태에 따라 받을수있는 객체는
ui
타이머
플레이어
정도가 생각난다.
여기서 잘 유니티 에서 사용하는 UnityAction 에 대해서 알아야 하기에 모른다면 간단한 구조만 이라도 알고오자
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public enum EventBusType
{
COUNTDOWN,
START,
STOP,
}
public class EventBusSystem : MonoBehaviour
{
private static readonly IDictionary<EventBusType, UnityEvent>
Events = new Dictionary<EventBusType, UnityEvent>();
public static void Subscribe(EventBusType eventType, UnityAction listener)
{
UnityEvent thisEvent;
if (Events.TryGetValue(eventType, out thisEvent))
{
thisEvent.AddListener(listener);
}
else
{
thisEvent = new UnityEvent();
thisEvent.AddListener(listener);
Events.Add(eventType, thisEvent);
}
}
public static void UnSubscribe(EventBusType eventType, UnityAction listener)
{
UnityEvent thisEvent;
if (Events.TryGetValue(eventType, out thisEvent))
{
thisEvent.RemoveListener(listener);
}
}
public static void Publish(EventBusType eventType)
{
UnityEvent thisEvent;
foreach (KeyValuePair<EventBusType, UnityEvent> data in Events)
{
Debug.Log(data.Key + "'s damage is " + data.Value);
}
if (Events.TryGetValue(eventType, out thisEvent))
{
thisEvent.Invoke();
}
}
}
EventBusSystem 클래스 안에서 제일 중요한거 보자면
Events 변수 이다.
여기에 이벤트 들을 등록하거나 제거 하고
Subscribe(EventBusType eventType, UnityAction listener) 으로 등록하고
UnSubscribe(EventBusType eventType, UnityAction listener) 으로 등록 해제 하고
호출 할때 이벤트에 있는 액션을 Publish(eventType) 으로 invoke 해서 실행 해주기도 한다.
여기서 궁금 증이 하나 생길수도있다. 같은 이름으로 등록하면 Dictionary에 중복 키값으로 들어갈수있냐 하는데
가능 하다 뒤에 있는 밸류 값으로 추가해서 같은 키값으로 여러개 UnityAction을 보유 할수있다.
이게 이벤트 버스 구조에 가장 기초적이 인 모습이다
이뒤에 코드는 활용 하는거기에 추가로 볼사람들만 보면 될꺼같다.
그러면 테스트를 위해서 임시로 타이머를 만들어주자
public class CountDownTimer : MonoBehaviour
{
private float _currentTime;
private float duration = 3.0f;
private void OnEnable()
{
EventBusSystem.Subscribe(EventBusType.COUNTDOWN, StartTimer);
}
private void OnDisable()
{
EventBusSystem.UnSubscribe(EventBusType.COUNTDOWN, StartTimer);
}
void StartTimer()
{
StartCoroutine(CountDown());
}
IEnumerator CountDown()
{
_currentTime = duration;
while(_currentTime > 0)
{
Debug.Log($"CountDown Log {_currentTime}");
yield return new WaitForSeconds(1f);
_currentTime--;
}
EventBusSystem.Publish(EventBusType.START);
}
}
여기 코드에서 보면 OnEanble 하면 이벤트 버스에 등록하고
꺼지면 이벤트 버스에서 취소한다.
StartTimer를 액션으로 등록해서 함수로 보낸다.
그러면 이 코루틴이 돌고 START 이벤트를 호출 한다.
그러면 이 코드는 COUNTDOWN을 구독 하고 있기에 이걸 호출 받아야 코루틴 액션이 실행 하기때문에
호출 해줄 ClientEventBus 만들자.
public class ClientEventBus : MonoBehaviour
{
public Button StartBtn;
public Button StopBtn;
void Start()
{
StartBtn.onClick.AddListener(() =>
{
EventBusSystem.Publish(EventBusType.COUNTDOWN);
});
StopBtn.onClick.AddListener(() =>
{
EventBusSystem.Publish(EventBusType.STOP);
});
}
}
버튼을 2개 두고 눌리면 COUNTDOWN, STOP 이벤트 버스에 호출 해주는 코드다.
public class PlayerController : MonoBehaviour
{
string _staus;
private void OnEnable()
{
EventBusSystem.Subscribe(EventBusType.START, StartPlay);
EventBusSystem.Subscribe(EventBusType.STOP, StopPlay);
}
private void OnDisable()
{
EventBusSystem.UnSubscribe(EventBusType.START, StartPlay);
EventBusSystem.UnSubscribe(EventBusType.STOP, StopPlay);
}
void StartPlay()
{
_staus = "Start";
Debug.Log($"MYStatus {_staus}");
}
void StopPlay()
{
_staus = "Stop";
Debug.Log($"MYStatus {_staus}");
}
}
신호가 오면 _staus가 변경 하는걸 보여준다.
이벤트 버스를활용 가능하다.