Input Systemでボタンを押しっぱなしにした時、「タン、タタタタタン」と反応させるようにしたいの。
キーリピートだね。やり方は色々あるけどInteractionを作って実装する方法を解説していくね。
Input Systemでキーリピートを実現する方法の解説記事です。
本記事で解説するキーリピートは、ボタンを押しっぱなしにすると、押してからワンクッション置いて連打されるような挙動を想定しています。
実装方法は一通りではありませんが、Input Systemの機能であるInteractionを自作して実現すると、次のようなメリットがあります。
- 様々なボタン操作(Action)などに対してキーリピート設定を適用可能になる
- ゲームロジックと入力ロジックを綺麗に分離できる
- スティック入力などボタン以外にも適用可能
Input SystemのInteractionは、長押しやダブルタップなど、特定の入力パターンを検知するための仕組みで、この仕組みに則ってキーリピートを実現できると使いまわしが効くようになります。
参考:Interactions | Input System | 1.7.0
本記事では、このようなキーリピートを行うInteractionを自作する方法を解説していきます。
本記事で解説する方法は、あくまでもキーリピート処理を独自実装するものであり、OS側の設定を反映するものではない点をご了承ください。
- Unity 2023.2.16f1
- Input System 1.7.0
目次 非表示
前提条件
事前にInput Systemパッケージがインストールされ、有効化されているものとします。
導入手順は以下記事で解説しています。
また、本記事を読み進めるにあたっては、Input SystemのInteractionの基本を押さえておくと理解がスムーズです。
Interactionの基本については以下記事で解説しています。
キーリピート処理の実現方法
ボタンを押しっぱなしにした時、最初に反応し、ワンクッション置いてから連打するように反応させるものとします。
押した瞬間から自動連打が開始しますが、1回目から2回目までの間隔は長め、2回目以降は短めの間隔で発火させるようにすれば良いです。
Input SystemのInteractionでは、ボタンが押された瞬間や長押しされた瞬間など、特定の入力パターンの条件を満たした時にperformedイベントが発火されるようになっています。
このボタンの反応するタイミングでperformedイベントを発火させれば、Interactionでキーリピートが実現できます。
また、入力の開始と終了ではそれぞれstarted、canceledイベントを発火させる必要があります。これはボタンが押された瞬間にstarted、離された瞬間にcanceledイベントを発火させればよいでしょう。
したがって、最終的なイベント発火タイミングは以下のようになります。
ボタン入力値は実際には0~1の連続値で得られる可能性があります。
そのため、入力値がある閾値(Press point)以上なら「押された」、ある閾値(Release Point)以下なら「離された」と閾値と比較して押下判定する必要があります。
Interactionは内部的には5つの状態(フェーズ)を持つステートマシンとして振る舞います。
- Waiting – 入力待ち状態
- Started – Interactionが開始された状態
- Performed – Interactionが期待する入力を満たした状態
- Canceled – Interactionがキャンセルされた状態
- Disabled – Actionが無効な状態
このうち、主に使用するのはDisable以外の4状態です。イベント発火はStarted、Performed、Canceledフェーズへの遷移時にそれぞれstarted、performed、canceledイベントが発火するようになっています。
したがって、キーリピートを実装するInteractionでは、次のようなフェーズ遷移をさせれば良いことになります。
Performedフェーズに限っては、同じPerformedフェーズに遷移させることが可能です。
参考:Struct InputInteractionContext| Input System | 1.7.0
実装例
以下、キーリピート判定を行うInteractionの実装例です。
上記スクリプトをUnityプロジェクトに保存すると、Key Repeatという独自のInteractionとして使用可能になります。
入力確認用スクリプト(必要ならば)
本記事では、キーリピート入力の挙動を確認するために、次のスクリプトを用いるものとします。あくまで確認用のため、必須の手順ではありません。
上記をKeyRepeatExample.csというファイル名でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチし、インスペクターよりActionの設定を行えば入力を受け取れるようになります。
例ではキーボードの「A」キーを設定するものとします。
Interactionの適用
前述のキーリピートInteractionを、適用したいActionまたはBindingに設定します。
例では、前述のサンプルスクリプトの「A」キーのBindingに設定するものとします。
本記事で提示したキーリピートInteractionでは、次のパラメータをインスペクターから設定できるようにしました。必要に応じて調整してください。
- Repeat Delay – リピートが開始されるまでの時間[s]
- Repeat Interval – リピート間隔[s]
- Press Point – ボタンの押された判定の閾値。0ならInput Settings側のデフォルト設定値を参照
実行結果
キーを押しっぱなしにすると、押した瞬間にログ出力された後、暫くして連打されたようにログ出力されます。分かりやすさのため、画面にキーの押下状態を示しています。
押した瞬間にはstartedとperformedが、キーリピート時はperformedのみがログ出力されていることが確認できます。また、キーを離すとcanceledが出力されてログ出力が止まります。
スクリプトの説明
Interactionの基本実装
キーリピートのInteractionを実装するために、以下のようにIInputInteractionインタフェースを実装したクラスを定義します。
参考:Interface IInputInteraction| Input System | 1.7.0
キーリピートの開始までの時間やリピート間隔は、publicフィールドで定義しています。
Press Pointが0の時はInput Settingsの設定値
を反映しますが、これは内部的にプロパティとして参照可能にしています。参考:Class InputSettings| Input System | 1.7.0
参考:Class InputSettings| Input System | 1.7.0
キーリピートは独自Interactionとして実装していますが、これはそのままでは使用可能になりません。次のようにInput System側に登録する必要があります。
参考:Class InputSystem| Input System | 1.7.0
IInputInteractionインタフェースには、Interactionの実際の処理を行うProcessメソッドと、初期化やリセット時の処理を行うResetメソッドを実装する必要があります。
参考:Interface IInputInteraction| Input System | 1.7.0
キーリピート処理
前述のProcessメソッド内にキーリピートのInteraction処理を実装していきます。この中で各種フェーズ遷移の処理が行われます。
Waitingフェーズ(入力無し)の時にボタン入力があると、Started→Performedの順にフェーズ遷移させます。
context.StartedメソッドでStartedフェーズへ遷移させます。
context.PerformedAndStayPerformedメソッドはPerformedフェーズへ遷移させ、そのままPerformedフェーズに留まらせる操作を行います。
参考:Struct InputInteractionContext| Input System | 1.7.0
参考:Struct InputInteractionContext| Input System | 1.7.0
ボタンが押されたかどうかの判定は、context.ControlIsActuatedメソッドで行います。引数には閾値を取ります。
これは内部的には入力値の大きさと閾値との比較を行っています。
参考:Struct InputInteractionContext| Input System | 1.7.0
これで押された瞬間の判定はできますが、このままではProcessメソッドが呼ばれない可能性があります。 そのため、一定時間経過したらタイムアウトを発生させ、強制的にProcessメソッドが呼ばれるようにしています。
最初の反応からワンクッション置いて連打開始するまでの処理は以下部分です。
context.timerHasExpiredプロパティはタイムアウトが発生したかどうかを返します。
参考:Struct InputInteractionContext| Input System | 1.7.0
これがtrueならタイムアウト発生したとみなせます。本記事のキーリピートInteractionでは、リピートするタイミングでタイムアウトが発生するため、この瞬間にPerformedフェーズに遷移させ、performedイベントを発火させています。
タイムアウトが発生した時点で既にキーリピートは始まっているとみなせるため、次の処理でリピート間隔でタイムアウトを設定しています。
これによってタイムアウトが連続で発生し、キーリピートが実現できます。
ボタンを離したときにInteractionを終了させる処理は、以下部分です。
context.ControlIsActuatedメソッドで閾値Release pointとの比較を行い、入力値の大きさがこれ以下なら「離された」判定としてcontext.CanceledメソッドでCanceledフェーズに遷移させています。
入力値が変化してProcessメソッド呼ばれる場合はタイムアウトとならないため、この処理に到達できます。
参考:Struct InputInteractionContext| Input System | 1.7.0
Event Systemのキー移動について
Event Systemでは、矢印キーやスティックなどで移動するナビゲーション機能が存在します。
このような移動は、Event System側でキーリピートが実現されているため、本記事のような手順は不要です。
ただし、InputActionを直接参照して入力を受け取っている場合はこの限りではありません。
EventSystemオブジェクトにアタッチされているInputSystemUIInputModuleコンポーネントの以下項目からリピート開始までの時間[s]とリピート間隔[s]を設定できます。
スティック操作などで使用する場合
本記事で解説したキーリピートInteractionは、スティックの2軸入力などボタン以外でも適用可能です。
スティック入力にキーリピートInteractionを適用すると、スティックを倒しっぱなしにしたときに連続で入力を受け取る(performedイベント発火)挙動を取ります。
このような挙動が実現できるのは、入力があったかどうかの判定を「入力値の大きさ」で判定しているためです。
2軸入力値の大きさは、そのベクトルの大きさとなります。
参考:Struct Vector2MagnitudeComparer| Input System | 1.7.0
さいごに
キーリピート処理はゲームロジック側で実装しても実現可能ですが、Input SystemのInteractionとして実装すると様々な操作(Action)に対して汎用的に使いまわせるようになります。
入力があるかどうかの判定は、「入力値の大きさ」として汎用的に比較できるので、スティックのような2軸入力などにも対応できるメリットを備えています。