프로그래밍/Unity

[UniRx 입문 강좌 1] 개념 및 기본 사용법 소개

[아마군] 2019. 9. 23. 16:30
반응형

[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 Extensions for Unity. Contribute to neuecc/UniRx development by creating an account on GitHub.

github.com

 

UniRx란 무엇인가

 

UniRx - Reactive Extensions for Unity 의 약자.

 

Functional Reactive Programming(FRP) 를 C# 에 사용할 수 있게 만든 .Net Reactive Extensions 를 일본인인 @neuecc가 Unity 전용으로 최적화 하여 공개한 라이브러리 이다.

 

그런 만큼 더 가벼우며 UGUI, GameObject, Coroutine 등 유니티의 시스템과 매우 강력하고 직관적으로 연동이 되어 있어 쉽게 Reactive Programming 의 사용이 가능하다.

 

Reactive Programming 은 .Net 뿐만 아니라 RxJava, RxJS, Rx++, ReactiveCocoa, RxScala, RxClojure, RxSwift 등등 수많은 언어에서 사용 가능하도록 이미 많은 라이브러리가 제공 되고 있으며 사용자층 또한 매우 빠르게 늘어나고 있다.

 


2. Reactive Programming 이란?

Reactive Programming 을 한마디로 정의 한다면 아래와 같다.

 


Reactive programming is programming with asynchronous data streams.

 

(출처 : https://gist.github.com/staltz/868e7e9bc2a7b8c1f754)

 

'비동기적 데이터 흐름' 을 처리하는 프로그래밍 기법이라는 뜻으로 모든 처리를 비동기적 데이터 스트림으로 간주, Observer 디자인패턴을 활용해서 이러한 비동기 이벤트를 처리하는 것이 핵심이다.

 

좀 더 쉽게 풀어 쓰자면 일련의 흐름을 관찰할 수 있는 (Observable) 형태로 만들어서 값의 변화, 혹은 이벤트의 발생을 감지하는 것으로 이 값들은 마치 물이 흐르듯 스트림 (Stream) 을 통해 흐르는 것에 비유 할 수 있다.

 

경우에 따라 이 스트림의 값들을 필터링 하거나 버퍼링, 또는 다른 스트림의 값으로 바꾸는 등의 다양한 연산을 할 수 있다.

 

이렇게 스트림을 조작해서 원하는 결과가 통지 (Subscribe) 되므로 이 때 최종적으로 필요한 처리를 해줄 수 있게 된다.

 

이 방식은 지금까지 일반적으로 개발해 왔던 '명령형 프로그래밍' 과 큰 차이점을 가지고 있다.

 

명령형 프로그래밍이 일련의 작업 과정을 처리하는 각 단계마다 어떤 일이 일어나고 있는지 무엇을 해야 하는지 직접 관찰하고 컨트롤 하는 반면 Rx 프로그래밍은 Async Event(비동기 이벤트) 와  Observer 디자인패턴의 통지 처리를 이용해 미리 이런 조건이 발생하면 이런 처리를 하라고 지시를 내려놓고 그 지시가 통과된 시점에서만 통지를 받는 형태가 된다.

 

개념적으로는 기존의 프로그래밍 패러다임과 워낙 다르다보니 바로 와닿지 않는 점이 있지만 연습을 거쳐 개념을 이해하게 되면 매우 편리하다는 점을 알게 될것이다.

 

- 단, 사용 방법이 LINQ 쿼리 방식 중심이기에 람다 구문과 LINQ 를 어느 정도 익숙하게 사용할 수 있어야 한다.

 

 


3. UniRx 를 사용하는 이유

UniRx 를 사용하면 시간 (및 타이밍) 의 관리가 매우 편리해 지며 코드의 가독성과 코드 라인 수를 획기적으로 개선할 수 있다.

 

UniRx를 사용하기 좋은 예를 들자면

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

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

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

 

등의 경우 예전에는 멤버 변수를 둔다거나 매프레임 Update 함수 내에서 변화를 체크하는 식의 작업을 해야 했지만 UniRx 를 사용하면 매우 간단하게 제어할 수 있게 된다.

 

이해를 돕기 위해 마우스 더블 클릭을 판정하는 경우를 예로 들어보겠다.

 

0.3초 내에 마우스 더블 클릭이 일어나면 GUI 의 MyText 컴포넌트에 클릭 횟수를 출력하는 로직이다.

 

 

public Text MyText; //Text GUI
bool isClicked = false; //첫번째 클릭이 된 상태인가
float clickTime = 0.0f; //첫번째 클릭 후 흐른 시간
void Update()
{
if (isClicked == true) //이미 첫번째 클릭이 되었다면
{
clickTime += Time.DeltaTime(); //흐른 시간을 누적 시킨다
}
if (Inpub.GetMouseButtonDown(0)) //마우스를 클릭 했다면
{
if (isClicked == false) //이번이 첫 클릭이라면
{
isClicked = true;
}
else //이번이 두번째 클릭이라면
{
if (clickTime <= 0.3f) //첫 클릭 후 0.3초 이내에 클릭되었다면
{
//더블클릭 성공
gameObject.GetComponent<Text>().text = "Double Clicked!";
}
clickTime = 0.0f;
isClicked = false;
}
}
}

기본적인 방법으로 구현한 더블클릭 판정 코드는 위와 같을 것이다.

 

더블 클릭이라는 점을 판단하기 위해 두개의 멤버 변수가 필요하며 마우스 클릭 순간을 판단하기 위해 어쩔 수 없이 Update 함수를 오버로딩 해서 매 프레임 체크를 해주고 있다.

 

많은 처리를 구현해 놓은 실제 프로젝트에서 이 코드가 추가되면 소스를 분석할 때 우선 멤버 변수 두개가 무슨 기능인지를 이해해야 하고 Update 함수 내에서 이루어 지는 여러개의 if 구문을 파악해야 한다.

 

만약 이 코드를 UniRx 기반으로 바꾸면 어떻게 될까

 

public Text MyText;
void Start()
{
//매프레임 마우스 클릭 이벤트를 관찰하는 스트림을 정의 한다.
var clickStream = UpdateAsObservable()
.Where(_ => Input.GetMouseButtonDown(0));
//이 스트림에 0.3초 간 흘러들어오는 (마우스클릭) 이벤트를 모은다.(Buffer)
clickStream.Buffer(clickStream.Throttle(TimeSpan.FromMilliseconds(300)))
.Where(x => x.Count >= 2) //(마우스클릭) 이벤트가 2회 이상 발생한 경우만 필터링
.SubscribeToText(MyText, x => //위 조건을 충족한 경우 GUI의 MyText 컴포넌트에 정보 출력
string.Format("Double Clicked! Click Count = {0}", x.Count);
}

단지 이것으로 끝이다.

 

통지 받기 원하는 상황을 여러군데 로직을 나누지 않고 쿼리 형식으로 입력하는데다 조건을 체크하는게 아니라 지시하는 형태이므로 Update 함수안에서 매프레임 체크할 필요 없이 Start 나 Awake 에서 한번만 등록해 두면 되기에 코드 분석과 가독성이 비약적으로 향상된다.

 

주석에 대부분의 내용이 들어있지만 좀더 이해하기 쉽게 도형을 통해서 흐름을 살펴보자

 

소스의 첫번째 줄은 위 그림을 통해 보듯이 마우스 클릭 된 프레임들만을 흘려 보낼 clickStream 을 정의 한다.

 

이 스트림은 정의를 했을 뿐 아직 생성된 것은 아니라는 점이 포인트이다.

 

스트림의 생성은 실제 이벤트가 발생하는 시점에서 이루어진다.

 

이 부분은 실제 이벤트 발생 시 통지를 받을 조건들을 정의 한다.

 

1. 위에서 UpdateAsObservable 의 Where 오퍼레이터를 통해 마우스 클릭이 발생한 프레임 들을 골라서 스트림으로 만들어 준다.

2. 1번에서 생성되어 흘러온 스트림은 Buffer 에서 흐름이 멈춘 채 차곡차곡 누적된다.

3. 2번의 Buffer 는 개방 조건이 만족될 때까지 스트림을 흘려보내지 않고 누적시키는데 이 코드의 개방 조건은 Throttle(0.3초) 이다.

4. 즉, 밸브의 역할을 하는 Throttle 오퍼레이터가 클릭 이벤트 발생 후 0.3초 동안 새로운 클릭 이벤트가 없으면 통지를 해주는데 그때 Buffer가 모은 이벤트의 갯수를 체크해서 2개가 넘었다면 더블클릭으로 판정하게 된다.

5. 샘플에 의하면 Throttle 은 3번 발생했고 그중 첫번째와 세번째는 각각 3번, 2번의 클릭이 기록되어 더블클릭으로 판정 되며 두번째 Throttle 이벤트는 1번의 클릭이므로 Subscribe 를 통지하지 않는다.

 

이 경우 일반적으로는 .Subscribe() 을 통해 결과를 통지하지만 UniRx 의 경우 다양한 유니티 전용 오퍼레이터를 추가로 지원하는 데 그중 예제의 SubscribeToText() 는 인자로 넘겨주는 Text 컴포넌트에 람다식으로 지정한 문자열을 자동으로 셋팅해 주는 기능을 제공한다.

이 외에도 굉장히 다양한 유니티 기능을 제공하는데 이후 강좌에서 하나씩 다루어 보겠다.

 

어쨌든, 이 샘플을 이해했다면 Rx의 핵심 작동원리를 알 수 있을 것이다.

 

1. 스트림(Stream) 을 정의한다

2. 오퍼레이터(Operator) 로 스트림의 데이터를 가공한다

3. 가공된 데이터를 통지 (Subscribe) 받아서 우리의 로직에 태운다.

 

즉, 이벤트를 받으면 이걸 어떻게 가공하고 사용할지를 작성하는게 아니라 이벤트를 받기 전에 어떤 이벤트만 받고 싶은지를 작성하는 것이다.

내 로직에 필요한 이벤트만 가공하고 걸러내면 코딩이 훨씬 쾌적해 지니까!

 


[UniRx 입문 강좌 2] UniRx 의 핵심, Subject 와 Observable 사용 방법

 

[UniRx 입문 강좌 2] UniRx 의 핵심, Subject 와 Observable 사용 방법

[UniRx 입문 강좌 1] 개념 및 기본 사용법 소개 1. UniRx 의 기초, Subject UniRx 를 공부할 때 첫번째로 볼 항목은 Subject 이다. Subject 형태로 원하는 자료형을 Subject 타입의 인스턴스로 생성하면 UniRx..

skuld2000.tistory.com

 

반응형