【Unity】新しいInput System入門 - 従来のUnityEngine.Inputに代わる高機能な入力管理システム

Unity2020のリリースと同時にverifiedとなった新しいInput Systemの使い方をざっとまとめます。
全体感を把握することを目的としているので細かい部分の説明は割愛します。

Unity2020.1
Input System 1.0.0

インストール

Input SystemのインストールはPackage Managerから行います。
(PackageManagerの使い方はこちらの記事にまとめていますので必要に応じて参照してください。)

f:id:halya_11:20200727220718p:plain
インストール

パッケージをインストールすると以下のようなダイアログが表示されるのでYESを選択します。

f:id:halya_11:20200727220821p:plain
ダイアログ

するとUnityEditorが再起動され、Input Systemが有効化され、旧Input Managerが無効化されます。
なおInput Systemや旧Input Managerの有効/無効はEdit > Project Settings > Playerから切り替えることもできます。

f:id:halya_11:20200727221920p:plain
切り替え

Input Systemと旧Input Managerを両方有効にすることもできますが、
プロジェクトとしてInput Systemしか使わない方針であれば間違いを防ぐためにも旧Input Managerは無効化しておきましょう。
無効化しておけば間違えて古い方を使ってしまってもエラーログが出力されます。

f:id:halya_11:20200727222248p:plain
エラーログ

基本的な入力の受け取り方(UI以外)

さてまずこの節でInput Systemにおける基本的な入力の受け取り方をまとめます。
この方法は、例えばタッチスクリーンのタッチやコントローラによる操作などを検知するものです。
UI(uGUI)とInput Systemとの連携については後述します。

Input Actionアセットを作る

Input Systemで入力を受け取るためにはまずInput Actionアセットを作る必要があります。
Input ActionはAssets > Create > Input Actionsから作成できます。
今回はExampleControlsという名前で作成しました。

f:id:halya_11:20200817221538p:plain
Input Actionアセット

次にアセットをダブルクリックするか、InspectorのEdit assetボタンからアセットのエディタを開きます。

f:id:halya_11:20200817215114p:plain
エディタ

ここから受け取る入力の設定をしていきます。
まずはAction Mapsの+ボタンを押してActionを作成します。
今回はプレイヤーからの入力を想定しているためPlayerという名前にしました。

f:id:halya_11:20200817215309p:plain
Action MapsにPlayerを追加

次にActionsを編集していきます。
既にActions Mapを作った時にNew actionというActionが自動的に追加されているためこれを使います。
以下のように名前をFireに変更し、BindingにはKeyboardのSpaceを選択します。

f:id:halya_11:20200817220302p:plain
Binding設定

これでキーボードのスペースキーの入力がFireという名前のActionで検知できる設定ができました。
ついでにFireアクションのBindingを一つ追加して、以下のようにタッチスクリーンの設定を行います。

f:id:halya_11:20200817220925p:plain
タッチスクリーンの設定

これで、キーボードのスペースキーあるいはタップが行われた際にFireが呼ばれる設定になりました。
ここまでできたらツールバーのSave Assetをクリックして内容を保存しておきます。

Input Actionを制御するスクリプトを自動生成する

次に前節で設定したInput Actionを制御するスクリプトを生成します。
これはInput Systemが提供しているコードの自動生成機能を使います。
生成するには以下のようにInput ActionアセットのインスペクタからGenerate C# Classにチェックを入れてApplyボタンを押します。

f:id:halya_11:20200817221830p:plain
スクリプトを生成

するとInput Actionアセットと同名のスクリプトが生成されます。

f:id:halya_11:20200817223427p:plain
スクリプトが自動生成

入力を受け取る

次にこの自動生成されたクラスを使って実際に入力を受け取るスクリプトを書きます。
適当なクラスを定義し、以下のように[アセット名].I[アクション名]Actions という名前のインタフェースを実装します。
このインタフェースにはInput Actionアセットに定義したアクションに対応するコールバックメソッドが定義されているのでそれも実装します(下記のOnFire())。

using UnityEngine;
using UnityEngine.InputSystem;

public class Example : MonoBehaviour, ExampleControls.IPlayerActions // [アセット名].I[アクション名]Actionsインタフェースを実装する
{
    private ExampleControls _input;
    
    private void Awake()
    {
        // アセット名と同名のクラスを生成する
        _input = new ExampleControls();
        
        // コールバックを登録する
        _input.Player.SetCallbacks(this);
    }

    // アクションに対応するコールバックメソッドがインタフェースに定義されている
    public void OnFire(InputAction.CallbackContext context)
    {
        // PhaseがPerformedのときにログ出力
        if (context.performed)
        {
            Debug.Log("Fire");
        }
    }

    private void OnEnable()
    {
        // 使用する前に有効化する必要がある
        _input.Enable();
    }

    private void OnDisable()
    {
        // 使用が終わったら無効化する
        _input.Disable();
    }

    private void OnDestroy()
    {
        _input.Dispose();
    }
}

上記の通りコールバックを登録して有効化すれば、入力があった時に各コールバックメソッドが呼ばれるようになります。

f:id:halya_11:20200817224944p:plain
コールバックが呼ばれる

正常に入力を取得することができました。

入力イベントの色々な受け取り方

さてInput Systemにおける入力イベントの受け取り方にはいくつかの方法があります。

インタフェースを実装する

一つ目はインタフェースを実装する方法です。
これは既に上記で説明した方法なので詳細は割愛しますが、補足としてPhaseの種類についてだけ説明します。

アクションのコールバックはStarted、Performed、CanceledなどのPhaseを持っています。
例えば「長押し」のようなアクションを考えた時、長押しが始まった時にStartedが呼ばれ、
指定の時間だけ長押しが行われたときにはPerformedが呼ばれ、指定の時間が経過する前に長押しが終わった場合にはCanceledが呼ばれます。

従ってアクションが実行された時にだけ処理をしたければ上記の例のようにif (context.performed)と記述する必要があります。

コールバックを登録する

二つ目はコールバックを登録する方法です。
インタフェースを実装せずに以下のように直接コールバックを登録できます。

using System;
using UnityEngine;
using UnityEngine.InputSystem;

public class Example : MonoBehaviour
{
    private ExampleControls _input;
    
    private void Awake()
    {
        _input = new ExampleControls();
        // コールバック登録
        _input.Player.Fire.performed += _ => Debug.Log("Fire");
    }

    private void OnEnable()
    {
        _input.Enable();
    }

    private void OnDisable()
    {
        _input.Disable();
    }

    private void OnDestroy()
    {
        _input.Dispose();
    }
}
ポーリングする

最後に、MonoBehaviourのUpdateメソッドの中でポーリングする方法を紹介します。
この方法を使うと以下のように旧Input Managerと同じような書き方ができます。

using System;
using UnityEngine;
using UnityEngine.InputSystem;

public class Example : MonoBehaviour
{
    private ExampleControls _input;
    
    private void Awake()
    {
        _input = new ExampleControls();
    }

    private void Update()
    {
        // ポーリング
        if (_input.Player.Fire.triggered)
        {
            Debug.Log("Fire");
        }
    }
    
    private void OnEnable()
    {
        _input.Enable();
    }

    private void OnDisable()
    {
        _input.Disable();
    }

    private void OnDestroy()
    {
        _input.Dispose();
    }
}

ちなみに例えば二次元の値を受け取るようなアクションであった場合には以下のようにReadValue()を使うことで値を取得できます。

private void Update()
{
    Debug.Log(_input.Player.Some.ReadValue<Vector2>());
}

より手軽に入力を受け取れるPlayerInput

より手軽に入力イベントを受け取れるコンポーネントとしてPlayerInputというものが用意されています。
使い方としてはまず入力管理用のGameObjectにPlayerInputをアタッチします。
下図のようにActionsにはExampleControlsを入れ、BehaviourはSend Messagesに設定しておきます。

f:id:halya_11:20200818111734p:plain
Player Input

こうしておくと、入力があった際にそのアクションに対応したメソッドがGameObject.SendMessage()を使って呼ばれます。
つまり今回の例では以下のようにOnFire()を定義したMonoBehaviourをアタッチしておくだけで入力が検知できるようになります。

using UnityEngine;
using UnityEngine.InputSystem;

[RequireComponent(typeof(PlayerInput))]
public class Example : MonoBehaviour
{
    private void OnFire()
    {
        Debug.Log("Fire");
    }
}

また、BehaviorにはSend Messagesの他にもGameObject.BroadcastMessage()を使うBroadcast Messagesや、
UnityEventを呼び出すInvoke Unity Eventsといった設定もできます。

f:id:halya_11:20200818112035p:plain
色んなBehavior

ActionやActionMapをMonoBehaviourに直接シリアライズする

あまり使う機会はないと思いますが、ActionやActionMapをMonoBehaviourにシリアライズすることも可能です。
例えば以下のようにActionをシリアライズしてみます。

using UnityEngine;
using UnityEngine.InputSystem;

public class Example : MonoBehaviour
{
    [SerializeField] private InputAction _action;
    
    private void Start()
    {
        _action.performed += _ => Debug.Log("Performed");
    }

    private void OnEnable()
    {
        _action.Enable();
    }
    
    private void OnDisable()
    {
        _action.Dispose();
    }

    private void OnDestroy()
    {
        _action.Dispose();
    }
}

すると以下のようにInspectorからActionを設定できるようになります。

f:id:halya_11:20200818201258p:plain
InspectorからActionを設定

ActionMapの場合はこんな感じです。

f:id:halya_11:20200818201459p:plain
ActionMapをシリアライズ

uGUIとの連携

uGUIの入力検知にはデフォルトでは旧Input Managerが使われています。
入力検知にInput Systemを使うためにはuGUIを使うときに作られるEvent SystemというGameObjectにアタッチされている
Standalone Input ModuleをInput System UI Input Moduleに置き換える必要があります。

Input Systemが有効化されている場合、この置き換えを自動的にやってくれるボタンがStandalone Input Moduleに追加されます。

f:id:halya_11:20200818121710p:plain
変換ボタン

これを押下すると自動的にコンポーネントが置き換わり、uGUIの入力検知にInput Systemが使われるようになります。

f:id:halya_11:20200818122205p:plain
変換された

参考

docs.unity3d.com