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)
  • 블로그 메뉴

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

  • 공지사항

  • 인기 글

  • 태그

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

  • 최근 글

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

티스토리툴바