【UniRx】Update()タイミングのイベントをFixedUpdate()のタイミングに変換する

# UpdateとFixedUpdate

Unityの`Update`と`FixedUpdate`には次のような使い分けが必要になっています。

 * `Input`による入力イベントは`Update()`内でしか正しく取得できない
 * `RigidBody`への操作は`FixedUpdate()`内で実行しないと正しく動作しない

そのため、「入力イベントをもとに`RigidBody`を操作する」といったことがしたくなった場合、`Update()`から`FixedUpdate()`のタイミングへと処理のタイミングをずらす必要があります。
今回はこういった処理をUniRxを使って書いてみます。

# 「タイミング」のみを`Update`から`FixedUpdate`へ変換する

「ジャンプボタンが押された」といった入力されたタイミングが重要で、メッセージの値そのものに価値がない場合は`BatchFrame`オペレータが利用できます。

## `BatchFrame`オペレータ

`BatchFrame`は「OnNextメッセージをバッファにたくわえ、指定したUnityのイベントタイミングに同期して出力する」といったオペレータです。
ジェネリクスをとる場合と`Unit`型を取る場合で挙動が異なるのですが、今回は`Unit`型版を利用することにします。

### `Unit`型をとる`BatchFrame`

```cs
public static IObservable<Unit> BatchFrame(this IObservable<Unit> source, int frameCount, FrameCountType frameCountType)
```

* `this IObservable<Unit> source` : 入力となるストリーム
* `int frameCount` : 何フレーム分遅延させるか。0を指定した場合は次のイベントタイミングになる。
* `FrameCountType frameCountType` : Unityのどのイベントタイミングに同期させるか。

`Unit`型をとる`BatchFrame`はシンプルに「イベントのタイミングを変換する」といった挙動をします。
ジェネリクスを取る場合は複数入力されたイベントをバッファするのですが、`Unit`型を取る版ではバッファの長さは1に固定されています。

## 実際のコード

実際に`BatchFrame`を使って`Update`から`FixedUpdate`へタイミングを変換するコードになります。

```cs
using UniRx;
using UniRx.Triggers;
using UnityEngine;

public class RigidBodyMover : MonoBehaviour
{
    void Start()
    {
        var rigidBody = GetComponent<Rigidbody>();

        this.UpdateAsObservable()
            .Where(_ => Input.GetKeyDown(KeyCode.Space))
            .AsUnitObservable() // Unit型に変換
            .BatchFrame(0, FrameCountType.FixedUpdate) //入力メッセージを次のFixedUpdate時に出力する
            .Subscribe(_ =>
            {
                rigidBody.AddForce(Vector3.up * 10, ForceMode.VelocityChange);
            });
    }
}

```

このように、 `BatchFrame(0, FrameCountType.FixedUpdate)` を指定することで、入力イベントを次の`FixedUpdate`のタイミングへと移動させることができます。

# `Update`内で設定された値を`FixedUpdate`内で利用する場合

`Input.GetAxis`のような、最新の入力値を`FixedUpdate`で利用したいといった場合は`WithLatestFrom`オペレータが使えます。

## `WithLatestFrom`オペレータ

![image.png](https://qiita-image-store.s3.amazonaws.com/0/47146/d2f4acda-4e70-df42-47c8-058602064f27.png)

`WithLatestFrom`は、1つのストリームを主軸とし、そこに別のストリームの最新値を合成するオペレータです。

```cs
public static IObservable<TResult> WithLatestFrom<TLeft, TRight, TResult>(this IObservable<TLeft> left, IObservable<TRight> right, Func<TLeft, TRight, TResult> selector)
```

 * `this IObservable<TLeft> left` : 主軸になる方のストリーム。このストリームの入力タイミングで出力される。
 * `IObservable<TRight> right` : ペアにする方のストリーム。このストリームの最新値が主軸のメッセージと合成される。
 * `Func<TLeft, TRight, TResult> selector` : 入力値を加工して最終結果に変換する関数。


## 実際のコード

実際に`WithLatestFrom`を用いて、`Update`の入力値を`FixedUpdate`時に取り出すコードになります。

```cs
using UniRx;
using UniRx.Triggers;
using UnityEngine;

public class RigidBodyMover2 : MonoBehaviour
{
    void Start()
    {
        var rigidBody = GetComponent<Rigidbody>();

        //入力ストリーム
        var inputStream = this.UpdateAsObservable()
            .Select(_ => new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")));

        //FixedUpdateを主軸にし、そこにinputStreamを合成する
        this.FixedUpdateAsObservable()
            .WithLatestFrom(inputStream, (_, input) => input)
            .Subscribe(input =>
            {
                rigidBody.AddForce(input, ForceMode.Acceleration);
            });
    }
}

```

# まとめ

UniRxにはいろいろオペレータが用意されており、その組み合わせで意外となんでもできてしまいます。
機会があればオペレータを全部まとめて紹介したいです。