はじめに
Unity 2020.1 から新しい入力システム( Input System
)がプレビューから検証済みに移行しました。
今回は、この Input System
を使ってFPSゲーム風の簡単なサンプルを作成していきます。
開発環境
- Unity 2020.1.2f1
環境構築
Input System
を使用できるように設定していきます。
まず、Input System
のパッケージをインストールします。

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

変更すると自動でエディターが再起動されます。
再起動が終わったら Input System
を使う準備は完了です。
シーンの作成
先に Input System
に直接関係のない部分の実装をしていきます。
まず、地面となる Plane
を配置します。

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

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

そして、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
を配置します。

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

Player Input
コンポーネントの Create Actions...
ボタンをクリックして、 Input Actions
を作成します。
Input System
では、ユーザーの入力 -> Input Actions
-> Player Input
という流れで入力が伝わっていきます。
ユーザーの入力と Player Input
をつないでいる Input Actions
を正しく設定することで、複数のデバイスによる入力を同じように扱うことができるようになります。

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

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

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

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

今回は、以下のようなクラスを作成しました。
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
を追加することで、入力値に倍率をかけます。


おわりに
今回は、 Input System
を使ってFPSゲーム風のサンプルを作成してみました。
非常に便利な機能なので、ぜひ皆さんも使ってみてください。