UniRx学習(1)
- 参考スライド
- UniRx の利点
- Rxを使わない方法とRxを使った場合の違い
- Rxの使い方
- クリックされたら画面に表示する
- Buttonが3回押されたらTextに表示
- Buttonが2つとも押されたらTextに表示
- よく使われるオペレータ
- 実例
参考スライド
こちらのスライドを一通り読んだ.
www.slideshare.net
UniRx の利点
時間の取扱が簡単になる!
(例)
- イベントの待ち受け
- マウスクリック
- ボタン入力のタイミング
- 非同期処理
- 別スレッド通信
- 別スレッドデータロード
- 時間計測が判定に必要な処理
- 長押し
- ダブルクリック
- 時間変化する値の監視
- False → True になった瞬間に 1 回だけ処理
Rxを使わない方法とRxを使った場合の違い
前者では,イベントを受け取った後にどうするかを書いていた 後者の場合,イベントを受け取る前に何をしたいかが書ける
Rxの使い方
① ストリームを用意する ② ストリームをオペレータで加工する ③ Subscribeする
クリックされたら画面に表示する
UniRx利用時
using UnityEngine; using UnityEngine.UI; using UniRx; public class ButtonClick : MonoBehaviour { [SerializeField] private Button button; [SerializeField] private Text text; void Start() { button.onClick .AsObservable() .Subscribe(_ => text.text = "Clicked"); } }
UniRx.Triggers利用時
UniRxには,uGUI用のObservableやSubscribeが準備されている.
using UnityEngine; using UnityEngine.UI; using UniRx; using UniRx.Triggers; public class ButtonClick : MonoBehaviour { [SerializeField] private Button button; [SerializeField] private Text text; void Start() { button .OnClickAsObservable() .SubscribeToText(text, _ => "clicked"); } }
Buttonが3回押されたらTextに表示
オペレータBuffer(3)
または オペレータSkip(2)
を加える
button .OnClickAsObservable() .Buffer(3) .SubscribeToText(text, _ => "clicked");
Buttonが2つとも押されたらTextに表示
オペレータZip()
を利用する
両方が交互に1回ずつ押された時にTextに表示する → 連打されても「1回押された」と判定させる
button1 .OnClickAsObservable() .Zip(button2.OnClickAsObservable(), (b1, b2) => "Clicked!") .First() // 1度動作した後に .Repeat() // Zip内のバッファをクリアする .SubscribeToText(text, x => text.text + x + "\n");
よく使われるオペレータ
Where
条件を満たすメッセージのみ通過させる Javaでいうfilter
Select
要素の値を射影(変換)する Javaでいうmap
SelectMany
新たなストリームを生成して,そのストリームが流すメッセージを 本流のストリームのメッセージとして扱う JavaでいうflatMap
Throttle/ThrottleFrame
落ち着いた後に最後のメッセージを流す
ThrottleFirst/ThrottleFirstFrame
最初にメッセージが来てから一定時間遮断する
Delay/DelayFrame
メッセージの伝達を遅延させる
DistinctUntilChanged
メッセージが変化した時のみ通知する 同じ値が連続している場合は無視する
SkipUntil
指定したストリームにメッセージがくるまでメッセージをSkipする
TakeUntil
指定したストリームにメッセージ来たら, 自身のストリームにOnCompletedを流して終了させる
Repeat
ストリームがOnCompletedで終了した時にもう一度Subscribeを行う
SkipUntil + TakeUntil + Repeat
よく使う組み合わせ
イベントAが来てからイベントBが来るまでの間だけ処理したいような時に利用する
(例)ドラッグでオブジェクトを回転させる
UpdateAsObservable() .SkipUntil(OnMouseDownAsObservable()) // マウスがクリックされるまでストリームを無視する .Select(_ => new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"))) // マウスの移動量をストリームに流す .TakeUntil(OnMouseUpAsObservable()) // マウスが離されるまで .Repeat() // TakeUntilでストリームが終了するため再Subscribe .Subscribe(move => { transform.rotation = Quaternion.AngleAxis(move.y * _rotationSpeed * Time.deltaTime, Vector3.right) * Quaternion.AngleAxis(-move.x * _rotationSpeed * Time.deltaTime, Vector3.up) * transform.rotation; });
First
ストリームに最初にきたメッセージのみを流す OnNextの直後にOnCompleteも流れる
First + Repeat で1回動作する度にストリームを作り直している
実例
ダブルクリック判定
// クリックストリームをキャッシュに入れる var clickStream = UpdateAsObservable() .Where(_ => Input.GetMouseButtonDown(0)); // ダブルクリック時の講読処理 clickStream.Buffer(clickStream.Throttle(TimeSpan.FromMilliseconds(200))) // 最後にクリックされてから200ミリ秒経過した時 .Where(x => x.Count >= 2) .SubscribeToText( _text, x => string.Format("DoubleClick detected! \n Count:{0}", x.Count) );
値の変動の監視 (着地判定を例に)
通常の方法の一例
① CharacterController.isGroundedを毎フレーム監視 ② 現フレームにおける値をフィールド変数に保存 ③ 次フレームでFalse → Trueに変わったときにエフェクトを再生
public class OnGroundedWithoutUniRx : MonoBehaviour { CharacterController _characterController; ParticleSystem _particleSystem; private bool oldFlag; private void Start() { _characterController = GetComponent<CharacterController>(); _particleSystem = GetComponentInChildren<ParticleSystem>(); oldFrag = _characterController.isGrounded; } private void Update() { var currentFlag = _characterController.isGrounded; if(currentFlag && !oldFlag) { _particleSystem.Play(); } oldFlag = currentFlag; } }
UniRxを利用した場合の着地検知
(例1)
public class OnGroundedScript : MonoBehaviour { public override void Start() { var characterController = GetComponent<CharacterController>(); var particleSystem = GetComponentInChildren<ParticleSystem>(); UpdateAsObservable() .Select(_ => characterController.isGrounded) .DistinctUntilChanged() .Where(x => x) .Subscribe(_ => particleSystem.Play()); } }
(例2)ObserveEveryValueChangedを利用する場合 毎フレーム値の変動を監視する場合は「ObserveEveryValueChanged」の方がシンプルにかける
characterController
.ObserveEveryValueChanged(x => x.isGrounded)
.Where(x => x)
.Subscribe(_ => Debug.Log("OnGrounded!"));
値の変動を丸める
isGroundedの変動を丸める
isGroundedは斜面を移動する場合,True/Falseが激しく変動する. 値の変動をUniRxで抑え込み,精度を改善させる
UpdateAsObservable()
.Select(_ => playerCharacterController.isGrounded)
.DistinctUntilChanged()
.ThrottleFrame(5)
.Subscribe(x => throttleIsGround = x);