LoginSignup
16
11

More than 5 years have passed since last update.

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

Posted at

はじめに

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

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

例:スペースキーでジャンプ

面倒くさい移動処理
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を用いる方法がありますのでそれぞれ紹介します。

コルーチン版

WaitUntilWaitForFixedUpdateを併用することで数行で書けます。

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を使うことを推奨します。

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で処理をする」という挙動にすることができます。

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で書いておくことを推奨します。

16
11
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
11