デニッキ

XRエンジニア🔰の備忘録・日記です.

UniRx学習(2)

 

参考資料

スライドURL

www.slideshare.net

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パターンが簡単に実装できる
    • ポーリング型実装が消え去る
    • イベント型実装になる.必要なタイミングで必要な処理をするように記述
  • 既存のイベント通知機構より簡単になる
    • C#のevent,UnityのSendMessageを使わなくていい
    • RxのObservableでラクチン

タイマのカウントを画面に表示する

UniRx未使用例(ポーリング型実装)

変化をポーリングする実装になる
毎フレーム,値が更新されたか確認するため,ムダが多い

cf. ポーリング型・イベント型の違い

ポーリング型実装かイベント型実装か - Neareal

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される度に新しく生成される(枝分かれしない)

詳細

RxのHotとColdについて - Qiita

【Reactive Extensions】 Hot変換はどういう時に必要なのか? - Qiita

Hot変換のオペレータは幾つかある中で,Publish()+RefCount()の組み合わせが便利(万能ではない)

UniRxとuGUIを組み合わせる

MVVM パターン

データバイディングないと使えない
→ Zenject(Extenject)の話になる

MVP・MV(R)Pパターン

過去ブログ参照

xrdnk.hateblo.jp

ObservableとReactivePropertyを組み合わせるとuGUI周りがスッキリかける

f:id:xrdnk:20200314160359p:plain

MV(R)Pパターンの作り方

ModelにReactivePropertyを持たせる
Presenterをつくる
PresenterModelViewを登録する
Presenter内でViewのObservableModelのReactivePropertyをそれぞれSubscribeしてつなげる