UniRxWorkBook - Operator

2023. 12. 24. 22:54·개발툴/Unity

1. Subscribe

using UnityEngine;
using UniRx;
using UniRx.Triggers;

public class Lesson_1_Subscribe : MonoBehaviour
    {
				// _____() 부분을 올바른 형식으로 대체하여 큐브가 회전하도록 만들어보세요.
        private void Start()
        {
            this.UpdateAsObservable().Subscribe(_=>RotateCube());
        }

        private void RotateCube()
        {
            this.transform.rotation = Quaternion.AngleAxis(1.0f, Vector3.up)*this.transform.rotation;
        }
    }
  • **UniRx.Trigger**를 추가하면 **this.UpdateAsObservable()**를 통해 Unity의 Update() 메서드를 옵저버블로 다루어서 Update 메서드를 이벤트 스트림으로 다룰 수 있게 한다.
  • **Subscribe**는 옵저버블을 구독하는 메서드로, 스트림에서 발생하는 각 이벤트에 대해 실행할 로직을 정의한다.
  • 각 프레임마다 실행되는 Update 이벤트를 활용해서 Cube를 회전시키는 로직을 Subscribe 내부에 추가하면 큐브가 회전하게 된다.

2. Where

using UnityEngine;
using UniRx;
using UniRx.Triggers;

public class Lesson_2_Where : MonoBehaviour
    {

        private void Start()
        {
            // _____() 부분을 올바른 형식으로 대체하여, 마우스의 왼쪽 클릭을 하는 동안에만 Cube가 회전하도록 만들어보세요.
            this.UpdateAsObservable()
                .Where(_ => Input.GetMouseButton(0))
                .Subscribe(_ => RotateCube());
        }

        private void RotateCube()
        {
            this.transform.rotation = Quaternion.AngleAxis(1.0f, Vector3.up) * this.transform.rotation;
        }

    }
  • **Where** 오퍼레이터를 추가해서, 특정 조건을 만족할 때만 스트림을 통과시킨다.
  • 마우스의 왼쪽 클릭을 조건으로 설정해서, 클릭이 발생한 동안에만 스트림을 통과시켜 Cube가 회전하게된다.

3. SkipUntil

 

using UnityEngine;
using UniRx;
using UniRx.Triggers;

public class Lesson_3_SkipUntil : MonoBehaviour
    {
        private void Start()
        {
            //마우스 클릭 스트림
            var clickStream = this.UpdateAsObservable().Where(_ => Input.GetMouseButtonDown(0));
            
            //_____() 부분을 올바른 형식으로 대체하여, 마우스 클릭이 한 번이라도 발생하면 회전이 시작되도록 만들어보세요.
            this.UpdateAsObservable()
                .SkipUntil(clickStream)
                .Subscribe(_ => RotateCube());
        }

        private void RotateCube()
        {
            this.transform.rotation = Quaternion.AngleAxis(1.0f, Vector3.up) * this.transform.rotation;
        }
    }
this.UpdateAsObservable()
                .SkipUntil(this.UpdateAsObservable().Where(_=>Input.GetMouseButtonDown(0)))
                .Subscribe(_ => RotateCube());
  • SkipUntil 오퍼레이터는 셔터(쉽게 말해 문)처럼 동작한다.
  • SkipUntil은 인자로 전달된 스트림(clickStream)이 값을 보내기 전까지 셔터가 닫혀 있다가 **clickStream** 에 값이 도달하면 **SkipUntil**셔터를 열고 원래 스트림으로부터의 메세지를 받아서 후속 스트림으로 전달한다.
  • **SkipUntil**이 닫혀 있는 동안에 도착한 모든 메시지는 폐기된다. 따라서 마우스 클릭이 처음으로 발생하면 회전이 시작되며, 그 이전에 있었던 이벤트는 무시된다.

4. Skip

using UnityEngine;
using UniRx;
using UniRx.Triggers;

public class Lesson_4_Skip : MonoBehaviour
    {
        private void Start()
        {
            // _____() 부분을 올바른 형식으로 대체하여, 마우스가 3번 클릭되면 Cube가 회전하도록 만들어보세요.
            var clickStream = this.UpdateAsObservable()
                .Where(_ => Input.GetMouseButtonDown(0));

            this.UpdateAsObservable()
                .SkipUntil(clickStream.Skip(2))
                .Subscribe(_ => RotateCube());
        }

        private void RotateCube()
        {
            this.transform.rotation = Quaternion.AngleAxis(1.0f, Vector3.up) * this.transform.rotation;
        }
    }
  • Skip(n) 오퍼레이터는 n번의 메시지가 도착할 때까지 메시지를 차단한다.
  • **Skip(2)**를 사용해서 처음 2번의 클릭 이벤트를 무시하고 3번째 이벤트부터 메시지를 전달하게할 수 있다. 이렇게 하면 처음 2번의 클릭은 무시되고 3번째 클릭부터 회전이 시작된다.

5. Buffer

using UnityEngine;
using UniRx;
using UnityEngine.UI;

public class Lesson_5_Buffer : MonoBehaviour
    {
        [SerializeField] private Text resultLabel;
        [SerializeField] private Button buttonA;
        [SerializeField] private Button buttonB;
        [SerializeField] private Button buttonC;

        private void Start()
        {
            var aStream = buttonA.OnClickAsObservable().Select(_ => "A");
            var bStream = buttonB.OnClickAsObservable().Select(_ => "B");
            var cStream = buttonC.OnClickAsObservable().Select(_ => "C");

            // _____()를 수정하여
            // 지난 3번의 눌린 버튼 이력을 표시해 보세요
            // (각각 3번 눌릴 때마다 업데이트되는 형태로 구현해도 좋습니다)
            Observable.Merge(aStream, bStream, cStream)
                .Buffer(3)
                .SubscribeToText(resultLabel, x => string.Join(", ",x));

            // IEnmerable<String>를 하나의 String으로 합치려면
            // strings.Aggregate((p, c) => p + c)와 Aggregate를 사용하면 간단하게 작성할 수 있습니다.
        }
    }
Observable.Merge(aStream, bStream, cStream)
                .Buffer(3)
                .SubscribeToText(resultLabel, x => x.Aggregate((p, c) => p + c));
  • **Buffer(n)**를 사용하면 지난 n개의 메시지를 버퍼링할 수 있다.
  • 몇 개가 쌓일 때 방출할지는 두 번째 매개변수로 지정할 수 있다 (기본값은 첫 번째 매개변수와 동일)

Buffer(3,1)


6. First

 

 

using UnityEngine;
using UniRx;
using UnityEngine.UI;

public class Lesson_6_First : MonoBehaviour
    {

        [SerializeField]
        private Text resultLabel;
        [SerializeField]
        private Button buttonA;
        [SerializeField]
        private Button buttonB;
        [SerializeField]
        private Button buttonC;

        private void Start()
        {
            var aStream = buttonA.OnClickAsObservable().Select(_ => "A");
            var bStream = buttonB.OnClickAsObservable().Select(_ => "B");
            var cStream = buttonC.OnClickAsObservable().Select(_ => "C");

            // _____를 수정하여, 처음 1회 눌릴 때만 Text가 변경되도록 만들어보세요.
            Observable.Merge(aStream, bStream, cStream)
                .First()
                .SubscribeToText(resultLabel);
        }

    }
  • First, **FirstOrDefault**를 사용하면 가장 처음의 메시지만 통과시킬 수 있다.
  • **First**는 가장 처음의 메시지를 통과시킨 후, **OnCompleted**를 발행한다.
  • **First**와 **FirstOrDefault**에서 메시지가 한 번도 발행되지 않고 Dispose된 경우
    • First : [OnError] 발행
    • FirstOrDefault : [OnNext + OnCompleted] 발행

7. Zip

 

public class Lesson_7_Zip : MonoBehaviour
    {
        [SerializeField]
        private Text resultLabel;
        [SerializeField]
        private Button buttonLeft;
        [SerializeField]
        private Button buttonRight;

        private void Start()
        {
            var rightStream = buttonRight.OnClickAsObservable();
            var leftStream = buttonLeft.OnClickAsObservable();

            // _____를 수정하여, Left와 Right 버튼이 각각 최소한 1회씩 눌렸을 때에만 Text가 변경되도록 만들어보세요.
            leftStream
                .Zip(rightStream, (l, r) => new { l, r })
                .First()
                .SubscribeToText(resultLabel, _ => "OK");
        }

    }
Observable.Zip(leftStream, rightStream)
                      .First()
                      .SubscribeToText(resultLabel, _ => "OK");
  • **Zip**은 여러 오퍼레이터의 값을 하나씩 조합하여 흐르게 하는 오퍼레이터
  • **Zip**은 두 스트림에서 각각 한 번씩 메시지가 모일 때마다 메시지를 방출한다.
  • **Zip**은 내부적으로 **Buffer**를 사용하고 있어, 내부적으로 버퍼링을 처리한다.
    • 만약 **First**를 삭제하고 Left를 연속으로 3번 누른 후에, 그 후에 Right를 연속으로 3번 누르면..?

8. Repeat

public class Lesson_8_Repeat : MonoBehaviour
    {
        [SerializeField]
        private Text resultLabel;
        [SerializeField]
        private Button buttonLeft;
        [SerializeField]
        private Button buttonRight;

        private void Start()
        {
            var rightStream = buttonRight.OnClickAsObservable();
            var leftStream = buttonLeft.OnClickAsObservable();

            // _____를 수정하여, Left와 Right를 번갈아가며 1번씩 눌렀을 때 "OK"가 표시되도록 만들어봅시다.
            // 
            // First()를 제거하는 것만으로는 Left와 Right를 번갈아가며 빠르게 눌렀을 때의 동작이 예측하기 어려우므로,
            // 적절한 오퍼레이터를 First 뒤에 추가해봅시다.
            leftStream
                .Zip(rightStream, (l, r) => Unit.Default)
                .First()
                .Repeat()
                .SubscribeToText(resultLabel, _ => resultLabel.text += "OK\\n");
        }
    }
  • **Repeat**는 OnCompleted가 발생하면 스트림을 다시 Subscribe하는 오퍼레이터
    • 흘러온 값들을 재현하여 반복하는 기능은 아님
  • 숫자를 인수로 전달하여 지정한 횟수만큼 동작시킬 수도 있다 (기본값은 무한 동작)
  • 적절하지 않게 사용하면 무한 Subscribe를 유발하여 프리즈를 일으킬 수 있다. 객체가 파괴되어 OnCompleted가 발생하는 타이밍을 주의해야 한다
  • 다양한 Repeat 오퍼레이터
    • RepeatSafe 짧은 기간에 Repeat가 여러 번 발생할 때 Dispose
    • RepeatUntilDisable GameObject가 Disable되는 타이밍에 Dispose
    • RepeatUntilDestory GameObject가 Destroy되는 타이밍에 Dispose

9. CombineLatest

public class Lesson_9_CombineLatest : MonoBehaviour
    {
        [SerializeField] private InputField leftInput;
        [SerializeField] private InputField rightInput;
        [SerializeField] private Text resultLabel;
        private void Start()
        {
            var leftStream = leftInput.OnValueChangedAsObservable().Select(x => Int32.Parse(x));
            var rightStream = rightInput.OnValueChangedAsObservable().Select(x => Int32.Parse(x));

            // 아래의 오퍼레이터 체인은 두 개의 InputField에 입력된 숫자를 합산하여 표시하는 스트림을 생성하고 있습니다
            // 그러나 Zip을 사용하면 동작이 이상하게 동작하기 때문에, Zip을 적절한 오퍼레이터로 변경하여 InputField의 변경이 즉시 반영되도록 해보겠습니다
            leftStream
                .CombineLatest(rightStream, (left, right) => left + right)
                .SubscribeToText(resultLabel);
        }
    }
  • **CombineLatest**는 모든 스트림 중 하나라도 값이 갱신되면 직전의 값으로 대체하여 결과를 방출합니다.
  • **Zip**은 여러 스트림의 값이 동시에 갱신될 때만 값을 방출하므로 값이 갱신되지 않은 스트림이 있는 경우 업데이트가 지연되는 문제가 있습니다.

10. Throttle

 

public class Lesson_10_Throttle : MonoBehaviour
    {
        [SerializeField] private InputField inputField;
        [SerializeField] private Text resultText;

        private void Start()
        {

            // _____를 수정하여, 마지막으로 문자가 입력된 후 1초 후에 resultText에 반영되도록 만들어보겠습니다
            inputField
                .OnValueChangedAsObservable()
                .Throttle(TimeSpan.FromSeconds(1))
                .SubscribeToText(resultText);
        }
    }
  • **Throttle**은 메시지가 연속적으로 발생할 때 일정 시간 동안 대기하는 오퍼레이터
  • 지정한 시간 간격보다 짧은 간격으로 메시지가 대량으로 도착하면 그 메시지들을 모두 무시하고, 지정한 시간 이상이 경과한 후에 받은 메시지 중에서 마지막에 도착한 메시지를 방출한다.

11. ThrottleFirst

public class Lesson_11_ThrottleFirst : MonoBehaviour
    {
        [SerializeField] private GameObject bulletObject;

        private void Start()
        {
            // 아래 스크립트는 "왼쪽 클릭을 유지하는 동안 총알을 발사하는" 스크립트입니다
            // 그러나 현재 상태에서는 매 프레임마다 총알이 생성됩니다
            // 그래서 ____을 수정하여 "왼쪽 클릭을 유지하는 동안 100ms마다 한 번씩 총알을 발사하는" 동작으로 변경하겠습니다
            this.UpdateAsObservable()
                .Where(_ => Input.GetMouseButton(0))
                .ThrottleFirst(TimeSpan.FromMilliseconds(100))
                .Subscribe(_ =>
                {
                    var b = Instantiate(bulletObject, transform.position, Quaternion.identity) as GameObject;
                    Destroy(b, 2.0f);
                });
        }

    }
  • **ThrottleFirst**는 **Throttle**의 반대로, "첫 번째 메시지가 도착한 후 일정 기간 동안 메시지를 차단하는" 오퍼레이터
  • 많은 메시지를 간편하게 간소화할 수 있다
  • 프레임 수를 지정할 수 있는 **ThrottleFirstFrame**도 있음

12. TakeUntil

public class Lesson_12_TakeUntil : MonoBehaviour
    {
        [SerializeField] private Button onButton;
        [SerializeField] private Button offButton;
        [SerializeField] private GameObject cube;

        private void Start()
        {
            // ____를 수정하여, OFF 버튼을 누르면 회전이 멈추도록 만들어보겠습니다

            var onStream = onButton.OnClickAsObservable();
            var offStream = offButton.OnClickAsObservable();

            this.UpdateAsObservable()
                .SkipUntil(onStream)
                .TakeUntil(offStream)
                .RepeatUntilDestroy(gameObject)
                .Subscribe(_ => RotateCube());

        }

        private void RotateCube()
        {
            cube.transform.rotation = Quaternion.AngleAxis(1.0f, Vector3.up) * cube.transform.rotation;
        }
    }
  • **TakeUntil**은 주어진 스트림에 메시지가 도달하면 OnCompleted를 발행
  • **SkipUntil**과 조합하여 "이벤트 A가 발생한 후 이벤트 B가 발생할 때까지 처리"와 같은 작업을 간편하게 기술할 수 있다.
🚫 주의 TakeUntil은 메세지의 도달 여부와 상관없이 동작한다. → SkipUntil을 거지치 않고도 TakeUntil이 발동할 수 있다.

13. DistinctUntilChanged

this.UpdateAsObservable()
                .Select(_ => controller.isGrounded)
                .Throttle(TimeSpan.FromMilliseconds(5))
                .Subscribe(isGrounded => StatusOutput(isGrounded));
  • **DistinctUntilChanged**는 메시지의 값이 변경되었을 때에만 메시지를 통과시키는 연산자
  • 어떤 값을 매 프레임 감시하고 변화가 있을 때만 처리
    • ObserveEveryValueChanged 오퍼레이터
      • **Observable.EveryUpdate().Select().DistinctUntilChanged()**와 동일

 

 

 

 

 

'개발툴 > Unity' 카테고리의 다른 글

UniRx 실습 - 간단한 식물 재배 로직 만들어보기  (0) 2024.08.16
UniRX 실습 - 간단한 MessageBox 만들기  (0) 2023.12.25
의존성 주입(Dependency Injection)  (1) 2023.12.18
Reflex 실습 - UniRx와 Reflex를 활용한 간단한 카운터 만들기  (0) 2023.12.18
Reflex (Github Readme 번역)  (1) 2023.12.18
'개발툴/Unity' 카테고리의 다른 글
  • UniRx 실습 - 간단한 식물 재배 로직 만들어보기
  • UniRX 실습 - 간단한 MessageBox 만들기
  • 의존성 주입(Dependency Injection)
  • Reflex 실습 - UniRx와 Reflex를 활용한 간단한 카운터 만들기
가든_
가든_
  • 가든_
    Code Garden
    가든_
  • 전체
    오늘
    어제
    • 글 목록 (60)
      • 프로그래밍 언어 (11)
        • JAVA (0)
        • C++ (2)
        • C# (9)
      • 개발툴 (24)
        • Visual Studio (0)
        • Visual Studio Code (1)
        • Eclipse (1)
        • Unity (19)
        • Unreal (0)
        • Spring (1)
        • SpringBoot (0)
        • Vue (2)
      • 디자인 패턴 (6)
      • 백엔드 (4)
        • MySQL (1)
        • Servlet (3)
      • 프론트엔드 (4)
        • HTML (3)
        • CSS (0)
        • Javascript (1)
      • 알고리즘 (10)
        • 공식 (3)
        • 백준 (6)
        • SW Expert Academy (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    ()=>
    Proxy 패턴
    Adaptee
    Adapter 패턴
    swea2112
    Factory 패턴
    12738
    chatGPT
    Abstract Factory 패턴
    Unity
    UniRX
    스택
    FixedUpdate
    행동 UML 다이어그램
    컴파일 상수
    c#
    구조패턴
    SetTile
    Java
    HTML
    상태공간트리
    MVC
    다이어그램 그리기
    RDBM
    오브젝터 어댑터
    Reflex
    DI
    런타임 상수
    구조적 UML 다이어그램
    클래스 어댑터
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
가든_
UniRxWorkBook - Operator
상단으로

티스토리툴바