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;
}
}
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");
}
}
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이 발동할 수 있다.