UnityにおけるInputAction管理とアクションマップ切り替え方法

本資料では、Unityの新Input SystemにおけるInputActionの取得方法および管理方法、さらにコードによるアクションマップの切り替え手法について、以下の内容をまとめています。

  • コード内で直接InputActionを取得する方法(FindActionを使用)
  • インスペクターからInputActionReferenceで割り当てる方法
  • コードでのアクションマップ切り替えサンプル
  • 使用目的に応じた選択のポイント

1. 現状のコードと課題

1.1 現状のコード(FindActionを使用する方法)

以下のコードは、PlayerInputコンポーネントからcurrentActionMap.FindAction("Move")FindAction("Jump")を用いて、InputActionをスクリプト内で直接取得する方法です。

using UnityEngine;
using UnityEngine.InputSystem;

[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(PlayerInput))]
public class PlayerController : MonoBehaviour
{
    [SerializeField] private Animator animator; // Animatorのキャッシュ
    [SerializeField] private float moveSpeed = 3; // 移動速度
    [SerializeField] private float jumpPower = 3; // ジャンプ力
    [SerializeField] private float rotationSpeed = 10f; // 回転速度

    private CharacterController _characterController; // CharacterControllerのキャッシュ
    private Transform _transform; // Transformのキャッシュ
    private Vector3 _moveVelocity; // キャラの移動速度情報
    private InputAction _move; // Moveアクションのキャッシュ
    private InputAction _jump; // Jumpアクションのキャッシュ

    private void Start()
    {
        _characterController = GetComponent<CharacterController>(); // 毎フレームアクセスするので、負荷を下げるためにキャッシュしておく
        _transform = transform; // Transformもキャッシュすると少しだけ負荷が下がる

        var input = GetComponent<PlayerInput>();
        // PlayerInputの「Default Map」で指定されているアクションマップを有効化
        input.currentActionMap.Enable();
        // アクションマップからアクションを取得するには FindAction() を使う
        _move = input.currentActionMap.FindAction("Move");
        _jump = input.currentActionMap.FindAction("Jump");
    }

    private void Update()
    {
        Debug.Log(_characterController.isGrounded ? "地上にいます" : "空中です");

        // Moveアクションを使った移動処理(慣性を無視しているので、キビキビ動く)
        var moveValue = _move.ReadValue<Vector2>();
        _moveVelocity.x = moveValue.x * moveSpeed;
        _moveVelocity.z = moveValue.y * moveSpeed;

        // 移動スピードを animator に反映する
        animator.SetFloat("MoveSpeed", new Vector3(_moveVelocity.x, 0, _moveVelocity.z).magnitude);

        // 移動方向が存在する場合にのみ回転処理を実行
        Vector3 moveDirection = new Vector3(_moveVelocity.x, 0, _moveVelocity.z);
        if (moveDirection != Vector3.zero)
        {
            // 目標の回転を計算
            Quaternion targetRotation = Quaternion.LookRotation(moveDirection);
            // 現在の回転から目標の回転に向かって補間
            _transform.rotation = Quaternion.Slerp(_transform.rotation, targetRotation, Time.deltaTime * rotationSpeed);
        }


        if (_characterController.isGrounded)
        {
            if (_jump.WasPressedThisFrame())
            {
                // ジャンプ処理
                Debug.Log("ジャンプ!");
                _moveVelocity.y = jumpPower; // ジャンプの際は上方向に移動させる
            }
        }
        else
        {
            // 重力による加速
            _moveVelocity.y += Physics.gravity.y * Time.deltaTime;
        }

        // オブジェクトを動かす
        _characterController.Move(_moveVelocity * Time.deltaTime);
    }
}

1.2 課題

  • コード上でInputActionを取得する方法は、エディタ上で直感的に設定(アウトレット接続)できないため、設定変更やチーム内での共有が難しい場合があります。

2. インスペクターからの割り当て:InputActionReferenceの利用

2.1 修正後のコード例

InputActionReferenceを用いることで、インスペクター上で直接InputActionをドラッグ&ドロップして割り当てられるようになります。

using UnityEngine;
using UnityEngine.InputSystem;

[RequireComponent(typeof(CharacterController))]
public class PlayerController : MonoBehaviour
{
    [SerializeField] private Animator animator;
    [SerializeField] private float moveSpeed = 3;
    [SerializeField] private float jumpPower = 3;
    [SerializeField] private float rotationSpeed = 10f;

    [SerializeField] private InputActionReference moveReference;
    [SerializeField] private InputActionReference jumpReference;

    private CharacterController _characterController;
    private Transform _transform;
    private Vector3 _moveVelocity;

    private void OnEnable()
    {
        _characterController = GetComponent<CharacterController>();
        _transform = transform;

        moveReference.action.Enable();
        jumpReference.action.Enable();
    }

    private void OnDisable()
    {
        moveReference.action.Disable();
        jumpReference.action.Disable();
    }

    private void Update()
    {
        var moveValue = moveReference.action.ReadValue<Vector2>();
        _moveVelocity.x = moveValue.x * moveSpeed;
        _moveVelocity.z = moveValue.y * moveSpeed;

        animator.SetFloat("MoveSpeed", new Vector3(_moveVelocity.x, 0, _moveVelocity.z).magnitude);

        Vector3 moveDirection = new Vector3(_moveVelocity.x, 0, _moveVelocity.z);
        if (moveDirection != Vector3.zero)
        {
            Quaternion targetRotation = Quaternion.LookRotation(moveDirection);
            _transform.rotation = Quaternion.Slerp(_transform.rotation, targetRotation, Time.deltaTime * rotationSpeed);
        }

        if (_characterController.isGrounded)
        {
            if (jumpReference.action.WasPressedThisFrame())
            {
                Debug.Log("ジャンプ!");
                _moveVelocity.y = jumpPower;
            }
        }
        else
        {
            _moveVelocity.y += Physics.gravity.y * Time.deltaTime;
        }

        _characterController.Move(_moveVelocity * Time.deltaTime);
    }
}

2.2 インスペクターでの設定手順

  1. InputActionsアセットの作成
    フォルトで定義されているのをそのまま使うか、InputActionsアセット内で、必要なアクション(例:Move、Jump)を定義する。
  1. InputActionReferenceへの割り当て
    次のいずれかの方法で登録します

(登録方法1)ドラッグ&ドロップで登録

作成したInputActionsアセットから、各アクションをドラッグ&ドロップして、PlayerControllerのmoveReferenceおよびjumpReferenceフィールドに設定する。

(登録方法2)ファイル選択で登録

⚪︎をクリックして、開いたファイル選択画面から登録します


3. アクションマップの切り替え方法(コードによる実装)

PlayerInputコンポーネントを使用して、コード内でアクションマップを切り替えることも可能です。以下はそのサンプルコードです。

using UnityEngine;
using UnityEngine.InputSystem;

public class ActionMapSwitcher : MonoBehaviour
{
    private PlayerInput playerInput;

    private void Awake()
    {
        // PlayerInputコンポーネントをキャッシュ
        playerInput = GetComponent<PlayerInput>();
    }

    private void Update()
    {
        // 数字キー1を押すと「Gameplay」マップに切り替え
        if (Keyboard.current.digit1Key.wasPressedThisFrame)
        {
            playerInput.SwitchCurrentActionMap("Gameplay");
            Debug.Log("Gameplayマップに切り替えました");
        }
        // 数字キー2を押すと「UI」マップに切り替え
        else if (Keyboard.current.digit2Key.wasPressedThisFrame)
        {
            playerInput.SwitchCurrentActionMap("UI");
            Debug.Log("UIマップに切り替えました");
        }
    }
}

3.1 説明

  • PlayerInputコンポーネントの利用:
    GetComponent<PlayerInput>()でPlayerInputコンポーネントを取得し、キャッシュします。
  • 入力に基づくマップ切り替え:
    Keyboard.currentを利用して、特定のキー入力(ここでは数字キー1および2)に応じ、SwitchCurrentActionMap()メソッドでアクションマップを切り替えます。
  • 利点:
    アクションマップの切り替えが簡潔なAPIで実装でき、シーン内で複数の入力コンテキストを管理する際に有効です。

3.2 アクションマップの切り替え用途

アクションマップの切り替えは、ゲーム内の異なる入力コンテキストを管理するために非常に有効です。具体的な用途は以下の通りです。

  • ゲームプレイとUIの切り替え:
    ゲームプレイ中はキャラクターの移動や攻撃などの操作が有効ですが、メニューやポーズ画面などのUI操作中は、カーソル移動や選択操作に切り替えることで、不要な操作ミスを防ぎます。
  • 状態やシーンに応じた入力管理:
    ゲーム中の通常の操作、カットシーン、対話シーンなど、シーンや状態ごとに必要な入力セットを定義し、適切なアクションマップを有効化することで、各シーンにおいて適切な入力が反映されるようにします。
  • 誤操作防止:
    プレイヤーが意図しない操作を行わないよう、必要な入力だけを有効にすることで、誤操作を防止する役割も果たします。
  • 柔軟な入力切り替え:
    ゲーム内イベントやモードの変更に合わせて、動的に入力設定を変更できるため、シーン遷移や特殊イベントの際にも柔軟に対応できます。

以上のように、アクションマップの切り替えを活用することで、ゲーム全体の操作性やユーザー体験を向上させることが可能です。


4. 使用目的に応じた選択

4.1 コード管理による柔軟な制御が必要な場合

  • メリット:
    • スクリプト内で直接InputActionを取得(FindAction)し、動作をプログラム的に制御できる。
    • アクションマップの切り替えもPlayerInputのAPIでシンプルに実装可能。
  • 用途:
    プログラマーが細かく動作を管理したい場合や、動的にアクションを切り替える必要がある場合。

4.2 インスペクターから直感的に設定したい場合

  • メリット:
    • InputActionReferenceを使用することで、インスペクター上でドラッグ&ドロップによる設定が可能。
    • デザイナーや非プログラマーとの協働が容易になり、設定変更も直感的に行える。
  • 用途:
    チーム開発や、設定の変更が頻繁に発生するプロジェクトに適している。

5. まとめ

本資料では、以下の点について解説しました。

  • InputActionの取得方法:
    コード内でFindAction()を使用する方法と、InputActionReferenceを利用してインスペクターから設定する方法の違いを理解する。
  • アクションマップの切り替え:
    PlayerInputのSwitchCurrentActionMap()を使用して、コード上でアクションマップを動的に切り替える実装例を示した。
  • 使用目的に応じた選択:
    完全にコードで管理し柔軟に制御したい場合は、FindActionを使用する方法が有効であり、エディタ上で直感的に設定や変更を行いたい場合はInputActionReferenceを使用する方法が適している。

プロジェクトの運用方法や開発フローに合わせ、最適な方法を選択してください。

Unity

Posted by hidepon