はじめに
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を用いる方法がありますのでそれぞれ紹介します。
コルーチン版
WaitUntilとWaitForFixedUpdateを併用することで数行で書けます。
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で書いておくことを推奨します。