Unityの新しいInput Systemに触れてみた

こんにちは。ファブテラスいわて役員の川田将宏です。
先日は、いわてメイカー展にお越しいただき誠にありがとうございました。
いわて未来アカデミーブースでも、生徒が初めて作ったゲームを公開し
多くのお客様に遊んでいただいたことを大変喜んでいました。

私もいわてメイカー展当日にゲームを公開しました。
今回紹介するUnityの新しいInput Systemを用いたゲームなのですが
今までとは何が違うのかを順を追って説明していきたいと思います。

Unityにおける入力システム

ゲームをやる上で、必要になるキーボードやコントローラーからの入力処理。
Unityでは、それらの処理をUnityEngine.InputというAPIを用いて処理していました。
これらはInput Managerで管理され、標準的な入力処理をおこなってきました。

if (Input.GetKeyDown(KeyCode.Space))
{
    ...
}

しかし、この入力システムは、Unityの初期バージョンから長い間使われてきました。
その間、ゲームで使用される入力デバイスにさまざまな変化がありました。
既存の入力システムでは、スマートフォンやタブレットなどのタッチデバイスへの対応がされておらず、コントローラーのようにタッチ入力の処理を行うことはできませんでした。
また、ゲーム実行中にゲームパッドを抜いて別のゲームパッドに差し替えたいという場合にも、対応していなかったなどの課題がありました。

そこで、そういった課題をまとめて解決しようと開発されたのがInput Systemになります。

Input Systemとは

ゲームパッド・キーボード・マウス・センサー等の各種入力デバイスからの入力を汎用的・統合的に取り扱うために導入されたUnityの新しい入力システム。
2020年4月にversion 1.0.0として正式版になりました。

using UnityEngine.InputSystem; // 追加
...
if (Keyboard.current.spaceKey.wasPressedThisFrame)
{
    ...
}

Input Systemでは、Actionという抽象的な操作に各種コントローラーの入力をバインドしてアプリケーション中から取り扱うことができます。
これについては後々、Unityの画面と一緒に説明します。

長々と説明をしてしまったので、早速触れていきたいと思います。

Input Systemを使う

実行環境

  • MacBook Pro (macOS Big Sur バージョン 11.5.2)
  • Unity Version 2020.3.19f1
  • Input System Version 1.0.2

環境構築

Input Systemを使うには
Window→Package Managerを開いて
左上にある「Packages」を「Unity Registry」に変換します。
すると、一覧が出てきますので、その中のInput Systemをインストールします。

すると、ダイアログが表示され、新しいInput System用のネイティブバックエンドを有効にするかを聞いてきます。
YesをクリックするとUnityが再起動します。(新しいInput System用のバックエンドに切り替えると、従来のUnityEngine.InputのAPIは使用できなくなりますので、既存のシステムに導入する場合は注意してください)

バックエンドの設定はEdit→Project SettingsのPlayer > Other Setting > Active Input Handlingに保存されており
いつでも変更することができます。Bothに設定すると新しいInput Systemと旧UnityEngine.Inputの両方のAPIを使用することができます。

これで、Input Systemを使う準備ができましたので、早速使ってみましょう。

Input Systemでキーボードの入力を取得

まず、最も簡単な方法で、キーボードからの入力を確認してみましょう。

キーボードの状態はKeyboard.currentで参照することができます。

using UnityEngine;
using UnityEngine.InputSystem; // 追加

public class KeyboardSample : MonoBehaviour
{
    void Update()
    {
        if (Keyboard.current.spaceKey.wasPressedThisFrame)
            Debug.Log("スペースキーが押された");
    }
}

キーボードの各キーの状態を参照するには、Keyboard.current.参照したいキーのように設定します。
今回はスペースキーを例としています。

キーそれぞれが持つ「キー状態」のプロパティは以下の通りです。

  • bool isPressed:押されているかどうか。(旧GetKeyに近い)
  • bool wasPressedThisFrame:押された瞬間かどうか。(旧GetKeyDownに近い)
  • bool wasReleasedThisFrame:離された瞬間かどうか。(旧GetKeyUpに近い)

つまり、このコードは、スペースキーが押されたら、「スペースキーが押された」という文をConsole画面に表示してくれるプログラムになります。

ですが、この書き方だと、旧UnityEngine.Inputと同じです。
なので、Input SystemのActionを使うことで、よりわかりやすい処理をおこないます。

Input SystemのActionを使う

いよいよInput Systemの本来の力を発揮するときがきました。
先程も説明したように、Input Systemでは、Actionという抽象的な操作に各種コントローラーの入力をバインドしてアプリケーション中から取り扱うことできます。

例えば、ゲーム内でジャンプをしたいというときに
仮に、キーボードではSpace、マウスでは左クリック、ゲームパッド(PS4コントローラー)では✕ボタンを対応するボタン、キーにしたいとなった。
その場合、先程のコードを繰り返し書くのは非常に手間がかかり、また、管理もしづらくなります。
こういった、ジャンプ、動くなどの動作に対応したキーをそれぞれまとめることができます。

空のGameObjectを作成して、Player Inputコンポーネントをアタッチしてください。
そして、「Create Actions…」というボタンがあるのでクリックしてください。
ダイアログが表示され、名前を入力できるのでわかりやすい名前を入力してください。
今回はデフォルトのままSaveを押します。

作成したTestInputをPlayer InputコンポーネントのActionsにアサインしてください。

これでひとまず準備は完了です。

続いて、作成したTestInputを開きます。すると、このような画面が表示されます。

左から順に説明していきます。
PlayerやUIといった部分がAction Maps(ゲーム中の操作モードのようなもの)があります。
そして、PlayerのAction MapsにはMove, Look, Fireの3つのActionsが設定されています。
これが先程言った、行動の部分になります。
試しにMoveを開いてみると、以下の画像のようになっています。

後ろの括弧書きで書かれているのが、どのデバイスからの入力であるかを表し
前の部分がどのキー(ボタン)の入力かを表している。

初期設定では、「動く(Move)」という抽象的な操作(行動)に、ゲームパッドの左スティック、キーボードの矢印キー、WASDといった具体的なデバイスの操作が関連付けられています。
そのため、Input Systemを使う場合は、こうしたバインドを設定したうえで、ただMoveというアクションについて移動処理を行えばいいということになります。

では、練習として、行動「表示させる(Print)」を定義して、キーボードのPを割り当ててみましょう。
そして、キーボードのPが押されたら”Input System Keyboard Sample”の文字列をConsoleに表示させてみましょう。

Actionの追加

Actionsの右にある+を押すと、以下の画像のように新しいアクション(帯が緑)が追加されます。
今回は、このアクションの名前を「Print」とします。

そして、<No Binding>と書かれた青の帯を押します。
すると、以下のようになるので、Binding > Pathを押します。

すると、以下のように、どのキーをバインドさせるかを選択できますが、最も簡単なListen機能があります。
左上の、Listenを押し、使用したいキーボード、今回の場合はPを押すことで、手元にあるコントローラーのこの操作にアクセスするにはどうすればいいんだっけ、と調べる必要がなくなります。

キーボードのPをバインドさせたら、「Use in control scheme」のキーボードのところにチェックをいれましょう。
ここまでできたら、上のSave Assetを押して、設定を保存します。

これで、デバイスの設定ができたので、あとは処理を実装していきます。

処理の実装

処理に関しては簡単なことしかしません。
下記スクリプトを先程作成したGameObjectにアタッチしてください。

using UnityEngine;
using UnityEngine.InputSystem; // 追加

public class PlayerActions : MonoBehaviour
{
    public void OnPrint(InputAction.CallbackContext context)
    {
        if (context.phase == InputActionPhase.Performed)
        {
            Debug.Log(context.control);
            Debug.Log("Input System Keyboard Sample");
        }
    }
}

今回は、イベントによって処理していくため、上記のようなスクリプトにしました。
InputAction.CallbackContext contextで、アクションをトリガーしたものに関するアクションコールバックに提供される情報を得られます。
つまり、Printという行動をしたときに、それに関する情報が得られます。
context.controlでは、アクションを実行したキーが帰ってきます。この場合、KeyboardのPが帰ってきます。
また、context.phaseでは、アクションの現在のフェーズが帰ってきます。
InputActionPhase.Performedで、アクションが実行されたばかりかどうかを判断しています。
つまり、このスクリプトは、キーボードPが押された瞬間のみ、キーボードPによって押されたことと、サンプルの文を表示させるようなものになっています。

今のままでは、このスクリプトを動かすことはできません。
先程作ったGameObjectのPlayer Inputを修正する必要があります。

Player InputのBehaviorをSend Messages→Invoke Unity Eventsに変更してください。

すると、下にEventsというツリーが表示されるので、展開します。
さらにその下にPlayerというツリーが表示されるので、展開します。
Printリストの右下にある+ボタンを押して、Player ActionsがアタッチされたGameObjectを登録します。
今回は、PlayerというGameObjectにPlayer Actionsをアタッチしたので、Playerを登録します。

PrintのNo FunctionをPlayer Actions > OnPrintに変更します。

以上で処理の実装が完了になります。

動作確認

動画では少しわかりにくいですが、きちんと押したボタンが反応していることがわかります。
他にも、別のボタンをバインドさせると以下のように、きちんとボタンを判別してくれます。

キーボードのP
キーボードのO
キーボードのI
といったように、一つの行動に複数個のキーを設定しても、処理が変える必要がないことが新しいInput Systemの大きな利点と言えるでしょう。

まとめ

今回は、入力システムに重きを置いた話をしました。
初歩的のように見えて実は奥深い入力システムには、頭を抱えることが多々あります。
しかし、Unityにおける入力システムは、新しくなったことによって、かなりゲーム開発者の手助けになっています。
今後は、いわて未来アカデミー「ゲームクリエイターコース」でも活用していきたいと考えています。
また、いわてメイカー展で発表したゲームは、要望があれば公開させていただきたいと考えております。

また、投稿記事がUnityに偏っているので、今後はGame Maker2のような、日本では少しマニアックなゲームエンジンについて触れていければと思っています。

それでは次の記事で会いましょう。