UniRx 공부하기 - 1

UniRX의 개념과 기본 사용법에 따라 코드 적용
UniRX
avatar
2025.03.31
·
11 min read


UniRX 란?

ReactiveX
https://reactivex.io/
Unity Asset Store
Discover the best assets for game making. Choose from our massive catalog of 2D, 3D models, SDKs, templates, and tools to speed up your game development.
https://assetstore.unity.com/#!/content/17276
Unity Asset Store
GitHub - neuecc/UniRx: Reactive Extensions for Unity
Reactive Extensions for Unity. Contribute to neuecc/UniRx development by creating an account on GitHub.
https://github.com/neuecc/UniRx
GitHub - neuecc/UniRx: Reactive Extensions for Unity

요즘 기업에서 UniRX를 활용하는 사례가 점점 늘어나고 있다.

참고 자료에 의하면, " UniRX - Reactive Extension For Unity 의 약자로, Functional Reactive Programming(FRP) 를 C# 에 사용할 수 있게 만든 .Net Reactive Extensions 를 일본인인 @neuecc가 Unity 전용으로 최적화 하여 공개한 라이브러리 이다. " 라고 한다.

Unity에 최적화하여 만들고, UGUI, GameObject, Coroutine에 관해서도 만들어져 있고, EveryUpdate 라던지, UpdateAsObservable과 같이 매 프레임 확인하는 스트림을 생성하는 코드도 있는 것 보니, LifeCycle도 고려하여 작성되었다.

Unity 와 C# 뿐만아니라, 많은 언어에도 지원하고 있다. RxJava, RxJS,Rx++ 과 같이 자바, 자바스크립트, C++ 외에도 많이 지원하고 있다. 그러다보니 사용자도 많이 늘어나고 있다.


Reactive Programming 은 무엇인가?

Reactive Programming은 우리가 일반적으로 알고 있는 '명령형' 프로그래밍과는 조금 다른 패러다임이라고 한다.

데이터 스트림과 변화에 반응하는 시스템을 구축하기 위한 프로그래밍 패러다임 이라고 정의된다고 하며, 이는 데이터의 흐름에 초점을 맞추어 동작하고, 이 데이터를 비동기적으로 처리하고, 이벤트 기반 아키텍처를 통해 실시간으로 데이터에 맞춰 프로그래밍이 가능하다고 한다.

명령형 프로그래밍은 어떻게 처리할지, 내 데이터를 계속 관찰하고 제어하는 반면에 Reactive Programming은 비동기 이벤트와, 옵저버 디자인 패턴의 Notify 처리를 통해, 필터링에서 패스된 경우만 이벤트가 발생하는 형태라고 한다.

게임에서의 예로는 명령형프로그래밍에서 지속시간 1분 짜리인 버프A가 끝났는지 안끝났는지 계속 체크해서 확인해야한다면, 리액티브프로그래밍에서는 처음 1분 등록을 하면 내부적으로 타이머를 돌려서 끝나면 다시 Notify 받는 형태라고 할 수 있다.


버프의 예에서 생긴 궁금증

만약, 명령형 프로그래밍에서 지속시간이 1분인 버프 A가 종료를 체크하기 위해서는 아래와 같이 코드가 짜여질 것이다.

float _mf_Duration;
float _mf_Accumulated;

public void Update()
{
     _mf_Accumulated += Time.DeltaTime;
     if( _mf_Accumulated >= _mf_Duration)
     {
         Debug.Log($"버프 시간 종료!");
     }
}

그렇다면, UniRX를 설명하진 않았지만, UniRX는 어떻게 코드가 작성될 지 한번 보자.

public void Start()
{
    Observable.Timer(TimeSpan.FromSeconds(60))
    .Subscribe(_ => {Debug.Log($"버프 시간 종료!");})
    .AddTo(this);
}

여기서 Observable.Timer() 는 일정 시간 후 한번만 발행하고 onComplete 이벤트를 발생시킨다고 한다.

바로 코드 구조를 작성해본다면

  1. 60초 뒤에 Subscribe 한 Debug.Log($"버프 시간 종료!"); 구문을 실행시킨다.

  2. AddTo 구문은, 해당 오브젝트가 Destory될 때, 스트림이 Dispose 되도록 하기 위해 사용.

그렇다면, 내가 떠올린 궁금증은 명령형 프로그래밍이나, 리액티브 프로그래밍이나 결국 시간 체크는 진행할 것이고, 컴퓨터도 내부적으로 Timer 함수에서 시간 카운팅 하고 이벤트를 통지하는 구조일텐데, 컴퓨터 입장에선 일이 줄거나 하지 않는다는 점이었다.

if 가 줄어들어서 조금 더 최적화 되는 것인가? 라고 생각도 해보았는데,그건 아닌건 같았다.

구글링해서 내린 결론은 리액티브 선언문 이라는 것도 있고, 시스템의 특이성을 작성한것 보니, 연산의 최적화보다는 구조적, 유지보수적 최적화에 포인트가 맞춰져 있다는 느낌이었다.

이 선언문에서는 4가지로 작성되어 있었는데, 이에 대한 설명은 차근차근히 달아볼까 한다.

  1. 반응성

  2. 탄력성

  3. 유연성

  4. 메시지 구동


주로 사용되는 코드 - 더블 클릭

주로 UniRX를 설명할 때, 설명되는 예는 더블 클릭 기능이다.
명령형과, 리액티브 프로그래밍을 비교하기가 가장 쉬우면서도 비교를 하기 쉽기 때문이지 않을까 한다.

public Text _m_Text; //Text GUI

bool isClicked = false; 
float clickTime = 0.0f; 

void Update()
{
    if (isClicked == true)  //이미 첫번째 클릭이 되었다면
    {
        clickTime += Time.DeltaTime();  
    }
    if (Input.GetMouseButtonDown(0))  // 왼쪽 마우스 버튼을 클릭 했다면
    {
        if (isClicked == false) //이번이 첫 클릭이라면
        {
            isClicked = true;
        }
        else 
        {
            if (clickTime <= 0.25f) 
            //첫 클릭 후 0.25초 이내에 클릭되었다면
            {
                _m_Text.text = "Double Clicked!";
            }

            clickTime = 0.0f;
            isClicked = false;
        }
    }
}

위는 명령형 프로그래밍의 코드다.
참고 블로그에 그대로 사용해도 될 코드로 보여서 그대로 사용했는데, 업데이트 구문에서 계속 체크해서 0.25초 안에 새로운 클릭이 들어온다면 더블클릭으로 판정한다는 코드다.

이를 리액티브 프로그래밍으로 변경한다면

public class UrxTest : MonoBehaviour
{
    [SerializeField] public Text _m_Text;

    void Start()
    {
        var stream = this.UpdateAsObservable().Where(_ => Input.GetMouseButtonDown(0));
        // 업데이트 마다 발생하는 옵저버블 생성

        stream.Buffer(stream.Throttle(TimeSpan.FromMilliseconds(300))).
            Where(x => x.Count >= 2).
            SubscribeToText(_m_Text, x => string.Format("Double Click Count = {0}", x.Count));

        // Stream은 0.3초동안 필터에 관한 버퍼를 수집한다.
        // 여기서 필터링을 추가 ( x.Count >= 2) 인 경우, 즉 2 이상인 경우만 이벤트를 발생
        // 조건이 만족된다면 SubscribeToText 실행
    }
}

UpdateAsObservable을 통해 Update 스트림을 생성하고, Where 필터링은 GetMouseButtonDown(0)으로 입력이 들어올 때만, 스트림을 생성하도록 한다.

Stream.Buffer로 일정시간 동안 버퍼를 담아서 이벤트를 전송하는데, Throttle은 이벤트가 발생하면 파라미터의 시간을 기준으로 타이머를 시작, 타이머 도중에 새로운 이벤트가 감지되면 타이머 리셋, 마지막 리셋된 기준으로 타이머 시간동안 이벤트가 없다면, 마지막 이벤트를 통지하도록 한다.

여기서는 입력이 감지되고, 300ms (0.3초) 동안 입력이 감지되면 새롭게 이벤트 타이머가 리셋된다.
그 이후 0.3초 동안 Buffer로 모아서 Where 필터로 비교하며, x.Count >= 2 와 같이 더블 클릭수가 2이상인 경우만 SubscribeToText 함수를 호출한다.

이를 이해하기 쉬운 이미지로는 아래와 같은 이미지들이 있다.

4492

4493

3의 경우는 처음 입력이 감지된 이후,3회가 감지되었기 때문에 Where 필터를 통과하여 SubscribeToText 가 호출되고 1의 경우만 Where에서 필터링되어 호출되지 않는다.

다음 UniRx는 조금 더 사용해본뒤 다른 내용을 포스팅 해볼 예정.

참고


[UniRx 입문 강좌 1] 개념 및 기본 사용법 소개
[UniRx 입문 강좌 2] UniRx 의 핵심, Subject 와 Observable 사용 방법 [UniRx 입문 강좌 3] IObserver 메세지 종류와 스트림의 수명 관리 [UniRx 입문 강좌 4] Operator 활용(1) - Where & Select & SelectMany 사용법 [UniRx 입문 강좌 5] Operator 활용(2) - 다양한 오퍼레이터 소개 [UniRx 입문 강좌 6] 코루틴(Coroutine) 과 UniRx 연동 1. UniRx 란? 제작자 : @neuecc (Yoshifumi Kawai, CTO at Grani, Microsoft C# MVP) License : MIT 로 공개 다운로드 : AssetStore, GitHub neuecc/UniRx Reactive Ex..
https://skuld2000.tistory.com/31
[UniRx 입문 강좌 1] 개념 및 기본 사용법 소개