見出し画像

Input SystemでFPSゲーム風サンプル

Unity 2020.1 から新しい入力システム(Input System)が"プレビュー"から"検証済み"のステータスになりました。
今回は、この Input System を使ってFPSゲーム風の簡単なサンプルを作成していきます!

Input System の環境構築を行う

開発環境
Unity 2020.1.2f1

環境構築
Input System を使用できるように設定していきます。
まず、Input System のパッケージをインストールします。

画像1

次に、Project Settings -> Player -> Active Input Handling の入力方式を従来の Input Manager から Input System に変更します。

画像2

変更すると自動でエディターが再起動されます。
再起動が終わったら Input System を使う準備は完了です👍

シーンの作成

先に Input System に直接関係のない部分の実装をしていきます。
まず、地面となる Plane を配置します。

画像3

次に Main Camera の Transform を以下の画像のように変更します。
今回はFPS視点なので、 Main Camera がプレイヤーとなります。

画像4

次に、射撃のターゲットとなる Cube を3つ配置します。

画像5

そして、3つのターゲットに撃たれた時の処理を定義したコンポーネントをアタッチします。
今回は以下のような、撃たれるごとに色が変わるコンポーネントを作成してみました。

using UnityEngine;

namespace Upft
{
   /// <summary>
   /// ターゲットオブジェクトクラス
   /// </summary>
   public class UpftTargetObject : MonoBehaviour
   {
       /// <summary>
       /// Renderer
       /// </summary>
       private Renderer _renderer;

       /// <summary>
       /// Start
       /// </summary>
       private void Start()
       {
           _renderer = GetComponent<Renderer>();
           
           _renderer.material.color = Color.green;
       }

       /// <summary>
       /// 色を変更する
       /// </summary>
       public void ChangeColor()
       {
           if (_renderer.material.color == Color.red)
           {
               _renderer.material.color = Color.green;
           }
           else
           {
               _renderer.material.color = Color.red;
           }
       }
   }
}

Main Camera 配下にクロスヘア用の Sprite を配置します。

画像6

これで、入力処理以外の実装が完了しました。

プレイヤーの入力処理の実装

ここから Input System を使ったプレイヤーの入力処理を実装していきます。
まず、プレイヤーとなる Main Camera に Player Input コンポーネントをアタッチします。

画像7

Player Input コンポーネントの Create Actions... ボタンをクリックして、 Input Actions を作成します。

Input System では、ユーザーの入力 -> Input Actions -> Player Input という流れで入力が伝わっていきます。
ユーザーの入力と Player Input をつないでいる Input Actions を正しく設定することで、複数のデバイスによる入力を同じように扱うことができるようになります。

画像8

作成した Input Actions には、デフォルトでプレイヤーの移動やUI操作などの入力が設定されています。
今回は、 Player の中の Move 、 Look 、 Fire を使います。

画像9

Player Input コンポーネントの Actions に作成した Input Actions を設定します。

画像10

Player Input コンポーネントの Behavior を Invoke Unity Events に変更します。

画像11

Player Input コンポーネントの Events -> Player と開いていくと、Input Actions に定義されていた Move 、 Look 、 Fire のコールバックがあります。
これらのコールバックに設定する処理を実装していきます。

画像12

今回は、以下のようなクラスを作成しました。

using UnityEngine;
using UnityEngine.InputSystem;

namespace Upft
{
   /// <summary>
   /// プレイヤー制御クラス
   /// </summary>
   public class UpftPlayerController : MonoBehaviour
   {
       /// <summary>
       /// 移動速度
       /// </summary>
       [SerializeField] private float _moveSpeed = 10f;

       /// <summary>
       /// カメラ回転速度
       /// </summary>
       [SerializeField] private float _lookSpeed = 50f;

       /// <summary>
       /// Player Input
       /// </summary>
       private PlayerInput _playerInput = null;
       
       /// <summary>
       /// 現在の移動入力値
       /// </summary>
       private Vector2 _currentMoveInputValue = Vector2.zero;
       
       /// <summary>
       /// 現在のカメラ回転入力値
       /// </summary>
       private Vector2 _currentLookInputValue = Vector2.zero;

       /// <summary>
       /// 前回のカメラの向き
       /// </summary>
       private Vector3 _preRotation = Vector3.zero;

       /// <summary>
       /// Input Actions - Move
       /// </summary>
       private const string ACTION_MOVE = "Move";

       /// <summary>
       /// Input Actions - Look
       /// </summary>
       private const string ACTION_LOOK = "Look";

       /// <summary>
       /// Input Actions - Fire
       /// </summary>
       private const string ACTION_FIRE = "Fire";

       /// <summary>
       /// Device - ゲームパッド
       /// </summary>
       private const string DEVICE_GAMEPAD = "Gamepad";
       
       /// <summary>
       /// Start
       /// </summary>
       private void Start()
       {
           Cursor.lockState = CursorLockMode.Locked;

           if (TryGetComponent(out _playerInput))
           {
               _playerInput.actions[ACTION_MOVE].started += OnMove;
               _playerInput.actions[ACTION_MOVE].performed += OnMove;
               _playerInput.actions[ACTION_MOVE].canceled += OnMove;
               
               _playerInput.actions[ACTION_LOOK].started += OnLook;
               _playerInput.actions[ACTION_LOOK].performed += OnLook;
               _playerInput.actions[ACTION_LOOK].canceled += OnLook;
               
               _playerInput.actions[ACTION_FIRE].started += _ => OnFire();
           }
       }

       /// <summary>
       /// Update
       /// </summary>
       private void Update()
       {
           move();
           look();
       }

       private void move()
       {
           var moveForward = Quaternion.Euler(0, transform.eulerAngles.y, 0) * new Vector3(_currentMoveInputValue.x, 0, _currentMoveInputValue.y);
           transform.position += moveForward * _moveSpeed * Time.deltaTime;
       }

       private void look()
       {
           _preRotation.y += _currentLookInputValue.x * _lookSpeed * Time.deltaTime;
           _preRotation.x -= _currentLookInputValue.y * _lookSpeed * Time.deltaTime;
           _preRotation.x = Mathf.Clamp(_preRotation.x, -89, 89);
           transform.localEulerAngles = _preRotation;
       }

       /// <summary>
       /// 移動処理
       /// </summary>
       public void OnMove(InputAction.CallbackContext context)
       {
           _currentMoveInputValue = context.ReadValue<Vector2>();
       }

       /// <summary>
       /// カメラ回転処理
       /// </summary>
       public void OnLook(InputAction.CallbackContext context)
       {
           _currentLookInputValue = context.ReadValue<Vector2>();
       }

       /// <summary>
       /// 射撃処理
       /// </summary>
       public void OnFire()
       {
           var ray = new Ray(transform.position, transform.forward);
           if (Physics.Raycast(ray, out var hit, 100))
           {
               if (hit.collider.TryGetComponent(out UpftTargetObject target))
               {
                   target.ChangeColor();
               }
           }
       }
   }
}

キーを押した時と離した時のみコールバックが呼ばれるので、長押しでの移動を実現するために、現在の入力値をメンバー変数で保持して、 Update で毎フレーム参照するようにしています。

/// <summary>
/// 現在の移動入力値
/// </summary>
private Vector2 _currentMoveInputValue = Vector2.zero;

動かしてみる

ゲームパッドにも対応させる

マウスとキーボードによる操作の実装が完了しました。
次は、ゲームパッドによる操作にも対応させていきます……という流れで記事を書いていく予定でしたが、ここまでの実装ですでにゲームパッドでの操作にも対応できてしまいました。

なので、ここからはちょっとした調整をしていこうと思います。
実際にゲームパッドをPCにつないで操作してみてください。マウスに比べてカメラの回転速度が遅いと感じると思います。
折角なのでマウスでもゲームパッドでも同じくらいの速度で動かせるようにしてみます。

Input Actions の Look -> Right Stick [Gamepad] -> Properties -> Processors を設定します。
これを設定することで、マウスの入力値はそのままに、右スティックで入力した値のみ加工することができます。
今回は Scale Vector 2 を追加することで、入力値に倍率をかけます。

画像13

画像14

おわりに

今回は、 Input System を使ってFPSゲーム風のサンプルを作成してみました。
とても便利な機能だったので、これからも活用していきたいところです!

それではまた。

この記事が気に入ったらサポートをしてみませんか?