Game Development, 게임개발/개발

UniRx, Reactive Extension for Unity - 개념

게임이 더 좋아 2022. 1. 7. 23:50
반응형
728x170

 

UniRx가 무엇이길래 요즘에 엄청 많이쓰나??

 

알아보자

 


 

무엇이냐??

 

UGUI, GameObject 등의 유니티의 시스템으로도

쉽게 Reactive Programming 의 사용을 가능하게 한다.

즉, 비동기적 처리를 더 효율적으로 하기 위한 도구 중 하나라는 말이다.

 


 

기존에는 그렇다면 비동기처리 도구가 없었느냐? 

있다면 기존의 것은 뭐가 안좋은데??

 

없었던 것은 아니었다.

 

닷넷에는 .NET Rx이라는 것이 있었다.

하지만 Unity에 최적화된 것은 아니었다.

즉, UniRx는 Unity C#에 최적화되어 만들어져있다.

때문에 Unity 개발에 유용한 기능이나 오퍼레이터가 추가적으로 구현되어 있다.

게다가 ReactiveProperty 등이 추가되어 있다.

그래서 UniRx가 기존의 닷넷에서 쓰던 Rx보다는 성능이 좋다는 평가가 훨씬 많다.

 

 


 

UniRx의 특징이 뭔데???

 

우선 디자인 패턴 중 하나인 Observer 패턴을 기반으로 설계되어 있어서

이미 패턴을 알고있는 사람에겐 어떻게 쓸 지 감이 올 것이다.

 

[Game Developer, 게임개발자/디자인패턴] - Observer Pattern, 관찰자, 감시자 패턴 [디자인패턴]

 

다시 말하자면 관찰할 수 있는 (Observable)오브젝트로 만들어서

값의 변화 혹은 이벤트의 발생을 감지하는 것이다.

 

더욱이 감지된 데이터 스트림의 값들을 필터링 하거나 버퍼링, 또는 다른 스트림의 값으로 바꾸는 등

다양한 연산을 할 수 있다.

 

이렇게 스트림을 조작해서 원하는 결과가 통지 (Subscribe) 되므로

이 때 최종적으로 필요한 처리를 해줄 수 있게 된다.

 

 

??? 기존의 Event가 있는데 굳이 UniRx로 써야해??

음.. 당연히 Event보다 좋으니까 쓴다.

 

 

??? 무엇이 좋은데???

 

바로 말하기는 조금 어렵고 다른 분의 예를 긁어와보겠다.

Timer의 구현을 Event와 UniRx로 짠 것이다.

 

#1. Event 발생시킴

using System.Collections;
using UnityEngine;

/// <summary>
/// 100에서 카운트 다운 값을 보고하는 샘플
/// </summary>
public class TimeCounter : MonoBehaviour
{
    /// <summary>
    /// 이벤ㅌ 핸들러 (이벤트 메시지의 형식 정의)
    /// </summary>
    public delegate void TimerEventHandler(int time);

    /// <summary>
    /// 이벤트
    /// </summary>
    public event TimerEventHandler OnTimeChanged;

    private void Start()
    {
        // 타이머 시작
        StartCoroutine(TimerCoroutine());        
    }

    private IEnumerator TimerCoroutine()
    {
        // 100에서 카운트 다운
        var time = 100;
        while (time > 0)
        {
            time--;

            // 이벤트 알림
            OnTimeChanged(time);

            // 1초 기다리는
            yield return new WaitForSeconds(1);
        }
    }
}

 

#2. Event 감지함

using UnityEngine;
using UnityEngine.UI;

public class TimerView : MonoBehaviour
{
    // 각 인스턴스는 인스펙터에서 설정
    [SerializeField] private TimeCounter timeCounter;
    [SerializeField] private Text counterText; // UGUI의 Text

    private void Start()
    {
        // 타이머 카운터가 변화한 이벤트를 받고 UGUI Text를 업데이트
        timeCounter.OnTimeChanged += time => // "=>" 는 람다식이라는 익명 함수 표기법 
        {
            // 현재 타이머 값을 UI에 반영
            counterText.text = time.ToString();
        };
    }
}

 

요약하자면 EventHandler가 이벤트를 발생시키고

timeCounter의 OnTimeChanged로 Event가 발생된 것을 알면.

해당 값을 받아서 업데이트하는 형식이다.

 

 

다음은 UniRx이다.

 

#1. 위와 동일

using System;
using System.Collections;
using UniRx;
using UnityEngine;

/// <summary>
/// 100에서 카운트 다운하고 Debug.Log에 그 값을 표시하는 샘플
/// </summary>
public class TimeCounterRx : MonoBehaviour
{
    // 이벤트를 발행하는 핵심 인스턴스
    private Subject<int> timerSubject = new Subject<int>();

    // 이벤트의 구독자만 공개
    public IObservable<int> OnTimeChanged => timerSubject;

    private void Start() => StartCoroutine(TimerCoroutine());

    private IEnumerator TimerCoroutine()
    {
        // 100에서 카운트 다운
        var time = 100;
        while (time > 0)
        {
            time--;

            // 이벤트를 발행
            timerSubject.OnNext(time);

            // 1초 기다리는
            yield return new WaitForSeconds(1);
        }
    }
}

 

#2. 위와 동일

using UnityEngine;
using UnityEngine.UI;
using UniRx;

public class TimerViewRx : MonoBehaviour
{
    // 각 인스턴스는 인스펙터에서 설정
    [SerializeField] private TimeCounterRx timeCounter;
    [SerializeField] private Text counterText; // UGUI의 Text

    private void Start() =>
        // 타이머 카운터가 변화한 이벤트를 받고 UGUI Text를 업데이트
        timeCounter.OnTimeChanged.Subscribe(time =>
        {
            // 현재 타이머 값을 ui에 반영
            counterText.text = time.ToString();
        });
}

 

바뀐 것을 보자면 Subject가 생겼고 EventHandler가 사라졌고

해당 Subject를 외부에 공개하는 Observable하게 바꾸었고

OnNext로 변화를 발생시키고

Subscribe로 해당 변화를 감지하여 업데이트했다.

 

 

?? 뭐야 Event랑 다를게 없는데???

 

우선 위에서는 다를바가 없다.

아니 그냥 delegate에다 +로 함수 등록하는 거랑 뭐가 다르냐고..?

어차피 등록된 함수들 모두 실행하는 거랑 Subscribe된 애들 모두 실행하는 거랑 뭐가 다르냐??

우선 Event를 감지하는 형식이 달라졌다는 것을 우선 알고가자.

Event랑 같으면 나오지도 않았다.

다만 배워가면서 Event보다 나은 점을 살펴보자.

 


 

우린 EventHandler가 사라지고 생겨난

Subject와 Subscribe와 OnNext에 대해 알 필요가 있다.

간단하게 알아만 보자.

 

Subscribe나 OnNext다 Subject에서 구현된 메서드들이다.

 

1. Subscribe: 메시지 수신시 실행을 함수에 등록한다.

2. OnNext: Subscribe에 등록 된 함수에 메시지를 전달하고 실행한다.

 

다시 다른 분의 예를 가져와서 OnNext와 Subscribe의 메커니즘을 살펴보자

 

private void Start()
{
    // Subject 작성
    Subject<string> subject = new Subject<string>();

    // 3회 Subscrbie
    subject.Subscribe(msg => Debug.Log("Subscribe1 : " + msg));
    subject.Subscribe(msg => Debug.Log("Subscribe2 : " + msg));
    subject.Subscribe(msg => Debug.Log("Subscribe3 : " + msg));

    // 이벤트 메시지 발행
    subject.OnNext("안녕하세요");
    subject.OnNext("안녕");
}
더보기
Subscribe1 : 안녕하세요
Subscribe2 : 안녕하세요
Subscribe3 : 안녕하세요
Subscribe1 : 안녕
Subscribe2 : 안녕
Subscribe3 : 안녕

 

즉, 해당 Subject가 Subscribe가 OnNext의 데이터를 받아서 해당 함수를 실행시킨다.

OnNext가 실행되었을 때만 함수를 실행시킨다고 보면 된다.

 

여기서 조금 다른 것이 나오는데

Event는 함수를 직접 등록해야하지만..서도

Subscribe에서 해당 데이터만 받아서 동작을 직접 정의할 수 있다.

즉, Event를 delegate로 관리하지 않아도 된다는 말이다!

여기서 우선 Evemt보다 나은 점이 생겼다.

 

즉, 우리가 원하는 Reactive 프로그래밍은

Async Event(비동기 이벤트) 와  Observer 패턴의 Notify를 이용해

미리 이런 조건이 발생하면 해당 처리를 하라고 지시를 내려놓고

그 지시가 통과된 시점에서만 통지를 받는 형태가 된다.

 

다시 말해서 Event의 개념을 포함하고

이제 더 유연한 표현이 가능해졌다는 말이다.

이는 후에 다음 글에서 개념과 응용을 더 하면서 알아보자.

 

 


 

어떤 작업을 비동기로 처리하는 것이 좋은데???

 

1. 더블 클릭 판정이나 유저의 입력 시간 제한 등의 일정 시간동안 대기 및 체크 해야 하는 이벤트

-> 왜냐하면.. 항상 클릭이 일어나는 것도 아닌데 항상 Update로 체크하는 경우는 효율이 떨어진다.

2. 파일을 다운로드 하거나 특정 통신 요청 후 응답 처리하는 경우

-> 이하 동문

3. UI 반응이나 로직 상에 특정 변수 값이 변경되는 순간의 처리

-> 이하 동문

 

멤버 변수를 둔다거나 매프레임 Update 함수 내에서 변화를 체크하는 식의 작업을 해야 했지만

UniRx 를 사용하면 매우 간단하게 제어할 수 있게 된다.

 

왜냐하면.. 해당 데이터로 바로 OnNext만 때리면 되니까!

 

 


 

어떻게 쓰는데??

 

그게 바로 우리가 이제 알아봐야할 것이다.

Subject부터 시작해서 더 자세히 알아보자.

 

 


 

참고링크

https://tech.lonpeach.com/2019/11/02/UniRx-Getting-Started-1/

https://skuld2000.tistory.com/31

반응형
그리드형