テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】新・新しいInput Systemの使い方(Inputsystem ver 1.0版)

以前書いた新しいInput Systemの記事の書き直しです。

新しいInput System(1.0)

 Unity 2019.3にて、新しいInput Systemのバージョンが1.0になりました。まぁ、まだPreviewなんですが。とはいえ1.0になったのでソコまでAPIが変化することは無いだろうと期待して、使い方的な記事を書いてみます。

f:id:tsubaki_t1:20191012195650j:plain
InputSystemもついにVer 1.0に…

導入

  1. Window > Package Managerでパッケージマネージャーを開く
  2. AdvanceShow Preview Packagesを有効にする
  3. Input System を選択して Installを押す
  4. PlayerSetting > Player > Active Input HandlingInput Manager(Old)からInput System Package に変更
  5. Unityエディターを再起動

f:id:tsubaki_t1:20191012200534j:plain
InputSystemの切り替えは勝手に行われている事もある

作るもの

 左右に移動してジャンプする…という超簡単な動作を作ります。

f:id:tsubaki_t1:20191013023755g:plain
左右に動いてジャンプするだけ

基本的な流れは下の通り。

  1. InputActionで、プレイヤーが行う操作とキーの割り当てを行う
  2. スクリプトでキー操作を受け取る処理を記述

以前の「キーの入力状態を取得して操作を記述する」と比較すると少し抽象的になっています。

手順1:Input Actionsを作る

 まずInputActionsを作ります。無くても使おうと思えば使えますが(例えばUnityEngine.InputSystem.Gamepad.current.leftStick等で)これをベースに作ると途中から辛くなってくるので、最初にInput Actionsを作ってしまいます。

 Input Actionsは、特定のアクションにどういった操作を割り当てるのかを定義するファイルです。InputSystemではInputActionsで定義したアクションに対して、スクリプトを割り当てるイメージで使えます。

f:id:tsubaki_t1:20191012203506j:plain
InputActionの定義内容

 まずはファイルを生成します。

  1. Assets > Create > Input ActionsでInputActionsを作成。ファイル名はPlayerActにする。
  2. PlayerActiを選択し、Generate C# Classにチェックを入れる。
  3. PlayerActiEdit Assetを選択。

 中身を設定していきます。アクション名を登録し、ActionTypeで入力が返す値を設定する感じです。

  1. ActionMap+をクリック。名前は横スクロールの操作なのでPlatformActionに設定。
  2. Actions+をクリックしてアクションを二つ作成、名前は移動操作の「Move」とジャンプの「Jump」に設定。
  3. MoveのActionTypeをValueに変更、Control TypeはVector2を設定。
  4. JumpのActionTypeをValueに変更、ControlTypeはButtonを設定。

f:id:tsubaki_t1:20191013031329j:plain
アクションを作成

 アクションにキー操作を登録します。今回はキーボード入力操作とゲームパッドの操作を登録します。キーボード入力は複数のキー入力を一つの入力に統合する2D Vector Compositeを使ってまとめます。

  1. Moveの右にある+を押しAdd Bindingを選択。BindingのPathをD-Pad[GamePad]を選択。
  2. Moveの右にある+を押しAdd 2D Vector Compositeを選択。
    UpW [Keyboard]を選択。 DownS [Keyboard]を選択。
    LeftA [Keyboard]を選択。 RightD [Keyboard]を選択。
  3. Jumpの右にある+を押し、Add Bindingを選択。
    BindingのPathにButton West[GamePad]を選択。
  4. Jumpの右にある+を押し、Add Bindingを選択。
    BindingのPathにSpace [Keyboard]を選択。
  5. Save Assetを選択

f:id:tsubaki_t1:20191013032046j:plain
InputActionsの設定

 次は作成した設定を使用してキャラクターを動かします。

手順2:キャラクターを動かす(コールバックで動かす場合)

 まずコールバックベースで動かすアプローチについてです。コールバックはInputActionsが自動で生成してくれるコードを使用して実現します。コードは指定が無ければInputActionと同じフォルダに生成されます。

 コードにはファイル名+ActionMapというインターフェースが含まれており、これを継承したクラスをActionMapに登録すれば、入力が入る度にコールバックが呼ばれるようになります。入力時のパラメーターはcontext.ReadValue<T>()で取得します。TがInputActionsのControlTypeと一致していないと動かないので注意が必要です。またControlTypeは実態と型が一致していない事もあるので、そのあたりも注意が必要です。例えばAxisやButtonはfloatです。

 新しいインプットシステムは、基本的にinput.Enable()input.Disable()が必要です。それと破棄時にはinput.Dispose()した方が良さそな気配があります。

public class MovePlayer : MonoBehaviour, PlayerAct.IPlatformActionActions
{
    PlayerAct.PlatformActionActions input;

    [SerializeField] MoveComponent move;
    float horizontal;

    void Awake()
    {
        // インプットを生成して、自身をコールバックとして登録
        input = new PlayerAct.PlatformActionActions(new PlayerAct());
        input.SetCallbacks(this);
    }

    // インプットの有効・無効化
    void OnDestroy() => input.Disable();
    void OnEnable() => input.Enable();


    void OnDisable() => input.Disable();
    void Update() => move.MoveHorizontal(horizontal);

    // --------------
    // コールバック
    // --------------
    public void OnJump(InputAction.CallbackContext context)
    {
        move.Jump();
    }

    public void OnMove(InputAction.CallbackContext context)
    {
        // 押しっぱなしの動作は、直接オブジェクトを動かすのではなく方向性のみを登録する
        horizontal = context.ReadValue<Vector2>().x;
    }
}

 ここで注意すべきは、このイベントは「値の変化時に呼ばれるっぽい」という点です。そのためMoveアクション(WASDキーにバインド)のような常に押し続ける操作はすぐに動きを停止してしまいます。これは値の変化を変数に格納し、後程Updateで反映することで回避出来ます。これバグかと思ったら仕様っぽいです。

 またコールバックは「一フレームに複数呼ばれる」事があります。New Input Systemはフレームの間の入力も取得する事ができます。コールバックは設定のタイミングで一気に処理されますが、入力時の時間はcontext.timeで確認出来ます。これをうまく使うと、基本的にフレームを落としても滑らかな操作といった事が可能かもしれません。

https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20190108/20190108222131.gif

手順2:キャラクターを動かす(ポーリングして動かす場合)

 コールバックの動作は便利ではありますが面倒くさいという点も確かにあります。いくつかのゲームは普通にUpdateのタイミングでプレイヤーが責任持って入力を取得する…といったモノの方が好ましいです。ということで、今度はポーリングスタイルで入力を取得します。

 使う場合は単純にActionMapのActionからReadValue<T>()で値を取得するだけです。

public class MovePlayer2 : MonoBehaviour
{
    [SerializeField] MoveComponent move;

    // インプットの登録と破棄
    PlayerAct input;
    void Awake() => input = new PlayerAct();
    void OnDisable() => input.Disable();

    // インプットの有効・無効化
    void OnDestroy() => input.Disable();
    void OnEnable() => input.Enable();


    void Update()
    {
        // スティックの移動を取得して動かす
        var velocity = input.PlatformAction.Move.ReadValue<Vector2>();
        move.MoveHorizontal(velocity.x);

        // ジャンプボタンが押されているか判定して動かす
        var isJumpButtonPressed = (input.PlatformAction.Jump.ReadValue<float>() >= InputSystem.settings.defaultButtonPressPoint);
        if (isJumpButtonPressed)
            move.Jump();
    }
}

 仕様かバグなのか微妙ですが、一つ注意点として「Interactions」という入力の動作をフィルタリングして特定の動作を行わないとアクションを呼ばない設定があるのですが、これが設定されてるとうまく動かない事があります。

f:id:tsubaki_t1:20191013040348j:plain
Jumpの場合は付けてると便利だけど、ポーリングに使うとジャンプ出来なくなる設定

 もしコールバックのように一フレーム内で起こった入力をすべて取得したい場合、下のような記述で取得できます。InputStateHistory recordsで観測するアクションを指定し、records.StartRecordingでアクションを格納していきます。なおInteractionsのようなアクションをフィルタする機能は使えないっぽいです。ストリームの値を読んでるだけなので是非もないね。

public class MovePlayer3 : MonoBehaviour
{
    [SerializeField] MoveComponent move;

    // インプットの登録と破棄
    PlayerAct input;
    InputStateHistory records;

    void Awake()
    {
        input = new PlayerAct();
        records = new InputStateHistory(input.PlatformAction.Jump.controls);
        records.historyDepth = 16;
    }
    void OnDestroy()
    {
        records.Dispose();
        input.Disable();
    }

    // インプットの有効・無効化
    void OnEnable() => records.StartRecording();
    void OnDisable() => records.StopRecording();

    void Update()
    {
        foreach (var record in records)
        {
            var isJumpPressed = record.ReadValue<float>() >= InputSystem.settings.defaultButtonPressPoint;
            if (isJumpPressed)
                move.Jump();
        }
    }
}

 以前はUnsafeを要求していたので、それと比べれば大分楽に使えるようになりましたが、2D Vector Compositeで使用するとうまく動作しなかったり、時々シャックリを起こしたように入力が取れなかったり、何となく妙な癖があります。

キーコンフィグ

動的にアクションに割り当てたキー操作を変更したり出来ます。

// ジャンプの操作にFキーを追加
input = new PlayerAction();
input.Move.Jump.AddBinding("<Keyboard>/f");

補足

 なおPlayerInputという簡単に使用できるハイレベルAPIが用意されていますが、これはUnity 2019.3では期待通りに動作しませんでした。正確にはUnityEventを使用するケースで入力内容を取得できません。同梱のサンプルも動作しないのでバグと思われます(2019.2では動作します)。

Unity 2019.3 b7だと正常に動作しました

関連

今回の記事が面倒くさいと感じた人向け

tsubakit1.hateblo.jp