UniRx学習(2)
参考資料
スライドURL
Updateを消し去る
Update()をObservableに変換してAwake()/Start()内にまとめて記述
メリットとして,
処理ごとに横に並べて記述ができることが可能
- 処理ごとのスコープが明示されるようになる
- 機能追加,削除,変更が容易
- 記述が宣言的になり,処理の意図がわかりやすくなる
Rxのオペレータがロジックの実現に使用可能
- 複雑なロジックがオペレータの組み合わせで実現できる
Observable化してない実装
問題点 : やりたいことを直列に書く必要があり流れが追いづらい
private void Update() { // 移動可能の場合 if(canPlayerMove) { var inputVector = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")); // 移動処理 if(inputVector.magnitude > 0.5f) { Move(inputVector.normalized); } // ジャンプ処理 if(isOnGrounded && Input.GetButtonDown("Jump")) { Jump(); } } // 残弾がある場合 if(ammoCount > 0) { if(Input.GetButtonDown("Attack")) { Attack(); } } }
Observable化
やりたいことを並列に書けるため読みやすい
private void Start() { // 移動処理 this.UpdateAsObservable() .Where(_ => canPlayerMove) .Select(_ => new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"))) .Where(input => input.magnitude > 0.5f) .Subscribe(input => Move(input.normalized)); // ジャンプ処理 this.UpdateAsObservable() .Where(_ => canPlayerMove && isOnGrounded && Input.GetButtonDown("Jump")) .Subscribe(_ => Jump()); // 攻撃処理 this.UpdateAsObservable() .Where(_ => ammoCount > 0 && Input.GetButtonDown("Attack")) .Subscribe(_ => Attack()); }
Observable化する方法
UpdateAsObservable
- 指定したgameObjectに紐づくObservableが作れる
- gameObjectのDestroy時にOnCompletedが発行される
Observable.EveryUpdate
- gameObjectから独立したObservableが作れる
- MonoBehaviourに関係ない場所でも使える
- 注意点:Destroy時にOnCompletedが発行されないので,AddTo()を加える
[SerializeField] private Text text; private void Start() { Observable.EveryUpdate() .Select(_ => transform.position) .SubscribeToText(text); }
ObserveEveryValueChanged
- Observable.EveryUpdateの派生版
- 値の変動を毎フレーム監視するObservableが作れる
ストリームで繋ぐ
コンポーネントをストリームで繋ぐことによってObserverパターンな設計にする → 全体がイベント駆動になるようにする(イベント型実装)
メリットは以下の通り.
- Observerパターンが簡単に実装できる
- ポーリング型実装が消え去る
- イベント型実装になる.必要なタイミングで必要な処理をするように記述
- 既存のイベント通知機構より簡単になる
タイマのカウントを画面に表示する
UniRx未使用例(ポーリング型実装)
変化をポーリングする実装になる 毎フレーム,値が更新されたか確認するため,ムダが多い
cf. ポーリング型・イベント型の違い
public class TimerDisplayComponent : MonoBehaviour { [SerializeField] TimerComponent _timerComponent; private Text _timerText; void Start() { _timerText = GetComponent<Text>(); } void Update() { // 現在のカウントを取得 var currentTimeText = _timerComponent.CurrentTime.ToString(); // カウントが更新されていたら描画を書き替える if (currentTimeText != _timerText.text) { _timerText.text = currentTimeText; } } }
UniRx使用例(イベント型実装)
タイマ側
public class TimerComponent : MonoBehaviour { private readonly ReactiveProperty<int> _timerReactiveProperty = new IntReactiveProperty(30); // 現在のカウントをReactivePropertyとして公開する public ReadOnlyReactiveProperty<int> CurrentTime { get { return _timerReactiveProperty.ToReadOnlyReactiveProperty();} } private void Start() { // 1秒おきに,_timerReactivePropertyの値をマイナス Observable.Timer(TimeSpan.FromSeconds(1)) .Subscribe(_ => _timerReactiveProperty.Value--) .AddTo(gameObject); } }
タイマを使う側
public class TimerDisplayComponent : MonoBehaviour { [SerializeField] private TimerComponent _timerComponent; private Text _timerText; void Start() { // 更新通知が送られたらそのタイミングで描画を更新する _timerComponent.CurrentTime.SubscribeToText(_timerText); } }
HotとColdについて
Observableは性質により2種類に分けられる
- Hotな性質
- Observerがいなくても稼働する
- ストリームを枝分かれさせ,メッセージを分配することが可能
- Coldな性質
- Observerがいないと動作しない
- Subscribeされる度に新しく生成される(枝分かれしない)
詳細
【Reactive Extensions】 Hot変換はどういう時に必要なのか? - Qiita
Hot変換のオペレータは幾つかある中で,Publish()+RefCount()
の組み合わせが便利(万能ではない)
UniRxとuGUIを組み合わせる
MVVM パターン
データバイディングないと使えない → Zenject(Extenject)の話になる
MVP・MV(R)Pパターン
過去ブログ参照
ObservableとReactivePropertyを組み合わせるとuGUI周りがスッキリかける
MV(R)Pパターンの作り方
① ModelにReactivePropertyを持たせる ② Presenterをつくる ③ PresenterにModelとViewを登録する ④ Presenter内でViewのObservableとModelのReactivePropertyをそれぞれSubscribeしてつなげる