新InputSystem使ってみた
新しいInputSystemを使ってみたのですが、ハマったポイントがあったので、記事にします。
記事内で使用しているUnityのバージョンは、「2019.2.2f1」です。
tsubakit1.hateblo.jp
tsubakit1.hateblo.jp
gametukurikata.com
基本的には上の記事でほとんどのことは分かるのですが、改めて導入から解説してみます。
InputSystemを導入する
Window→Package Managerを開きます。
「InputSystem」をインストールします。
※InputSystemが表示されない方は、「Show preview packages」にチェックを入れると
表示されます。
ProjectSettingsを開きます。
「Player」のActive Input Handlingの箇所を変更します。
Input System (Preview)かBothに変更します。
設定ファイルを作成します。
Input System Packageで、Create settings assetボタンを押します。
使ってみる
InputSystemは、従来の
Input.GetButton("hoge");
のように、そのまま入力を取ることもできるのですが、
今回は従来のInputManagerのように、
予めアクションを登録する方法を紹介します。
今までInputManagerでアクションを管理していたところが、
新しいInputSystemでは、InputActionというシステムで管理することになります。
そして、InputActionをまとめたファイルを「InputActions」と呼びます。
InputActionを設定する
InputActionsは、プロジェクト内にファイルとして作ります。
Creates→InputActionsを選択すると、
その場所にInputActionsファイルが作成されます。
記事内ではこのファイルに「NewControl」という名前を付けたとして進行します。
NewControlをダブルクリックすると、InputActionsのウィンドウが開きます。
ここでアクションを登録していきます。
ActionMapsは、ActionMapを管理する場所です。
ActionMapとは、これから登録していくActionをまとめたものとなります。
今回はアウトゲーム用とインゲーム用、2種類のActionMapを作ることにします。
作りました。
Action Mapsと書いてあるところの右の+ボタンで作成できます。
続いて、Actionを登録していきます。
OutGameでは、主にアウトゲームのUI周りの入力を想定しているので、
スタートボタン(Start)
上選択ボタン(Up)
下選択ボタン(Down)
左選択ボタン(Left)
右選択ボタン(Right)
決定ボタン(Decide)
キャンセルボタン(Cancel)
を登録することにします。
ActionMapsを作成した時に、New actionというActionが既にあるので、
そのActionをStartという名前に変更し、Enterキーを割り当てます。
Start→Enterキー
Up→上キーとWキー
Down→下キーとSキー
Left→左キーとAキー
Right→右キーとDキー
Decide→Enterキー
Cancel→Backspaceキー
に登録することにします。
一つのアクションに複数の入力を対応させるには、
登録したいActionの右の+ボタンをクリックし、
Add Bindingを選択します。
追加しました。
今回はゲームパッドも対応させたいので、
ゲームパッドのボタンも登録していきます。
ゲームパッドはXboxコントローラーを想定します。
Start→スタートボタン
Up→左スティック上と十字キー上と右スティック上
Down→左スティック下と十字キー下と右スティック下
Left→左スティック左と十字キー左と右スティック左
Right→左スティック右と十字キー右と右スティック右
Decide→Bボタン
Cancel→Aボタン
を割り当てます。
ゲームパッド用のボタンはGamePadのところにあります。
ひとまず感覚でそれっぽいボタンを設定していきます。
(知る方法はありますが後述)
登録が終わりました。
続いて、InGameの方も追加したいと思います。
InGameでは、インゲームのプレイヤーの入力を想定してActionを設定していきます。
上移動
下移動
左移動
右移動
アクション(Act)
を登録したいので、
Up→上キーとWキーと左スティック上と十字キー上と右スティック上
Down→下キーとSキー左スティック下と十字キー下と右スティック下
Left→左キーとAキーと左スティック左と十字キー左と右スティック左
Right→右キーとDキーと左スティック右と十字キー右と右スティック右
Act→マウスの左クリック
といきたいところですが、今回はゲームパッドも対応させたい、
それに、プレイヤーの移動はアナログ入力にしたい・・・ということで、
移動→Move
アクション→Act
とし、
Move→上下左右キーとWASDキーと左スティックと十字キーと右スティック
Act→マウスの左クリックとBボタン
として登録することにします。
選択しているActionMapをInGameにし、Actionを登録していくのですが、
スティック/ボタン入力とキー入力とで設定方法が少し異なります。
スティック入力に関しては、
キーを設定する箇所で、Up/Down/Left/Rightではなく、
スティックそのものを選択します。
キー入力に関しては、Bindingを追加するところで、
先ほど解説したAdd Bindingではなく、
Add 2D Vector Compositeを選択します。
すると、上下左右のBindingがセットになったActionが作成されました。
あとは先ほど同様、設定していきます。
マウスの左ボタンはMouse→Left Buttonになります。
設定が終わりました。
右上らへんのAuto-Saveにチェックをつけていない場合は、
上らへんにあるSave Assetをクリックして保存しましょう。
※ちなみに、テラシュールブログさんの記事にあった
Control Schemaを使ってみましたが、
ゲームパッドの入力が取れなくなってしまったので、
今回は導入を見送りました。
実装する
先ほど設定したInputActionsを実際に使用してみます。
Actionをスクリプト側で使うには、
作成したアクション名の先頭に「On」を付けたpublicなメソッドを作成します。
今回はOutGameのActionMapをテストするスクリプトを作成します。
using UnityEngine; public sealed class InputSystemTestForOutGame : MonoBehaviour { //======================================== // Methods. //======================================== //---------------------------------------- // Event methods. //---------------------------------------- public void OnStart() { Debug.Log("start"); } public void OnUp() { Debug.Log("up"); } public void OnDown() { Debug.Log("down"); } public void OnLeft() { Debug.Log("left"); } public void OnRight() { Debug.Log("right"); } public void OnDecide() { Debug.Log("decide"); } public void OnCancel() { Debug.Log("cancel"); } }
そして、このスクリプトをシーン内のGameObjectにアタッチし、
同じ場所にPlayerInputをアタッチします。
Default MapはOutGameを選択します。
これで実行すると、入力に応じて、ログが出力されます。
InputActionsを使うことができました!
ただ、スティックのようなアナログ入力だと、
1フレームに何度も入力が入ってしまいます。
基本的に1フレーム1入力でよいので、
Update()内で入力を処理するよう変更します。
また、アナログ入力に関して、入力のコールバックを受け取りたいため、
PlayerInputを使うのではなく、InputActionsのスクリプトを用いる方法に変更します。
New Controlsを選択し、Inspectorからスクリプトを作成します。
すると、New Controlの場所に、同名のスクリプトが作成されます。
InputActionsファイルにNew Controlsとは違う名前を付けていると、
作成されるスクリプトやクラス名もその名前で作られます。
そして、テスト用のスクリプトを修正します。
using UnityEngine; using UnityEngine.InputSystem; public sealed class InputSystemTestForOutGame : MonoBehaviour, NewControls.IOutGameActions { //======================================== // Properties. //======================================== private NewControls.OutGameActions Input { get; set; } = default; private bool PressedStart { get; set; } = false; private bool EnteredUp { get; set; } = false; private bool EnteredDown { get; set; } = false; private bool EnteredLeft { get; set; } = false; private bool EnteredRight { get; set; } = false; private bool PressedDecide { get; set; } = false; private bool PressedCancel { get; set; } = false; //======================================== // Methods. //======================================== //---------------------------------------- // MonoBehaviour methods. //---------------------------------------- private void Awake() { Input = new NewControls.OutGameActions(new NewControls()); Input.SetCallbacks(this); } private void Update() { if (PressedStart) { Debug.Log("start"); PressedStart = false; } else if (EnteredUp) { Debug.Log("up"); EnteredUp = false; } else if (EnteredDown) { Debug.Log("down"); EnteredDown = false; } else if (EnteredLeft) { Debug.Log("left"); EnteredLeft = false; } else if (EnteredRight) { Debug.Log("right"); EnteredRight = false; } else if (PressedDecide) { Debug.Log("decide"); PressedDecide = false; } else if (PressedCancel) { Debug.Log("cancel"); PressedCancel = false; } } private void OnDestroy() { Input.Disable(); } private void OnEnable() { Input.Enable(); } private void OnDisable() { Input.Disable(); } //---------------------------------------- // Event methods. //---------------------------------------- public void OnStart(InputAction.CallbackContext context) { PressedStart = true; } public void OnUp(InputAction.CallbackContext context) { if (context.ReadValue<float>() >= 1.0f) { EnteredUp = true; } } public void OnDown(InputAction.CallbackContext context) { if (context.ReadValue<float>() >= 1.0f) { EnteredDown = true; } } public void OnLeft(InputAction.CallbackContext context) { if (context.ReadValue<float>() >= 1.0f) { EnteredLeft = true; } } public void OnRight(InputAction.CallbackContext context) { if (context.ReadValue<float>() >= 1.0f) { EnteredRight = true; } } public void OnDecide(InputAction.CallbackContext context) { PressedDecide = true; } public void OnCancel(InputAction.CallbackContext context) { PressedCancel = true; } }
InputActionのコードを簡略化するため、
using UnityEngine.InputSystem;
を追加します。
OutGameのActionMapのインターフェースを継承します。
public sealed class InputSystemTestForOutGame : MonoBehaviour, NewControls.IOutGameActions
インターフェース名はActionMap名により変わります。
先ほどの手順で作成したInputActionsのスクリプト内のクラスを生成し、
有効/無効化するコードを追加します。
private NewControls.OutGameActions Input { get; set; } = default; private void Awake() { Input = new NewControls.OutGameActions(new NewControls()); Input.SetCallbacks(this); } private void OnDestroy() { Input.Disable(); } private void OnEnable() { Input.Enable(); } private void OnDisable() { Input.Disable(); }
Action用のメソッドの引数にコールバックを追加します。
アナログ入力の場合は、入力がfloatの0~1で取得できますので、
1の時のみ入力を判定するようにします。
public void OnStart(InputAction.CallbackContext context) { PressedStart = true; } public void OnUp(InputAction.CallbackContext context) { if (context.ReadValue<float>() >= 1.0f) { EnteredUp = true; } } public void OnDown(InputAction.CallbackContext context) { if (context.ReadValue<float>() >= 1.0f) { EnteredDown = true; } } public void OnLeft(InputAction.CallbackContext context) { if (context.ReadValue<float>() >= 1.0f) { EnteredLeft = true; } } public void OnRight(InputAction.CallbackContext context) { if (context.ReadValue<float>() >= 1.0f) { EnteredRight = true; } } public void OnDecide(InputAction.CallbackContext context) { PressedDecide = true; } public void OnCancel(InputAction.CallbackContext context) { PressedCancel = true; }
入力をプールする変数を用意し、それをUpdate()内で処理します。
private bool PressedStart { get; set; } = false; private bool EnteredUp { get; set; } = false; private bool EnteredDown { get; set; } = false; private bool EnteredLeft { get; set; } = false; private bool EnteredRight { get; set; } = false; private bool PressedDecide { get; set; } = false; private bool PressedCancel { get; set; } = false; private void Update() { if (PressedStart) { Debug.Log("start"); PressedStart = false; } else if (EnteredUp) { Debug.Log("up"); EnteredUp = false; } else if (EnteredDown) { Debug.Log("down"); EnteredDown = false; } else if (EnteredLeft) { Debug.Log("left"); EnteredLeft = false; } else if (EnteredRight) { Debug.Log("right"); EnteredRight = false; } else if (PressedDecide) { Debug.Log("decide"); PressedDecide = false; } else if (PressedCancel) { Debug.Log("cancel"); PressedCancel = false; } }
これにより、スティック入力が連続しなくなりました。
InputActionsのスクリプトを用いる際は、PlayerInputは使用しないので、
PlayerInputコンポーネントはRemoveしましょう。
InGame側も実装してみましょう。
using UnityEngine; using UnityEngine.InputSystem; public sealed class InputSystemTestForInGame : MonoBehaviour, NewControls.IInGameActions { //======================================== // Properties. //======================================== private NewControls.InGameActions Input { get; set; } = default; private Vector2 Velocity { get; set; } = Vector2.zero; private bool PressedAct { get; set; } = false; //======================================== // Methods. //======================================== //---------------------------------------- // MonoBehaviour methods. //---------------------------------------- private void Awake() { Input = new NewControls.InGameActions(new NewControls()); Input.SetCallbacks(this); } private void Update() { Debug.Log(Velocity); if (PressedAct) { Debug.Log("act"); PressedAct = false; } } private void OnDestroy() { Input.Disable(); } private void OnEnable() { Input.Enable(); } private void OnDisable() { Input.Disable(); } //---------------------------------------- // Event methods. //---------------------------------------- public void OnMove(InputAction.Callbackcontext context) { Velocity = context.ReadValue<Vector2>(); } public void OnAct(InputAction.CallbackContext context) { PressedAct = true; } }
上下左右の移動は、x, yそれぞれ-1~1のVector2が取得できます。
public void OnMove(InputAction.Callbackcontext context) { Velocity = context.ReadValue<Vector2>(); }
これで、インゲーム側もInputActionsを使うことができました!
基本的にはこれで問題ないのですが、
1台のPCに複数個ゲームパッドをつなぐ場合、
入力がどのゲームパッドからかが判別つきません。
なので、Device IDを使って判別することにします。
※場合によっては接続されているゲームパッドの数が合わないことがあり、
正常に判別できない場合があります。
参考程度に留めていただきますよう、よろしくお願いいたします。
今回は、InputUtilityというstaticなクラスに、
CallbackContextからプレイヤーのインデックスを取得するメソッドを作成しました。
(LINQを使用しています)
using UnityEngine.InputSystem; using System.Linq; public static class InputUtility { //======================================== // Methods. //======================================== public static int GetGamepadIndex(InputAction.CallbackContext context) { return GetGamepadIndex(context.control.device.deviceId); } /// <summary> /// ゲームパッドのインデックスを取得する. /// </summary> /// <param name="deviceId">ゲームパッドのデバイスID.</param> /// <returns>ゲームパッドのインデックス(1オリジン).</returns> public static int GetGamepadIndex(int deviceId) { var deviceIds = InputSystem.devices .Where(device => device is Gamepad) .Select(device => device.deviceId) .ToArray(); if ((deviceIds == null) || (deviceIds.Length == 0)) { return 1; } var index = System.Array.IndexOf(deviceIds, deviceId); if (index == -1) { return 1; } return index + 1; } }
public void OnAct(InputAction.Callbackcontext context) { var index = InputUtility.GetGamepadIndex(context); Debug.Log(index + "P"); }
ちなみに、
PlayerInputのOpenInputDebuggerを押すと、入力のテストに便利な情報が見れます。
また、Input Debugでゲームパッドをダブルクリックすると、
ゲームパッドのキー割り当てやDevice IDを見ることができます。
まとめ
今回、新しいInputSystemを使用してみましたが、感想として、
InputActionsでのActionの設定は便利だな~と思いました。
旧InputManagerは、複数ゲームパッドの入力の管理が地獄でしたので・・・
まだPreview版ですが、できることは多そうですし、結構使い心地はいいので、
早く正式版になってほしいなぁ・・・と思います。
今回は私も初めて使用してみたので、もっといいやり方があるかもしれません。
参考程度にしていただければ幸いです。