기존의 Event Aggregator 패턴을 이용한 이벤트 시스템에서 SO를 활용해서 시스템을 설계해보려고 한다.
주석을 보고 이해하면 됩니다.
using System;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 이벤트 발행 및 구독을 처리하는 GameEvent ScriptableObject입니다.
/// 이를 통해 게임의 다양한 부분이 직접적인 의존성 없이 통신할 수 있습니다.
/// </summary>
[CreateAssetMenu(fileName = "GameEvent", menuName = "Event System/GameEvent")]
public class GameEvent : ScriptableObject
{
/// <summary>
/// 이 이벤트에 구독된 모든 리스너들의 리스트입니다.
/// </summary>
private readonly List<Action<object>> eventListeners = new();
/// <summary>
/// 이벤트에 리스너를 구독합니다.
/// </summary>
/// <param name="listener">이벤트가 발생했을 때 알림을 받을 리스너입니다.</param>
public void Subscribe(Action<object> listener)
{
if (!eventListeners.Contains(listener))
{
eventListeners.Add(listener);
}
}
/// <summary>
/// 이벤트에서 리스너의 구독을 해제합니다.
/// </summary>
/// <param name="listener">알림 리스트에서 제거할 리스너입니다.</param>
public void Unsubscribe(Action<object> listener)
{
if (eventListeners.Contains(listener))
{
eventListeners.Remove(listener);
}
}
/// <summary>
/// 이벤트를 발행하여 구독된 모든 리스너들에게 알림을 보냅니다.
/// </summary>
/// <param name="eventData">리스너에게 전달할 선택적인 데이터입니다.</param>
public void Publish(object eventData = null)
{
for (int i = eventListeners.Count - 1; i >= 0; i--)
{
eventListeners[i]?.Invoke(eventData);
}
}
}
이제 원하는 이벤트를 생성할때는 SO를 만들어서 생성하고 붙이기만 하면 끝이다.
아래는 예제
public class Player : MonoBehaviour
{
// 발행(pub)될 데미지 이벤트 SO
[SerializeField] private GameEvent playerDamagedEvent;
// 데미지 이벤트를 발행하여 모든 리스너들에게 알림
public void TakeDamage(int damage)
{
// 데미지 로직
// 이벤트를 발행하여 리스너들에게 알림
playerDamagedEvent.Publish(damage);
}
}
public class UIManager : MonoBehaviour
{
// 데미지 이벤트 발생을 듣기(sub) 위한 SO
[SerializeField] private GameEvent playerDamagedEvent;
// 스크립트가 활성화될 때 데미지 이벤트를 구독
private void OnEnable()
{
playerDamagedEvent.Subscribe(OnPlayerDamaged);
}
// 스크립트가 비활성화될 때 데미지 이벤트 구독 해제
private void OnDisable()
{
playerDamagedEvent.Unsubscribe(OnPlayerDamaged);
}
// 이벤트 발생시 호출되는 콜백 함수
private void OnPlayerDamaged(object eventData)
{
int damage = (int)eventData; // 이벤트 데이터를 캐스팅
Debug.Log($"UI 업데이트: 플레이어가 {damage} 데미지를 받았습니다!");
}
}
이런식으로 사용하면 된다. 물론 직접 참조하지 않고 초기화 단계에서 로드해도 된다.
playerDamagedEvent = Resources.Load<GameEvent>("PlayerDamagedEvent");
ScriptableObject 기반 이벤트 시스템의 장점
1. 메모리 공유: ScriptableObject는 메모리에 공유된 상태로 존재하므로 씬이나 객체간 데이터 중복이 없고 중복 인스턴스를 만들지 않아 메모리 사용이 효율적임
2. 씬간 데이터 공유: SO는 씬 간에 동일한 데이터를 유지할 수 있으므로 씬 전환 후에도 메모리를 다시 할당하지 않음 이로인해 메모리 할당/해제가 줄어들어 메모리 안정성에 기여함
3.인스펙터를 이용하여 관리가 가능함
주의할점
1. 메모리 상주: SO는 많지는 않지만 메모리에 계속 상주하는 형식임. 큰 프로젝트의 경우 필요하지 않은 상태에서 메모리에 남아있어 불필요한 메모리 차지가 있을 수 있음