【Unity】Input Systemを旧Inputのように扱う方法と注意点

こじゃらこじゃら

Input Systemの入力をInput.GetAxisInput.GetButtonDownのような使い方で取得する方法はないの?

このはこのは

可能だわ。幾つか注意点もあるので順番に解説していくね。

Input Systemには、旧Inputのような要領で入力値を取得するAPIが提供されています。

例えば、次のようにUpdateイベント内でスティックやボタンなどの入力を判定できます。

InputAction moveAction, jumpAction;

・・・(中略)・・・

// スティックの移動量取得
// Input.GetAxisRawに相当
var inputMove = moveAction.ReadValue<Vector2>();

// ジャンプボタンが押された瞬間かどうか
// Input.GetButtonDownに相当
var jumpButtonDown = jumpAction.WasPressedThisFrame();

// ジャンプボタンが離された瞬間かどうか
// Input.GetButtonUpに相当
var jumpButtonUp = jumpAction.WasReleasedThisFrame();

// ジャンプボタンが押されているかどうか
// Input.GetButtonに相当
var jumpButtonPressed = jumpAction.IsPressed();

このようなAPIは旧Inputの利用者に馴染み深い形式である一方、コールバック方式と比較して幾つか制約事項やデメリットも存在します。

一般的に、このような毎フレーム問い合わせるコードは、Input Systemの更新タイミングと一致させる必要があります。

特に、ボタンを押した瞬間離した瞬間を判定するコードは、その瞬間のフレームのみtrueを返す仕様です。そのため、更新タイミングが一致しないとボタンが反応しなかったり、逆に複数回反応してしまうなどの不具合を引き起こす可能性があります。

このようなポーリング方式で状態をチェックする方法は、旧Inputに慣れ親しんだ人が好んで使うことが想定されますが、そのデメリットや注意点については触れられないことが殆どかもしれません。

本記事では、旧InputのようにUpdateイベントの中で入力を取得する方法および使用上における注意点について解説します。

動作環境
  • Unity 2023.1.1f1
  • Input System 1.6.1

スポンサーリンク

前提条件

事前にInput Systemパッケージがインストールされ、有効化されているものとします。

ここまでの手順がわからない方は、以下記事を参考にセットアップを進めてください。

また、本記事では紹介する方法はInput Action経由で入力値を取得するものとします。

Input Actionの基本的な使い方は以下記事で解説しています。

また、この後解説するコードは、Input System PackageUpdate ModeProcess Events In Dynamic Update(デフォルト設定)であることを前提とします。

これは、UpdateイベントのタイミングでInput Systemの入力状態が更新されることを意味します。

参考:Input settings | Input System | 1.6.1

設定状態は、トップメニューのEdit > Project Settings… > Input System Package > Update Mode項目から確認できます。

Input.GetAxisRawのように入力を取得する

InputAction.ReadValue<T>メソッドで取得できます。

public TValue ReadValue<TValue>()
    where TValue : struct

Tには入力値の型を指定します。ボタン入力や1軸入力ならfloat、スティックなどの2軸入力ならVector2を指定します。

参考:Class InputAction | Input System | 1.6.1

なお、上記メソッドは旧InputのInput.GetAxisRawのような振る舞いをしますが、Input.GetAxisメソッドのような振る舞いとは違います。理由はInput.GetAxis内部で行われるような段階的に値を変化させるような処理 [1] が行われないためです。

参考:Input Manager – Unity マニュアル

サンプルスクリプト

以下、Input Actionから1軸の入力値として毎フレーム取得し、それをログ出力する例です。

ReadValueExample.cs
using UnityEngine;
using UnityEngine.InputSystem;

public class ReadValueExample : MonoBehaviour
{
    // 1軸入力を受け取るAction
    [SerializeField] private InputActionProperty _actionProp;

    private void Update()
    {
        // ReadValue()で入力値を取得
        var inputValue = _actionProp.action.ReadValue<float>();

        // 入力値をログ出力
        print($"入力値: {inputValue}");
    }

    private void OnDestroy()
    {
        _actionProp.action.Dispose();
    }

    private void OnEnable()
    {
        _actionProp.action.Enable();
    }

    private void OnDisable()
    {
        _actionProp.action.Disable();
    }
}

上記をReadValueExample.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチし、インスペクターよりActionの設定を行ってください。

例では、ゲームパッドの左トリガーキーを割り当てることとします。

注意

スティックなど2軸のBindingを指定するとエラーとなりますのでご注意ください。

実行結果

実行すると、毎フレーム入力値がログ出力されるようになります。

ゲームパッドの左トリガーボタンのようなアナログ入力の場合、0〜1の範囲の値が出力されることでしょう。

スクリプトの説明

Actionをインスペクターから設定可能にするために、次の部分でInputActionProperty構造体として定義しています。

// 1軸入力を受け取るAction
[SerializeField] private InputActionProperty _actionProp;

InputActionProperty構造体を使うことで、インスペクターに直接Actionを埋め込んで定義するか、Input Action Assetの定義済みActionを指定するかをUse Reference項目から選択して指定できるようになります。

参考:Struct InputActionProperty | Input System | 1.6.1

また、Actionからの入力受け取りはこのままではできず、InputAction.EnableメソッドでInput Actionを有効化する必要があります。

private void OnEnable()
{
    _actionProp.action.Enable();
}

有効化したら、Updateイベントで入力値を受け取れるようになります。

次の処理でReadValueメソッドを呼び出し、入力値を受け取ってログ出力しています。

private void Update()
{
    // ReadValue()で入力値を取得
    var inputValue = _actionProp.action.ReadValue<float>();

    // 入力値をログ出力
    print($"入力値: {inputValue}");
}

Input.GetButtonDown/GetButtonUp /GetButtonのように入力を取得する

次のように、ボタンの押下状態を判定するメソッドも提供されています。

Old Input SystemNew Input System
Input.GetButtonDown()InputAction.WasPressedThisFrame()
Input.GetButtonUp()InputAction.WasReleasedThisFrame()
Input.GetButton()InputAction.IsPressed()
旧InputとInput Systemとのメソッド対応

参考:Migrating from the old input system | Input System | 1.6.1

InputAction.WasPressedThisFrameメソッドは、ボタンが押された瞬間かどうかを返します。

InputAction.WasReleasedThisFrameメソッドは、ボタンが離された瞬間かどうかを返します。

InputAction.IsPressedメソッドは、ボタンが押されているかどうかを返します。

いずれもUpdate(Update Modeの設定次第ではFixedUpdate)イベントなどで使用されることを想定しています。

メモ

これらのメソッドは、Input ActionのInteractionの設定の影響を受けません。例えば、長押しを検知するHold Interactionが指定されていたとしても、常にボタンの生データの状態をチェックします。

また、ボタンに限らず、2軸のスティック入力に対しても使用可能です。この場合、スティック入力値のベクトルの長さの変化量をチェックし、長さが閾値(Press Point)以上になったら「押された」と判定されます。

参考:Class InputAction | Input System | 1.6.1

サンプルスクリプト

以下、ボタンの押された瞬間、離された瞬間、押されている状態を判定してログ出力する例です。

ButtonExample.cs
using UnityEngine;
using UnityEngine.InputSystem;

public class ButtonExample : MonoBehaviour
{
    // 押された状態を判定するAction
    [SerializeField] private InputActionProperty _actionProp;

    private void Update()
    {
        if (_actionProp.action.IsPressed())
        {
            print("ボタンが押されている!");
        }

        if (_actionProp.action.WasPressedThisFrame())
        {
            print("ボタンが押された!");
        }

        if (_actionProp.action.WasReleasedThisFrame())
        {
            print("ボタンが離された!");
        }
    }

    private void OnDestroy()
    {
        _actionProp.action.Dispose();
    }

    private void OnEnable()
    {
        _actionProp.action.Enable();
    }

    private void OnDisable()
    {
        _actionProp.action.Disable();
    }
}

上記をButtonExample.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチし、インスペクターよりActionの設定を行います。

例では、ゲームパッドのSouth Buttonの他、スティック入力でも機能するか確かめるために左スティックも登録することとしました。

実行結果

ボタンが押されると、その旨のログが出力されます。

分かりやすさのため、各種入力の状態をUIで可視化しています。

スティックが倒されてもボタンが押された判定されていることが確認できます。

スクリプトの説明

1つ目の例との違いは次のUpdate内部です。

private void Update()
{
    if (_actionProp.action.IsPressed())
    {
        print("ボタンが押されている!");
    }

    if (_actionProp.action.WasPressedThisFrame())
    {
        print("ボタンが押された!");
    }

    if (_actionProp.action.WasReleasedThisFrame())
    {
        print("ボタンが離された!");
    }
}

この中でボタンの押下状態を各種APIから取得し、trueならログ出力しています。

performedが発火した瞬間を取得する

ボタン判定のもう一つの性質が異なるメソッドとして、Interactionがトリガーされた瞬間かどうかを判定するInputAction.WasPerformedThisFrameメソッドがあります。

public bool WasPerformedThisFrame()

これはボタンが押された瞬間を判定するInputAction.WasPressedThisFrameメソッドによく似ていますが、こちらはInteractionの設定を反映します。

例えば、長押しを表すHold Interactionが設定されていると、一定時間以上ボタンが押し込まれた(長押しされた)瞬間にtrueを返します。

参考:Class InputAction | Input System | 1.6.1

サンプルスクリプト

Interactionがトリガーされた瞬間にログ出力する例です。

PerformExample.cs
using UnityEngine;
using UnityEngine.InputSystem;

public class PerformExample : MonoBehaviour
{
    // 押された状態を判定するAction
    [SerializeField] private InputActionProperty _actionProp;

    private void Update()
    {
        if (_actionProp.action.WasPerformedThisFrame())
        {
            print("performedになった!");
        }
    }

    private void OnDestroy()
    {
        _actionProp.action.Dispose();
    }

    private void OnEnable()
    {
        _actionProp.action.Enable();
    }

    private void OnDisable()
    {
        _actionProp.action.Disable();
    }
}

上記をPerformExample.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチし、インスペクターよりActionの設定を行うと機能します。

例では1つ前の例と同じくゲームパッドのSouth Buttonと左スティックにしました。また、Actionには長押しを表すHold Interactionを設定し、0.5秒以上入力されたらInteractionがトリガー(performedが通知)されるようにしました。

実行結果

ボタンまたはスティックが一定時間(例では0.5秒以上)入力されたらコンソールにログ出力されるようになります。

スクリプトの説明

主な違いは次の部分です。

private void Update()
{
    if (_actionProp.action.WasPerformedThisFrame())
    {
        print("performedになった!");
    }
}

前述の例ではボタンを押した瞬間をWasPressedThisFrameメソッドで判定していましたが、長押しを含むInteractionがトリガーされた瞬間を判定するためにWasPerformedThisFrameメソッドを用いています。

使用上の注意点

ReadValueメソッドは特定条件を満たさないと0を返す

InputAction.ReadValueメソッドは、呼び出し元のInputActionインスタンスが有効化されていないと常に0を返します。

また、Interactionが開始されていない(Startedフェーズでない)かつトリガーされていない(Performedフェーズでない)時も常に0を返します。

InputAction.ReadValueメソッドから入力を受け取るためには、InputActionのInteractionのフェーズがStartedまたはPerformedになっている必要があります。

Startedフェーズは例えば入力され始めなどに遷移します。Performedフェーズボタンが一定の閾値以上押し込まれたり、一定時間以上長押しされた場合などに遷移します。

この辺の挙動はInteractionの設定によって異なります。Interactionの仕組みやフェーズ遷移の挙動については、次の記事で解説しています。

~ThisFrame系メソッドはUdpate Modeの設定に依存する

ボタンを押した瞬間、離した瞬間、トリガーされた瞬間かどうかを判定するWasPressedThisFrame、WasReleasedThisFrame、WasPerformedThisFrameメソッドは、Input System PackageのUpdate Modeの設定に依存します。

ここまで示したサンプルスクリプトがUpdateイベントで正常動作するのは、次のUpdate Modeの設定がProcess Events In Dynamic Updateになっているためです。

Update Modeには次の3種類の設定が可能です。

  • Process Events In Dynamic UpdateUpdateイベントでInput Systemの状態を更新する。
  • Process Events In Fixed UpdateFixedUpdateイベントでInput Systemの状態を更新する。
  • Process Events Manually手動で更新する設定。InputSyste.Updateメソッドで自分で更新する必要あり。

参考:Enum InputSettings.UpdateMode | Input System | 1.6.1

例えば、Update ModeにProcess Events In Fixed Updateを指定し、Updateイベントでボタンの押された判定を行うと、次のようにボタンの押された瞬間などを正しく判定できなくなります。

動画の例では、Input System側の状態更新周期がUpdate周期より長いため、このようにボタンが押された瞬間や離された瞬間に1度だけ反応すべきところが複数回反応してしまっています。

この逆としてボタンが押されたのに反応しないといったケースも起こり得ます。

WasPressedThisFrame、WasReleasedThisFrameメソッドはInteractionの影響を受けない

WasPressedThisFrame、WasReleasedThisFrameメソッドはInput Actionの入力値に対して処理するメソッドで、Interactionの設定を無視します。

例えば、Hold Interactionなどを指定しても長押しされた瞬間ではなく常にボタンが押された瞬間や離された瞬間を判定します。

Interactionの設定でこのような入力パターンを判定したい場合はWasPerformedThisFrameを使う必要があります。

さいごに

Input Systemには、旧Inputで存在していたようなGetAxisやGetButtonDownのようなメソッドが提供されています。

ただし、Update内でポーリング方式で状態をチェックするため、Update Modeの設定に強く依存したコードになってしまいます。

特にボタンを押した瞬間や離した瞬間を判定したい場合、このようなコードを書かなくてもコールバック方式で受け取ったほうがコードの無駄や柔軟性を担保できる場合が多いです。

やむを得ずUpdate内で処理しないといけない場面など、特別なケースを除いてはコールバックを使用したほうが安全でしょう。

関連記事

参考サイト

スポンサーリンク