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