アクションマップを動的に設定する

ページ更新日 :
ページ作成日 :

検証環境

Windows
  • Windows 11
Unity エディター
  • 2020.3.25f1
入力システムパッケージ
  • 1.2.0

この Tips の前提設定

この Tips の説明の前提として以下の設定を事前に行っています。

また、以下の Tips の内容を熟知しているものとします。

アクションマップの動的設定について

アクションマップについてはあらかじめプロジェクトに追加して設定する方法が一般的ですが、 この場合ゲーム実行中にコントローラーのボタンの割り当ては固定となり、ゲーム中にユーザーが自由に変更することができません。 主にこの場面が想定されるのはキーコンフィグなどが必要なゲームの場合です。

この Tips ではスクリプト内でアクションマップのキーの割り当てを任意に変更する方法について説明します。

アクションマップの動的変更処理

今回は初期状態のアクションマップもスクリプトで設定するようにし、途中のアクションマップ変更もスクリプトで行うようにします。 ゲーム起動時に保存されたキーコンフィグを読み込んで設定する場合はこの方法になるかと思います。

サンプルの内容としてはボタンを押すとアクションマップのキー割り当てを変更する、操作した内容をテキストで表示する、というものにします。 ボタンの配置と表示用テキストを図のように配置していますがあまり重要ではないので自由に配置してください。

アクションマップ初期設定処理

スクリプトを作成します。名前は任意ですがここでは InputActionMap とします。 今回処理はすべてここに記述しますが、実際の制作ではプロジェクトの作りに応じて分けてください。

スクリプトは以下のようにします。まずは起動時に初期設定のアクションマップを作成するようにします。

using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UI;

public class InputActionMap : MonoBehaviour
{
  /// <summary>情報を表示させるテキストオブジェクト。</summary>
  [SerializeField] private Text TextObject;

  /// <summary>Move アクション用の定義。</summary>
  public InputAction MoveAction { get; set; }

  /// <summary>Attack アクション用の定義。</summary>
  public InputAction AttackAction { get; set; }

  private void Awake()
  {
    // Move アクションの作成
    MoveAction = new InputAction("Move");

    // バインド(キー割り当て)の追加
    // 設定する文字列の形式はアクションマップ設定画面の Path に表示される文字列となる
    MoveAction.AddBinding("<Gamepad>/leftStick");

    // キーボードに上下左右を割り当てるにはコンポジットの 2DVector を設定する
    MoveAction.AddCompositeBinding("2DVector")
        .With("Up", "<Keyboard>/upArrow")
        .With("Down", "<Keyboard>/downArrow")
        .With("Left", "<Keyboard>/leftArrow")
        .With("Right", "<Keyboard>/rightArrow");

    // Attack アクションの作成
    AttackAction = new InputAction("Attack");

    // バインド(キー割り当て)の追加
    AttackAction.AddBinding("<Gamepad>/buttonSouth");
    AttackAction.AddBinding("<Keyboard>/z");

    // 操作時のイベントを設定
    MoveAction.performed += context => OnMove(context);
    MoveAction.canceled += context => OnMove(context);
    AttackAction.performed += context => OnAttack(context);
    AttackAction.canceled += context => OnAttack(context);
  }

  private void OnEnable()
  {
    // オブジェクトが有効になったときにアクションマップを有効にする
    MoveAction.Enable();
    AttackAction.Enable();
  }

  private void OnDisable()
  {
    // アクションマップが誤動作しないようにオブジェクト終了時に無効にする
    MoveAction.Disable();
    AttackAction.Disable();
  }

  /// <summary>
  /// Move 操作を行ったときに呼ばれる。
  /// </summary>
  /// <param name="context">コールバック内容。</param>
  public void OnMove(InputAction.CallbackContext context)
  {
    var vec = context.ReadValue<Vector2>();
    TextObject.text = $"Move:({vec.x:f2}, {vec.y:f2})\n{TextObject.text}";
  }

  /// <summary>
  /// Move 操作を行ったときに呼ばれる。
  /// </summary>
  /// <param name="context">コールバック内容。</param>
  public void OnAttack(InputAction.CallbackContext context)
  {
    var value = context.ReadValueAsButton();
    TextObject.text = $"Attack:{value}\n{TextObject.text}";
  }
}

まずフィールドに実装したいアクションの数だけ InputAction クラスで定義します。 ここでは前回のアクションマップの説明にも使用した「Move」と「Attack」用のフィールドを用意します。 アクションの数が増えればその数だけ宣言が必要ですが、多い場合は ListDictionary 等で管理しても構いません。 ここでは使っていませんが独自に InputActionMap クラスを用意しているのであればそちらで管理しても構いません。

初期化時に呼ばれる Awake メソッド内で InputAction のインスタンス生成とキー割り当てを行う AddBinding 等の処理を行います。 やってることはアクションマップの設定画面で Binding を追加している操作をそのままプログラムで行っているものと思っていただいてよいです。 AddBinding メソッド等に指定している文字列はアクションマップ設定画面の Path に表示される文字列と同じものとなります。 文字列を表示させる場合は右の「T」ボタンを押してください。

ボタンなどを操作したときに呼ばれるイベント処理などは通常のアクションマップのスクリプト版の処理と同じです。 コールバック処理もそのまま流用しています。

// 操作時のイベントを設定
MoveAction.performed += context => OnMove(context);
MoveAction.canceled += context => OnMove(context);
AttackAction.performed += context => OnAttack(context);
AttackAction.canceled += context => OnAttack(context);
/// <summary>
/// Move 操作を行ったときに呼ばれる。
/// </summary>
/// <param name="context">コールバック内容。</param>
public void OnMove(InputAction.CallbackContext context)
{
  var vec = context.ReadValue<Vector2>();
  TextObject.text = $"Move:({vec.x:f2}, {vec.y:f2})\n{TextObject.text}";
}

Enable, Disable 処理は InputAction 単位になるのでアクションの数だけ記述します。 面倒であれば List などで管理しても構いません。

private void OnEnable()
{
  // オブジェクトが有効になったときにアクションマップを有効にする
  MoveAction.Enable();
  AttackAction.Enable();
}

スクリプトを保存したら EventSystem にアタッチして表示用テキストオブジェクトを設定します。

ゲームを実行して入力情報が表示されるか確認してください。 結果はアクションマップ静的処理のスクリプト版と同じになるはずです。

アクションマップ動的変更処理

アクションマップの動的変更はボタンを押したときに行うようにするのでボタンを押したときに呼ばれるメソッドを定義しておきます。 ここでは OnClickButton としておきます。

// 省略

public class InputActionMap : MonoBehaviour
{
  // 省略

  /// <summary>
  /// ボタンをクリックしたときに呼ばれる。
  /// </summary>
  public void OnClickButton()
  {
  }
}

ボタンのクリックイベントを設定します。

アクションマップの書き換え処理を以下のようにします。

/// <summary>
/// ボタンをクリックしたときに呼ばれる。
/// </summary>
public void OnClickButton()
{
  TextObject.text = "アクションマップを変更しました。";

  // Move アクションのキーを置き換える
  MoveAction.ApplyBindingOverride(new InputBinding { path = "<Gamepad>/leftStick", overridePath = "<Gamepad>/dpad" } );
  MoveAction.ApplyBindingOverride(new InputBinding { path = "<Keyboard>/upArrow", overridePath = "<Keyboard>/w" });
  MoveAction.ApplyBindingOverride(new InputBinding { path = "<Keyboard>/downArrow", overridePath = "<Keyboard>/s" });
  MoveAction.ApplyBindingOverride(new InputBinding { path = "<Keyboard>/leftArrow", overridePath = "<Keyboard>/a" });
  MoveAction.ApplyBindingOverride(new InputBinding { path = "<Keyboard>/rightArrow", overridePath = "<Keyboard>/d" });

  // Attack アクションのキーを置き換える
  AttackAction.ApplyBindingOverride(new InputBinding { path = "<Gamepad>/buttonSouth", overridePath = "<Gamepad>/buttonEast" });
  AttackAction.ApplyBindingOverride(new InputBinding { path = "<Keyboard>/z", overridePath = "<Keyboard>/space" });
}

各アクションに ApplyBindingOverride メソッドが用意されているので path に初期設定したキーやボタンのパス、 overridePath に上書きしたいキーやボタンのパスを記述します。

ちなみにこれは上書きパスを設定しているだけですので元のパスはそのまま残ります。 例えば Attack アクションで z キーを space キーに変更しますが、 さらに space から x キーに変えたい場合は以下のように space を基準にするのではなく z キーに対して上書き記述になります。

AttackAction.ApplyBindingOverride(new InputBinding { path = "<Keyboard>/z", overridePath = "<Keyboard>/x" });

保存したらゲームを実行し、ボタンをクリックして操作するキーやボタンが変更されることを確認します。

今回は InputAction のインスタンスを生成したものに対してアクションマップの変更をしていますが、 GUI のアクションマップ設定で生成したスクリプトにも InputAction がありますのでやりやすい方で実装してみてください。