Unity InputSystemを用いたPlayerController実装ガイド(ポーリング方式からイベント駆動型へ)

2025年4月2日

この資料では、Unityの新しいInputSystemを使用してプレイヤーキャラクターの移動やジャンプ処理を実装する方法について解説します。従来のポーリング方式での入力取得から、イベント駆動型の入力処理への移行方法を具体例とともに紹介します。


1. 背景と目的

背景

UnityのInputSystemは、従来のInput.GetAxis()などを使用した方法に比べ、イベント駆動型のコールバックを利用することで、より直感的で効率的な入力管理が可能となっています。

目的

  • キャラクターの移動およびジャンプ処理を実装する
  • InputSystemのイベント駆動型(OnMove、OnJumpコールバック)を活用して入力を取得する
  • キャラクターが地上にいる場合のみジャンプ処理を実行する

2. コード例

2.1 従来のポーリング方式による実装例

以下のコードは、PlayerInputコンポーネントからアクションを取得し、Update内で常に入力値を読み取る従来の実装例です。

using UnityEngine;
using UnityEngine.InputSystem;

[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(PlayerInput))]
public class PlayerController : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 3; // 移動速度
    [SerializeField] private float jumpPower = 3; // ジャンプ力
    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;

        // 移動方向に向く
        _transform.LookAt(_transform.position + new Vector3(_moveVelocity.x, 0, _moveVelocity.z));

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

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

このスクリプトは、Unityの新しいInput SystemとCharacterControllerを利用して、プレイヤーの移動とジャンプを制御する基本的なキャラクターコントローラーの実装例です。以下、各部分の詳細な解説を行います。


1. クラスの設定と依存コンポーネント

  • RequireComponent属性
    PlayerControllerクラスの上にある [RequireComponent(typeof(CharacterController))][RequireComponent(typeof(PlayerInput))] は、このスクリプトがアタッチされているゲームオブジェクトに必ず CharacterControllerPlayerInput コンポーネントが存在するように強制します。これにより、後続のコードでコンポーネントを安全に使用できます。

2. フィールドの宣言

  • 移動とジャンプのパラメータ
    • moveSpeed: プレイヤーの移動速度を指定しています。
    • jumpPower: ジャンプ時の上方向への初速度(力)を表します。
  • キャッシュ変数
    • _characterController: 毎フレームのアクセスを効率化するために、CharacterControllerコンポーネントの参照をキャッシュしています。
    • _transform: transform のキャッシュ。頻繁に使われるため、キャッシュすることでパフォーマンスの向上を狙っています。
  • 移動とジャンプの処理に用いる変数
    • _moveVelocity: キャラクターの現在の速度(移動ベクトル)を保持します。X軸とZ軸は水平移動、Y軸はジャンプや重力による上下移動に使います。
    • _move_jump: 新しいInput Systemで設定されたアクションを保持するための変数で、プレイヤーの入力(移動とジャンプ)を受け取るために使用されます。

3. Startメソッドでの初期化

  • コンポーネントの取得とキャッシュ
    Start()メソッド内で、GetComponent<CharacterController>()transform を用いて、それぞれの参照を取得し、キャッシュしています。
  • Input Systemの初期化
    • PlayerInput コンポーネントを取得し、currentActionMap(通常は「Default Map」)を有効化しています。
    • その後、FindAction("Move")FindAction("Jump") により、入力アクションを取得し、それぞれ _move_jump に格納しています。これにより、今後のアップデート内で入力が簡単に参照できるようになります。

4. Updateメソッドでの処理

  • グラウンドチェック
    • Debug.Log(_characterController.isGrounded ? "地上にいます" : "空中です");
      で、キャラクターが地上にいるか空中にいるかを毎フレームログに出力しています。isGrounded プロパティは、CharacterControllerが地面に接しているかどうかを判定するために使われます。
  • 移動処理
    • _move.ReadValue<Vector2>() で、左右や前後の入力値(通常、キーボードのWASDキーやジョイスティックなど)を取得し、X軸(左右)とZ軸(前後)の速度を計算しています。
    • 入力値に moveSpeed を掛け合わせることで、速度をスケーリングしています。
  • 向きの調整
    • _transform.LookAt(_transform.position + new Vector3(_moveVelocity.x, 0, _moveVelocity.z));
      により、キャラクターが移動方向を向くように回転させています。Y軸成分は無視して水平面上での回転にしています。
  • ジャンプと重力の処理
    • ジャンプ:
      地上にいる状態 (isGrounded が true) の場合、_jump.WasPressedThisFrame() が true なら、ジャンプ処理が実行され、Y軸方向の速度に jumpPower を設定します。これにより、上方向に加速します。
    • 重力:
      地上にいない場合、毎フレーム Physics.gravity.y * Time.deltaTime を加算することで、重力が適用され、自然な落下が実現されます。
  • 移動の適用
    • 最後に、_characterController.Move(_moveVelocity * Time.deltaTime); で、計算された移動速度に基づいてキャラクターを実際に移動させます。Time.deltaTime を乗じることで、フレームレートに依存しない移動が実現されています。

5. 全体の流れ

  1. 初期化:
    ゲーム開始時に必要なコンポーネント(CharacterController、PlayerInput)や入力アクションを取得・初期化します。
  2. 毎フレームの入力取得:
    Update() で、ユーザーの入力(移動とジャンプ)を読み取り、移動方向やジャンプの開始を決定します。
  3. 向き調整と物理挙動:
    キャラクターの向きを移動方向に合わせ、ジャンプや重力による上下移動を加味して、実際の移動を CharacterController を通じて実行します。

注意点

  • 入力のスムーズさ:
    このコードは慣性や滑らかな加速・減速を実装していないため、入力に対して即座に反応する「カクカク」した動きになります。より自然な動きを実現する場合は、加速度や減衰処理を加えるとよいでしょう。
  • 重力処理:
    重力の影響は Physics.gravity を用いているため、Unityのプロジェクト設定に依存します。必要に応じて値の調整が可能です。
  • 衝突処理:
    CharacterController は独自の衝突判定システムを持っているため、物理挙動を自作する場合とは異なる注意点(例えば、坂道の滑りなど)が存在します。

このスクリプトは、基本的なプレイヤーキャラクターの動作(移動・ジャンプ)を実現するためのシンプルかつ効果的な実装例です。これを基に、より複雑な動作やアニメーションとの連動、その他の機能(例えばダッシュ、回避動作など)を追加していくことが可能です。

進行方向への向きをなめらかに変更するには、Quaternion.SlerpQuaternion.RotateTowards を使って、現在の回転から目標の回転に補間(補正)する方法が一般的です。たとえば、以下のように実装できます。

[SerializeField] private float rotationSpeed = 10f; // 回転速度

private void Update()
{
    // 入力から移動速度を計算(既存の処理)
    var moveValue = _move.ReadValue<Vector2>();
    _moveVelocity.x = moveValue.x * moveSpeed;
    _moveVelocity.z = moveValue.y * moveSpeed;

    // 移動方向が存在する場合にのみ回転処理を実行
    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);
}

解説

  • Quaternion.LookRotation(moveDirection):
    進行方向(水平面上の移動方向)から目標の回転を求めます。
  • Quaternion.Slerp:
    現在の回転から目標の回転へ、Time.deltaTime * rotationSpeed の割合でなめらかに補間します。rotationSpeed の値を調整することで、回転の速さを変えられます。
  • moveDirectionのチェック:
    入力がゼロでない場合にのみ回転処理を実行することで、入力がないときに不要な回転計算を避けます。

この方法で、キャラクターは移動方向に向かって滑らかに回転し、より自然な挙動を実現できます。


2.2 イベント駆動型による実装例

次に、InputSystemのイベント駆動型コールバック(OnMoveOnJump)を利用した実装例です。こちらは、PlayerInputコンポーネントが自動的に各イベントを呼び出す設定となっている前提です。

using UnityEngine;
using UnityEngine.InputSystem;

[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(PlayerInput))]
public class PlayerController : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 3f;   // 移動速度
    [SerializeField] private float jumpPower = 3f;     // ジャンプ力

    private CharacterController _characterController;
    private Vector3 _moveVelocity;
    private Vector2 _moveInput;
    private bool _jumpPressed;

    private void Awake()
    {
        _characterController = GetComponent<CharacterController>();
    }

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

        // Moveイベントで更新された入力値を利用した移動処理
        _moveVelocity.x = _moveInput.x * moveSpeed;
        _moveVelocity.z = _moveInput.y * moveSpeed;

        // 入力がある場合のみキャラクターの向きを更新
        if (_moveInput.sqrMagnitude > 0.01f)
        {
            transform.LookAt(transform.position + new Vector3(_moveVelocity.x, 0, _moveVelocity.z));
        }

        if (_characterController.isGrounded)
        {
            // ジャンプイベントで設定されたフラグが立っていたらジャンプ実行
            if (_jumpPressed)
            {
                Debug.Log("ジャンプ!");
                _moveVelocity.y = jumpPower;
                _jumpPressed = false;  // ジャンプフラグをリセット
            }
        }
        else
        {
            // 空中にいる場合は重力を適用
            _moveVelocity.y += Physics.gravity.y * Time.deltaTime;
        }

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

    // InputSystemからの移動入力を受け取るコールバック
    public void OnMove(InputValue value)
    {
        _moveInput = value.Get<Vector2>();
    }

    // InputSystemからのジャンプ入力を受け取るコールバック
    public void OnJump(InputValue value)
    {
        // 地上にいる場合のみジャンプフラグを立てる
        if (value.isPressed && _characterController.isGrounded)
        {
            _jumpPressed = true;
        }
    }
}

2.3 参考コード例

以下のコードは、InputSystemのイベントを利用した移動処理の簡単な例です。イベント駆動型の考え方をシンプルに理解するための参考としてご利用ください。

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerControllerSample : MonoBehaviour
{
    private Vector2 moveInput;
    public float moveSpeed = 5f;

    void Update()
    {
        Vector3 move = new Vector3(moveInput.x, 0, moveInput.y) * moveSpeed * Time.deltaTime;
        transform.Translate(move, Space.World);
    }

    void OnMove(InputValue value)
    {
        moveInput = value.Get<Vector2>();
    }
}

3. 実装のポイントと注意点

  • キャッシュ処理:
    頻繁にアクセスするCharacterControllerTransformの参照は、StartAwakeでキャッシュすることでパフォーマンスの向上が期待できます。
  • InputSystemの活用:
    PlayerInputコンポーネントを利用すると、アクションマップの有効化や各種イベント(OnMove, OnJumpなど)が自動的に呼び出され、コードがシンプルになります。
  • イベント駆動型:
    イベント駆動型の入力処理は、ポーリング方式に比べて直感的に入力の変化を捉えやすく、また不要な処理が削減されるため、コードの可読性も向上します。
  • ジャンプ処理:
    ジャンプ処理は、キャラクターが地上にいることを確認した上で実行する必要があります。イベント駆動型の実装では、入力時にフラグをセットし、Update内でそのフラグをチェックしてジャンプを実行する方法が効果的です。
  • 重力の適用:
    キャラクターが空中にいる場合は、重力の影響を継続して適用することで、自然な落下運動を実現します。

4. まとめ

UnityのInputSystemを活用したイベント駆動型の入力処理は、従来のポーリング方式に比べて、コードのシンプル化や直感的な入力管理が可能となります。今回のサンプルコードを参考に、プロジェクトのニーズに合わせたカスタマイズや拡張を行い、より洗練されたプレイヤーコントローラーを実装してください。


Unity

Posted by hidepon