猫茶の研究日誌

ゲーム開発などの技術や、そのほか趣味などの雑記。

【Unity/InputSystem】新・操作中のコントローラーの種類を識別する

はじめに

Unityを使ったPCゲームで、上動画のように、

  • PS5コントローラなら「×で決定」
  • Xboxコントローラなら「Aで決定」
  • SwitchのProコンなら「Bで決定」
  • マウス&キーボードなら「左クリックで決定」

と、自動で表示を切り替えたいとき、ありますよね?
(余談ですが、私が学生時代で制作したゲームたちには、ほぼ100%この手の仕組みを入れてました。こだわり。)

そこで、「今使っている入力デバイスを識別できるようにしよう!」という内容の記事です。

本記事は、以前に書いた記事のリメイクです。
2年近く前に書いた「Unityでコントローラーの種別を識別する(InputSystem)」の内容から、いろいろ追加・書き直しをした内容です。
qiita.com 対応デバイスの追加、実装のアプローチの変更、デバイス変更時のイベント処理など、変更点盛り沢山。

動作確認環境

  • Unity 2022.3.18f1
  • Windows 11 22H2

サンプルソース

とりあえず早速、サンプルソース貼ります。

InputDeviceManager

今回の記事の内容のすべてのソースです。

シングルトンクラスとして実装してみました。
シングルトンなので、シーンに空のゲームオブジェクトを1つ作って本コンポーネントをセットしておく必要があります。

using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.InputSystem;

public class InputDeviceManager : MonoBehaviour
{
    // シングルトン
    public static InputDeviceManager Instance { get; private set; }

    /// <summary>
    /// 入力デバイスの種別
    /// </summary>
    public enum InputDeviceType
    {
        Keyboard,   // キーボード・マウス
        Xbox,       // Xboxコントローラー
        DualShock4, // DualShock4(PS4)
        DualSense,  // DualSense(PS5)
        Switch,     // SwitchのProコントローラー
    }

    // 直近に操作された入力デバイスタイプ
    public InputDeviceType CurrentDeviceType { get; private set; } = InputDeviceType.Keyboard;

    // 各デバイスのすべてのキーを1つにバインドしたInputAction(キー種別検知用)
    private InputAction keyboardAnyKey = new InputAction(type: InputActionType.PassThrough, binding: "<Keyboard>/AnyKey", interactions: "Press");
    private InputAction mouseAnyKey = new InputAction(type: InputActionType.PassThrough, binding: "<Mouse>/*", interactions: "Press");
    private InputAction xInputAnyKey = new InputAction(type: InputActionType.PassThrough, binding: "<XInputController>/*", interactions: "Press");
    private InputAction dualShock4AnyKey = new InputAction(type: InputActionType.PassThrough, binding: "<DualShockGamepad>/*", interactions: "Press");
    private InputAction detectDualSenseAnyKey = new InputAction(type: InputActionType.PassThrough, binding: "<DualSenseGamepadHID>/*", interactions: "Press");
    private InputAction switchProControllerAnyKey = new InputAction(type: InputActionType.PassThrough, binding: "<SwitchProControllerHID>/*", interactions: "Press");

    // 入力デバイスタイプ変更イベント
    public UnityEvent OnChangeDeviceType { get; private set; } = new();

    private void Awake()
    {
        // シングルトン
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }

        // キー検知用アクションの有効化
        keyboardAnyKey.Enable();
        mouseAnyKey.Enable();
        xInputAnyKey.Enable();
        dualShock4AnyKey.Enable();
        detectDualSenseAnyKey.Enable();
        switchProControllerAnyKey.Enable();
    }

    
    private void Start()
    {
        // 初回のみ、必ず入力デバイスの種別検知を行ってコールバック発火
        StartCoroutine(InitializeDetection());
    }

    private void Update()
    {
        // 検知の更新処理
        UpdateDeviceTypesDetection();
    }

    /// <summary>
    /// 入力デバイスの種別検知を初期化する
    /// </summary>
    /// <returns></returns>
    IEnumerator InitializeDetection()
    {
        // 入力デバイスの種別検知を更新
        UpdateDeviceTypesDetection();
        // 1フレーム待機
        yield return null;
        // イベント強制発火
        OnChangeDeviceType.Invoke();
    }

    
    /// <summary>
    /// 入力デバイスの種別検知を更新する
    /// </summary>
    public void UpdateDeviceTypesDetection()
    {
        var beforeDeviceType = CurrentDeviceType;

        if (xInputAnyKey.triggered)
        {
            CurrentDeviceType = InputDeviceType.Xbox;
        }

        // DualSense(PS5)は、DualShock4(PS4)としても認識される。
        // つまり、DualSenseを操作しているときは、DualSchock4とDualSenseの両方が検知される。
        // DualSenseとDualShockの両方から同時に入力検知した場合は、DualSenseとして扱うようにする。
        if (dualShock4AnyKey.triggered)
        {
            CurrentDeviceType = InputDeviceType.DualShock4;
        }
        if (detectDualSenseAnyKey.triggered)
        {
            CurrentDeviceType = InputDeviceType.DualSense;
        }

        if (switchProControllerAnyKey.triggered)
        {
            CurrentDeviceType = InputDeviceType.Switch;
        }

        if (keyboardAnyKey.triggered || mouseAnyKey.triggered)
        {
            CurrentDeviceType = InputDeviceType.Keyboard;
        }

        // 操作デバイスが切り替わったとき、イベント発火
        if (beforeDeviceType != CurrentDeviceType)
        {
            OnChangeDeviceType.Invoke();
        }
    }
}

使い方説明のサンプルソース

下画像のように、操作中のデバイスが変わったときに、そのデバイスの種別をコンソールに出力するサンプルです。

using UnityEngine;

public class InputDeviceDetectionExample : MonoBehaviour
{
    private void Start()
    {
        // イベントハンドラの登録
        InputDeviceManager.Instance.OnChangeDeviceType.AddListener(OnChangeDeviceTypeHandler);
    }

    private void OnDestroy()
    {
        // イベントハンドラの解除
        InputDeviceManager.Instance.OnChangeDeviceType.RemoveListener(OnChangeDeviceTypeHandler);
    }

    private void OnChangeDeviceTypeHandler()
    {
        // 入力デバイスの種別が変更されたときの処理
        Debug.Log("入力デバイスの種別が変更されました。\n現在の入力デバイスの種別:" + InputDeviceManager.Instance.CurrentDeviceType);
    }
}

解説

ざっくりとだけ解説しておきます。

「いま使われているか?」は、直前のフレームでいずれかのボタンが押されたかで判定します。
このために、「毎フレーム、それぞれのデバイスについて、すべてのボタンの状態を確認する」 ということをやっていきます。

"いずれかのキー"が入力されたかの監視

すべてのキーを1つの入力と捉えるInputActionを作成

バイス種ごとに、すべてのキーを1つの入力と捉えるInputActionを作成します。
これにより、各デバイス種ごとに「いずれかのキーが押されたか?」を確認できるようになります。

InputActionのコンストラクタでbinding"<デバイス名>/*"を指定することで、そのデバイスのすべての入力を対象にしたアクションを作成できます。

...
    // 各デバイスのすべてのキーを1つにバインドしたInputAction(キー種別検知用)
    private InputAction mouseAnyKey = new InputAction(type: InputActionType.PassThrough, binding: "<Mouse>/*", interactions: "Press");
...

キーボードには「AnyKey」がもとからあるのでそれを使います。

   private InputAction keyboardAnyKey = new InputAction(type: InputActionType.PassThrough, binding: "<Keyboard>/AnyKey", interactions: "Press");
入力デバイス名の調べ方
InputActionのコンストラクタで指定するデバイス名は、以下の手順で調べることができます。
下動画と併せてご参考ください…👀
  1. なんでもいいのでInputActionの設定を開く。
  2. 任意のBindで、確認したい対象のデバイスのボタンを登録する。
    (下動画ではListenボタンを使って登録してます)
  3. 「T」ボタンを押して、登録されたPathを確認する。
    (冒頭の「<>」で囲まれた部分がデバイス名です。「<DualSenseGamepadHID>/dpad/down」なら、コンストラクタに登録するPathは「<DualSenseGamepadHID>/*」です。)

作成したInputActionの有効化

Awake関数内で、各InputActionを有効化しておくことをお忘れなく。

        // キー検知用アクションの有効化
        keyboardAnyKey.Enable();
        mouseAnyKey.Enable();
        xInputAnyKey.Enable();
        dualShock4AnyKey.Enable();
        detectDualSenseAnyKey.Enable();
        switchProControllerAnyKey.Enable();

入力されたか判定

あとは、各デバイスについて、毎フレームtriggeredを確認するだけです。
trueになっていれば、何かしら操作がされたということです。

if (xInputAnyKey.triggered)
{
    // XInputデバイスに、"いずれかのキー"の入力があった!
}

毎フレーム操作されたデバイスを確認する

さきほどの方法で、毎フレームすべての入力デバイスについて確認していくだけです。

...
        if (xInputAnyKey.triggered)
        {
            CurrentDeviceType = InputDeviceType.Xbox;
        }

        if (switchProControllerAnyKey.triggered)
        {
            CurrentDeviceType = InputDeviceType.Switch;
        }
...
        if (keyboardAnyKey.triggered || mouseAnyKey.triggered)
        {
            CurrentDeviceType = InputDeviceType.Keyboard;
        }
...

DualSenseコントローラーについて

…が、DualSense(PS5コントローラー)について、注意点があります。

DualSenseは、Unity上ではDualShock4(PS4コントローラー)としても認識されます

つまり、DualSenseで〇ボタンを押したら、同時にDualShock4でも〇ボタンが押されたと認識されます。
そのため、必ずDualShock4の判定を終えてから、DualSenseの判定を行う必要があります。 (DualSenseが入力中は、DualShock4が入力されていても無視する)

...
        // DualSense(PS5)は、DualShock4(PS4)としても認識される。
        // つまり、DualSenseを操作しているときは、DualSchock4とDualSenseの両方が検知される。
        // DualSenseとDualShockの両方から同時に入力検知した場合は、DualSenseとして扱うようにする。
        if (dualShock4AnyKey.triggered)
        {
            CurrentDeviceType = InputDeviceType.DualShock4;
        }
        if (detectDualSenseAnyKey.triggered)
        {
            CurrentDeviceType = InputDeviceType.DualSense;
        }
...

バイスが切り替わった瞬間の通知

毎フレームの各デバイスについての入力されたか判定の際に、最後のフレームから変わったかを確認しておきます。

この際、変わったときのイベント等を用意しておくと便利です

 // 入力デバイスタイプ変更イベント
public UnityEvent OnChangeDeviceType { get; private set; } = new();
...
        var beforeDeviceType = CurrentDeviceType;
...(各デバイスについての入力されたか判定)
        // 操作デバイスが切り替わったとき、イベント発火
        if (beforeDeviceType != CurrentDeviceType)
        {
            OnChangeDeviceType.Invoke();
        }
...

初期化処理的なもの

初回フレームで、「操作中のデバイスの確認」、「強制的に変更時イベントを発火」をしておく必要があります。

これをしておかないと、デバイスの切り替え検知が正しく動かないことがあるので…

   private void Start()
    {
        // 初回のみ、必ず入力デバイスの種別検知を行ってコールバック発火
        StartCoroutine(InitializeDetection());
    }

    /// <summary>
    /// 入力デバイスの種別検知を初期化する
    /// </summary>
    /// <returns></returns>
    IEnumerator InitializeDetection()
    {
        // 入力デバイスの種別検知を更新
        UpdateDeviceTypesDetection();
        // 1フレーム待機
        yield return null;
        // イベント強制発火
        OnChangeDeviceType.Invoke();
    }

使い方

シングルトンクラスなので、シーンに空のゲームオブジェクト作ってInputDeviceManagerコンポーネント付けてあげれば、シーンのどこからでも使える。

いま使っているデバイス種を知りたければ、以下で取ってこれます。

InputDeviceManager.Instance.CurrentDeviceType

変更した際のイベントを登録しておけば、キーガイドのアイコンを切り替えたりするときに便利。

    private void Start()
    {
        // イベントハンドラの登録
        InputDeviceManager.Instance.OnChangeDeviceType.AddListener(OnChangeDeviceTypeHandler);
    }

おわりに

以前の記事では、すべてのキーに対してfor文で認識してましたが、今回はすべての入力をまとめてバインドしたInputActionを作成するというアプローチに変えてみました。

マウス移動やアナログパッドのような入力も取ってこれる上に、今後新たなコントローラに対応したくなった際にも、デバイス名を確認すれば理論上いくらでも可能だったりと、メリットの多い方式だと思います。
ちょっと実装長くなっちゃうけど。

とりあえず、いつか書き直したい記事ナンバーワンを書き直せて満足です。
(こんどは実際にキーアイコンを切り替えるところの解説書きたいなぁ…書いてる時間あるかなぁ…)

…あと、本記事冒頭に出てきたドタバタ対戦ゲーム「Prank Heart」は絶賛販売中!
特にフレンドと遊ぶと盛り上がると評判なので、ぜひ遊んでみてね。(宣伝)

store.steampowered.com