エイプリルフール
SNS上での企業や芸能人等々のエイプリルフール投稿に花冷えの風邪をひきそうな冷笑系の皆さんこんにちは。なんであんなにウキウキで寒いネタを投稿できるのでしょうか?もう飽きたのでいっそのことエイプリルフール自体を廃止にしても良いと思う。
連打を判定したい
作成中のリズムゲーム「Under A Groove」について前回はリズムゲームなら定番のホールド(長押し)の入力判定をInputSystemを利用して実装することができました。
今回はそれに引き続き「連打」の入力判定を取り入れるたいと思ったのですが、ホールド(長押し)とは異なり「連打」の場合はInputSystemにはそれを判定するような入力パターンは定義されていません。
まぁ、単純にUpdateなどで一定時間内に発生するキー/ボタン押下のイベントの回数を判定すれば実装できそうな気もしますが、入力デバイスがキーボード/ゲームパッド等々と複数存在する場合は判定が面倒なことと、どうせならこういった入力判定はInputSystemで一元管理したいので、「連打」を判定する為の入力パターンをInputSystem側に登録し、それを使って判定を実装することにしました。
Interaction
InputSystemではPress(単押し)やHold(長押し)、Tap(押して離す)などの入力に関してはプリセットとして用意されていますが、それ以外の入力パターンを判定したい場合にそれを自作して登録することができます。
自作のInteractionは、IInputInteractionインタフェースを実装したクラスとして作成し、アプリケーション初期化などのタイミングでInteractionを登録する処理を呼び出します。
IInputInteractionインタフェースにはProcessとResetの二種類のメソッドが用意されており、そのうちProcessメソッド側に必要な入力パターンの判定処理を実装します。
using UnityEngine; using UnityEngine.InputSystem; public class MashInteraction : IInputInteraction { #if UNITY_EDITOR [UnityEditor.InitializeOnLoadMethod] #else [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] #endif public static void Initialize() { // 初期処理でInteractionをInputSystemに登録する InputSystem.RegisterInteraction<MashInteraction>(); } public void Process(ref InputInteractionContext context) { // TODO : Interactionの処理 } public void Reset() { // TODO : Interactionの状態をリセットする処理 } }
上記をMashInteraction.csという名前でUnityプロジェクトに保存すると、Input System側にInteractionとして「Mash」という名前で表示され使用できるようになります。
状態遷移について
InputSystemでは内部に状態(ステート)を持っており特定の条件を満たすと状態(ステート)が他の状態に遷移することでイベントを発火→上位へコールバック通知する、という仕組みとなっています。
上図の星マークがある状態へ遷移したときにそれぞれに応じたイベントが発火する仕組みですが、Performed状態の場合はその状態が継続される場合(PerformedからPerformedへ遷移)もイベントが発火します。
連打判定について
この状態遷移を「連打」の判定に利用する場合は、Performedへ遷移する条件を「特定の秒数(T秒)以内に指定の回数(N回)ボタンが押下された」場合とする事で「連打」中の状態を判定し、「T秒以内にN回押されなかった」場合にCanceled状態へ遷移させれば「連打」中でない状態になったことを上位へ通知することができます。
この為、先ほどのMashInteractioクラスに「特定の秒数」を指定するための変数(tapTimeOut)、「指定の回数」を指定するための変数(reqTapCount)を用意し、併せてボタン押下判定用の閾値を用意します。
// 連打判定の最大許容時間間隔[s] public float tapTimeOut; // 入力判定閾値 public float pressPoint; // 連打判定に必要な回数 public int reqTapCount = 2; public void Process(ref InputInteractionContext context) { // 入力値の大きさ閾値以上かどうか if (context.ControlIsActuated(pressPoint)) { // ボタンが押された時の処理 } }
ここで入力値の判定はProcessメソッドに引数として渡されるInputInteractionContextクラスを利用している事に注目してください。
Interactionの各状態への遷移は、このInputInteractionContextクラスの次のメソッドにより行います。
- Started() – Startedフェーズへ遷移する
- Performed() – Performedフェーズへ遷移する
- PerformedAndStayPerformed() – Performedフェーズへ遷移し、そのままPerformedフェーズに留まる
- PerformedAndStayStarted() – Performedフェーズへ遷移し、その後Startedフェーズへ遷移する
- Canceled() – Canceledフェーズへ遷移する
そして、「特定の秒数」を判定するためのタイムアウト処理にはInputInteractionContextクラスのSetTimeoutメソッド、「特定の秒数」入力がないタイムアウトが発生したかの判断にはInputInteractionContextクラスのtimerHasExpiredプロパティを使用します。
これらを利用したProcess内での「連打」判定の処理シーケンスとしては以下のようになります。
Interactionの適用
上記までの内容でIInputInteractionインタフェースを実装したクラスを作成するとInputSystemで利用することができます。プロジェクト内のInput Actionsアセットをダブルクリックしてウィンドウを開きます。Input Actionsアセットを作成していない場合は先にInput Actionsアセットを作成する必要があります。
Actions名に「Mash」という名に設定し、+アイコンから入力デバイスを追加、右のBindingProperties欄のInteractionに「Mash」が追加されているので、それを選択し「連打」によるイベントの通知を設定します。
作成したInteractionでpublic変数として用意した変数を使って判定に必要な値を設定することができます。
実行結果
ボタンを短い時間間隔(tapTimeOutに設定した時間以内)で押下すると「連打」状態と判定し、その間のボタン押下時にエフェクト(緑のサークル)を表示されるようにしています。
ボタンを一定時間操作(押したり離したり)しなければ、canceledコールバックが実行されて終了します。
新規にクラスを作成してTimeoutを使ったりと少々面倒くさい実装かもしれませんが、update内で実装するよりはスマートであること、InputSystemにて入力イベントを一元管理できることを考えると、こういった用途にオリジナルのInteractionを作る手法は適していると思います。