본문 바로가기
Unity/스크립터블오브젝트

Scriptable Object - 응용: 이벤트, 데이터셋 (4)

by PlaneK 2020. 6. 1.

이벤트

Player는 이벤트를 전파하는 주체인 이벤트 트리거이다. Player가 이벤트 조건을 충족하면 이벤트 에셋을 통해 리스너들을 호출한다.

Player는 'public UnityEvent damageEvent'라는 필드를 갖는다. 해당 필드는 Inspector뷰로부터 리스너를 추가할 수 있다.

Player는 이벤트를 트리거 시 리스너에게 전파해 줄 GameEvent 에셋을 추가했다. 이벤트 발생 시 에셋의 Raise()를 호출할 것이다.

'Player > UnityEvent.Invoke > GameEvent.Raise()'

리스너들은 'GameEventListener' 컴포넌트를 추가해야한다. 이 컴포넌트는 해당 Event에 자신을 등록한다. 

그러면 GameEvent로부터 'public UnityEvent response'가 Invoke된다.

'GameEvent.Raise() > GameEventListener.OnEventRaised() > UnityEvent.Inoke()'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System.Collections.Generic;
using UnityEngine;
 
[CreateAssetMenu]
public class GameEvent : ScriptableObject
{
    private List<GameEventListener> listeners = new List<GameEventListener>();
 
    // 이벤트 트리거가 옵저버들에게 전파(Notify)하기 위한 함수.
    public void Raise()
    {
        for(int i = listeners.Count -1; i >= 0; i--)
            // 이벤트를 구독 중인 옵저버들의 콜백 호출. 
            listeners[i].OnEventRaised();
    }
    // 옵저버의 이벤트 등록 함수
    public void RegisterListener(GameEventListener listener)
    {
        listeners.Add(listener); 
    }
    // 옵저버의 이벤트 등록 취소 함수
    public void UnregisterListener(GameEventListener listener)
    {
        listeners.Remove(listener);
    }
}
 

GameEvent 소스.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using UnityEngine;
using UnityEngine.Events;
 
public class GameEventListener : MonoBehaviour
{
    // 등록할 이벤트
    public GameEvent Event;
    // 리스너(콜백)
    public UnityEvent Response;
 
    private void OnEnable()
    { 
        Event.RegisterListener(this);
    }
    private void OnDisable()
    {
        Event.UnregisterListener(this);
    }
    public void OnEventRaised()
    {
        Response.Invoke();
    }
}
 

GameEventListener 소스.

기본적으로 옵저버 패턴을 따른다.
Observer들이 Subject의 이벤트에 register하면 Subject로부터 Notify를 호출 받을 수 있다.
위 사례에서는 Subject는 이벤트 에셋, Observer는 리스너들이며 Player는 Subject를 invoke한다.

 

기존 방식과 다른 점은 이벤트와 리스너를 분리시켰다는 점이다. 

기존엔 이벤트 트리거에 리스너를 곧바로 추가해줘야 했다. 그래서 외부 개체를 리스너로 사용하지 못했다. 이런 디펜던시의 한계를 해소하기 위해 코드 상에서 트리거-리스너 관계를 구성하게 된다. 결국 리스너 확인을 위해 코드를 뒤적여야 하며 프리팹의 모듈화를 저해한다.

새로운 방식은 해당 디펜던시의 개체에 'GameEventListener' 컴포넌트를 추가해 모듈화를 유지하고 리스너들의 내용물을 Inspector뷰로 바로 확인할 수 있다.

 

데이터 셋 (DataSet, DataTable)

데이터 셋은 기존 싱글톤 매니저의 일부 역할을 수행한다. 현재 활성화 된 개체들을 저장해서 다른 개체들에게 제공하는 것이다.

예를들면, 전략 시뮬레이션에서 건물의 갯수를 통해 어빌리티가 부여되는 시나리오가 있다고 해보자. 그러면 현재 해당 건물이 몇 채 건설됐는지 파악해야한다. 

구현은 간단하다. 해당 건물이 생성되면 싱글톤 매니저를 참조해서 건물 스스로 컨테이너에 할당시키면 된다. 하지만 앞서 얘기했듯이 싱글톤 매니저는 런타임 중의 초기화 작업을 필요로 한다.

EnabledThings 에셋은 활성화된 Things들을 저장하는 컨테이너를 갖는다. 저 컨테이너에 Thing들은 단순히 Add만 호출해주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System.Collections.Generic;
using UnityEngine;
 
// 추상 클래스
public abstract class RuntimeSet<T> : ScriptableObject
{
    public List<T> Items = new List<T>();
 
    public void Add(T thing)
    {
        if (!Items.Contains(thing))
            Items.Add(thing);
    }
 
    public void Remove(T thing)
    {
        if (Items.Contains(thing))
            Items.Remove(thing);
    }
}
 

RuntimeSet<T> 소스. 추상 클래스.

1
2
3
4
5
using UnityEngine;
 
[CreateAssetMenu]
public class ThingRuntimeSet : RuntimeSet<Thing>
{}
 

ThingRuntimeSet : RuntimeSet<Thing> 소스. 구체 클래스.

댓글