Unity2019から正式リリースされた新しいInputSystemには、ボタンを押した(離した)フレームのみTrueになるWasPressedThisFrame()とWasReleasedThisFrame()というメソッドがあります。
このメソッドの値をUniTask.Yield()を使ってUpdateのタイミングでポーリングしようとしたのですが、WasReleasedThisFrame()が常にfalseを返し、入力を検知できない現象が発生しました。
原因は使用しているUniTaskとUnityのバージョンが合っていなかったという単純なものだったのですが、特定に至るまでが大変だったので1、将来同じ問題に直面する方のために情報を残しておきます。
環境
- Unity 6 (6000.0.40f1)
- InputSystem 1.13.1
- Update Mode:Process Events In Dynamic Update
- PlayerInput未使用
- UniTask 2.0.24
解決方法
UniTaskをUnity 6をサポートしているバージョンにアップデートすることで解決しました。
筆者の場合は2.0.24から2.5.10(2025年5月6日時点の最新)にアップデートしてWasPressedThisFrame()から正しく値が取れるようになりました。
原因
古いUniTaskをUnity2020.2以降で使用した場合に発生する不具合でした。
Unity2020.2からPlayerLoopのイベントの種類(TimeUpdate)が増えた影響でUniTaskのイベント実行タイミングがずれ、PlayerLoopTiming.UpdateならPreUpdateのタイミングでYield()を抜けてしまい、まだ値のセットされていないWasPressedThisFrame()にアクセスしてしまっていたようです(InputSystemはPreUpdateで値をセットします)。
この不具合はUniTask 2.1.0で修正されています。当時アナウンスされていたのですが見落としていました。
おまけ:問題が発生したコード
UniTaskでInputActionをポーリングして、WasPressedThisFrame()がtrueになったらイベントを発行するコードです。
バージョン2.1.0未満のUniTaskをUnity 2020.2以降で使用するとPreUpdateのタイミングでYield()を抜けてWasPressedThisFrame()にアクセスしてしまい、必ずfalseが返ります。
using Cysharp.Threading.Tasks;
using System.Threading;
using UnityEngine;
class InputActionWatcher : MonoBehaviour
{
private void Start()
{
RunUpdater(this.GetCancellationTokenOnDestroy()).Forget();
}
async UniTaskVoid RunUpdater(CancellationToken cancel)
{
// InputActionAssetが自動生成したクラスのインスタンスを作成
// 実際の運用ではstaticで定義するなど他と共有できるようにした方が良い
var input = new MyInputActions();
while (!cancel.IsCancellationRequested)
{
// 攻撃ボタンがこのフレームに押されたかチェック
if(input.Battle.Attack.WasPressedThisFrame())
{
// ボタンが押されたことを通知
CallbackOnPressed();
}
await UniTask.Yield(PlayerLoopTiming.Update, cancel);
}
}
private void CallbackOnPressed()
{
Debug.Log("Attack button pressed");
}
}
-
最初はPlaerLoopのイベント実行順がUnityの仕様で順不同になることが原因と予想していたため、かなり遠回りをしました。 ↩