Update()で検知したInputイベントを用いて、FixedUpdate()でRigidbodyを動かす

# はじめに

Unityの`Input`ですが、この機能は**Update()のタイミングでないと正確な値が取得できない**という性質があります。
一方の**RigidBodyへの操作はFixedUpdate()で行わなくては正しく動作しない**という制約もあります。

そのため「入力イベントを検知してオブジェクトにAddForceする」という処理をそのまま書こうとすると結構面倒くさい処理になってしまっていました。


## 例：スペースキーでジャンプ

```csharp:面倒くさい移動処理
using UnityEngine;

public class MoveSample : MonoBehaviour
{
    private Rigidbody _rigidbody;
    private bool _isJumpPushed;

    void Start()
    {
        _rigidbody = GetComponent<Rigidbody>();
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            _isJumpPushed = true;
        }
    }

    void FixedUpdate()
    {
        if (_isJumpPushed)
        {
            _rigidbody.AddForce(Vector3.up * 10.0f, ForceMode.VelocityChange);
            _isJumpPushed = false;
        }
    }
}
```

#解決方法

この解決方法として、コルーチンを用いる方法と、`Observable`を用いる方法と、`UniTask`を用いる方法がありますのでそれぞれ紹介します。

## コルーチン版

`WaitUntil`と`WaitForFixedUpdate`を併用することで数行で書けます。

```cs
using System.Collections;
using UnityEngine;

public class MoveSample : MonoBehaviour
{
    private Rigidbody _rigidbody;

    void Start()
    {
        _rigidbody = GetComponent<Rigidbody>();

        StartCoroutine(JumpActionCoroutine());
    }

    private IEnumerator JumpActionCoroutine()
    {
        while (true)
        {
            yield return new WaitUntil(()=> Input.GetKeyDown(KeyCode.Space));
            yield return new WaitForFixedUpdate();
            _rigidbody.AddForce(Vector3.up * 10.0f, ForceMode.VelocityChange);
        }
    }
}
```

## UniTask版

`UniTask`の場合もコルーチンとほぼ同じ形式で記述できます。
`GetCancellationToken`のハンドリングが必要になるためコルーチンより若干記述量は増えますが、他の処理へのつなぎ込みがやりやすくなるので**コルーチンで書くよりもUniTaskを使うことを推奨します。**

```cs
using System.Threading;
using UniRx.Async;
using UniRx.Async.Triggers;
using UnityEngine;

public class MoveSample : MonoBehaviour
{
    private Rigidbody _rigidbody;

    void Start()
    {
        _rigidbody = GetComponent<Rigidbody>();

        var token = this.GetCancellationTokenOnDestroy();
        _ = JumpAction(token);
    }

    private async UniTaskVoid JumpAction(CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.Space), PlayerLoopTiming.Update, token);
            await UniTask.Yield(PlayerLoopTiming.FixedUpdate);
            _rigidbody.AddForce(Vector3.up * 10.0f, ForceMode.VelocityChange);
        }
    }
}
```


## Observable版

`BatchFrame`というオペレータがあります。
これは発行されたイベントをまとめて、一気に処理したいときに利用するオペレータです。
この`BatchFrame`の引数に「`0`」と「`FrameCountType.FixedUpdate`」を渡すことで、「イベントが発行されたらすぐに次の`FixedUpdate`で処理をする」という挙動にすることができます。

```csharp
using UniRx;
using UniRx.Triggers;
using UnityEngine;

public class MoveSample : MonoBehaviour
{
    private Rigidbody _rigidbody;

    void Start()
    {
        _rigidbody = GetComponent<Rigidbody>();

        this.UpdateAsObservable()
            .Where(_ => Input.GetKeyDown(KeyCode.Space))
            .BatchFrame(0, FrameCountType.FixedUpdate)
            .Subscribe(_ => _rigidbody.AddForce(Vector3.up * 10.0f, ForceMode.VelocityChange));
    }
}
```

# まとめ

`Observable`にすることに強い意味があるなら`BatchFrame`を使いましょう。
そうでないならコルーチンか`UniTask`で書いたほうが楽です。

ただ`UniTask`で書いておくと、後ろに処理を繋げたり、あとから`Observable`に変換したりといった処理がやりやすいので、コルーチンで書くよりはUniTaskで書いておくことを推奨します。