기존의 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는 많지는 않지만 메모리에 계속 상주하는 형식임. 큰 프로젝트의 경우 필요하지 않은 상태에서 메모리에 남아있어 불필요한 메모리 차지가 있을 수 있음