Skip to content

第01回 Input Systemとキャラクター操作

次回: 第02回 カメラ操作とコンポーネント分割

この授業の目的

  • ゲームの機能を段階的に実装しながら C#の用法と設計の基本を実践的に学ぶ
  • 個人でゲームを完成させる過程を通じて ゲーム制作の経験と全体観を養う
  • ソフトウェア部品の責務を理解し 保守と拡張を考慮した設計を身につける

制作するゲーム

Unity Scene上に床とPlayerを配置した完成イメージ

  • 床は3DのPlaneまたはMesh
  • カメラは斜め上からフィールドを見下ろす俯瞰カメラ
  • プレイヤー、敵、アイテム等は3Dオブジェクト
  • 移動は水平面(X-Z平面)に限定する
  • ジャンプ、複雑な段差は授業内では扱わないが 実装は自由とする

全10回の流れ

  1. Input Systemとキャラクター操作
  2. カメラ操作とコンポーネント分割
  3. 攻撃入力と攻撃モーション
  4. 衝突判定とダメージ
  5. Enemyの探索・追跡・攻撃
  6. Enemyの状態制御とゲームフィール
  7. アイテムとオブジェクトプール
  8. GameManagerとゲーム状態
  9. UIとシーン遷移
  10. ビルドと公開

今回の授業内容

  • Input Systemで移動入力を受け取る
  • 入力値から移動方向を作る
  • X-Z平面でキャラクターを移動させる
  • キャラクターを移動方向へ振り向かせる
  • 移動速度と回転速度をInspectorで調整できるようにする

今回の配布コード

この回で使う完成コードは、以下から個別に確認できます。

ファイル内容
Assets/Scripts/PlayerController.cs入力を読み取り、移動と振り向きを行う

入力を抽象化する

Unityの入力マネージャー(Input Manager)はキーボードやマウス、ゲームパッドなどの入力を扱うための仕組みです。 例えば以下のように書けば、キーボードのWASDキーでプレイヤーを移動させることができます。

cs
// キーボードでの入力と移動の処理
if (Input.GetKey(KeyCode.W))
{
    // 前進
}
else
if (Input.GetKey(KeyCode.S))
{
    // 後退
}
else
if (Input.GetKey(KeyCode.A))
{
    // 左移動
}
else
if (Input.GetKey(KeyCode.D))
{
    // 右移動
}

ですがここでゲームパッドの左スティックでも移動できるようにするには Input.GetAxis を併用しなければなりません。

cs
// ゲームパッドでの入力と移動の処理
if (Input.GetAxis("Vertical") > 0)
    ...

Input Managerは簡単に使えますが、こうした個別対応のコードを都度書き加えながら複数の入力方法に対応するのは大変です。 キーボードとコントローラーの操作感を揃える手間もかかりますし、キーコンフィグの実装も自前で行う必要があります。

実はInput Managerは古い仕組みで、Unity 2019.1以降は新しいInput Systemの利用が推奨されています。 今回は新しい入力システムであるInput Systemの利用方法と、入力の抽象化の仕組みを学びます。

Input Systemで入力を抽象化する

Input Systemは従来のInput Managerに代わる入力管理のシステムで、その最も大きな特徴は入力をアクションとして抽象化できることです。 ゲーム側は入力デバイスやキーコードを意識せず、移動方向やジャンプ、メニューを開くといった必要なアクションの単位で入力を扱えるようになります。

アクション単位で入力をまとめることで、キーボード、ゲームパッド、タッチなどの複数デバイスを統一的に扱えるようになるのがInput Systemの大きな利点です。

Input Actionsアセットを設定する

Input SystemではInput Actionsアセットで入力の種類や割り当てを定義し、スクリプトからはActionを通じて入力値を読み取ります。

Input Actionsの設定手順

今回は Move Actionで移動入力を扱います。Move Actionはキー入力ではなく移動量を表す Vector2 として読み取ることができます。

Project SettingsでInput System Packageを有効にした画面

  1. Input Actions アセットを作成します
  2. Player Action Mapに Move Actionを追加します
  3. Action TypeValueControl TypeVector2 にします
  4. WASD、方向キー、左スティックを割り当てます

Input ActionsからC#クラスを生成する

Input ActionsでGenerate C# Classを有効にしたInspector

Generate C# Class を有効にすると、Actionへ型安全にアクセスするクラスが生成されます。 設定変更の際にコンパイルが発生しますが、TypoやAction名の変更に強く、コード補完も効き便利です。

この教材ではクラスを生成して利用します。 クラス名は InputSystem_Actions で統一しています。

生成されたクラスから入力値を読む

AwakeInputActions を作成し、OnEnable OnDisable でAction Mapを有効化/無効化します。

有効化されたAction MapのActionは ReadValue<T>() で値を読み取ることができます。 ここでは ReadValue<Vector2>() で移動入力を読み取っています。

cs
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    private InputSystem_Actions _inputActions;

    private void Awake()
    {
        _inputActions = new InputSystem_Actions();
    }

    private void OnEnable()
    {
        _inputActions.Player.Enable();
    }

    private void OnDisable()
    {
        _inputActions.Player.Disable();
    }

    private void Update()
    {
        var moveInput =
            _inputActions.Player.Move.ReadValue<Vector2>();

        // 省略

2D入力を3Dの移動方向に変換する

cs
var direction = new Vector3(
    moveInput.x,
    0f,
    moveInput.y
);

// 斜め移動が速くなりすぎないようにする
direction = Vector3.ClampMagnitude(direction, 1f);

Input Systemの移動入力は二次元の Vector2 ですが 3D 空間の移動方向は三次元の Vector3 で扱います。 ここでは入力の x をワールドの x に、入力の y をワールドの z に適用します。

入力値は-1.0~1.0の範囲で正規化されています。 そのまま移動に適用すると斜め入力時の移動の速度が √2倍(約1.414倍)になってしまいますので、長さを最大1に制限します。

設定値をInspectorで調整可能に

cs
public class PlayerController : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 5f;
    [SerializeField] private float rotationSpeed = 720f;

プレイ感に関わる数値は何度も調整することになります。こうした値はコードを書き換えずに変更できるようにするのがゲーム制作の基本です。 変数の定義時に SerializeField を付けると Inspector から値を設定できるようになります。

INFO

アクセス修飾子を public にしても Inspector から設定できるようになりますが、それでは変数が外部公開されてしまいます。 変数のアクセス修飾子は、特に理由がなければ private を基本とし、必要に応じて SerializeField で Inspector から設定できるようにすることをおすすめします。

プレイヤーをワールド座標基準で移動させる

cs
public class PlayerController : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 5f;

    private InputSystem_Actions _inputActions;

    // Awake / OnEnable / OnDisable は省略

    private void Update()
    {
        var moveInput =
            _inputActions.Player.Move.ReadValue<Vector2>();

        var direction = new Vector3(moveInput.x, 0f, moveInput.y);
        direction = Vector3.ClampMagnitude(direction, 1f);

        transform.position +=
            direction * (moveSpeed * Time.deltaTime);
    }
}

Time.deltaTime はフレーム内の経過時間です。移動速度に Time.deltaTime を掛ければ、1フレームあたりの移動量を求めることができます。 まずはプレイヤーを X-Z 平面で動かしてみましょう。

入力があるときだけ移動方向に振り向く

cs
if (direction != Vector3.zero)
{
    var targetRotation =
        Quaternion.LookRotation(direction);

    transform.rotation = Quaternion.RotateTowards(
        transform.rotation,
        targetRotation,
        rotationSpeed * Time.deltaTime);
}

Quaternion.LookRotation で、最終的な回転を表す Quaternion を作った後、Quaternion.RotateTowards で現在の回転から目標の回転へ、指定の速度で回転させます。rotationSpeed は1秒あたりに回転できる角度の速さ(度/秒)です。

これでプレイヤーが入力方向に指定の速度で振り向くようになります。

完成した PlayerController

cs
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 5f;
    [SerializeField] private float rotationSpeed = 720f;

    private InputSystem_Actions _inputActions;

    private void Awake()
    {
        _inputActions = new InputSystem_Actions();
    }

    private void OnEnable()
    {
        _inputActions.Player.Enable();
    }

    private void OnDisable()
    {
        _inputActions.Player.Disable();
    }
    private void Update()
    {
        var moveInput =
            _inputActions.Player.Move.ReadValue<Vector2>();

        var direction =
            new Vector3(moveInput.x, 0f, moveInput.y);
        direction = Vector3.ClampMagnitude(direction, 1f);

        transform.position +=
            direction * (moveSpeed * Time.deltaTime);

        if (direction == Vector3.zero)
            return;

        var targetRotation =
            Quaternion.LookRotation(direction);

        transform.rotation = Quaternion.RotateTowards(
            transform.rotation,
            targetRotation,
            rotationSpeed * Time.deltaTime);
    }
}

実習:プレイヤーを操作できるようにする

  1. Planeを配置する
  2. CapsuleをPlayerとして配置する
  3. Input Actionsアセットを作成する
  4. Move Actionを設定する
  5. C#コード生成を有効にして InputSystem_Actions を生成する
  6. PlayerController をPlayerに追加する
  7. WASDまたは左スティックで 移動を確認する
  8. 移動方向へ振り向くことを確認する

動作しない場合のチェックリスト

  • Move Actionの型が Vector2 になっているか
  • InputSystem_Actions.cs が生成されているか
  • OnEnablePlayer Action Mapを有効化しているか
  • moveSpeed が0になっていないか
  • rotationSpeed が0になっていないか
  • direction の値がゼロのままになっていないか

完成条件

  • WASDまたはコントローラーの左スティックで移動できる
  • 斜め移動が他の入力に比べて速くなっていない
  • プレイヤーが移動方向へ振り向く
  • Move ActionをInput Systemで設定している
  • InputSystem_Actions のC#コード生成を有効にしている
  • moveSpeedrotationSpeed をInspectorで調整できる

まとめ

  • 入力は Input Action から受け取る
  • 移動方向は入力値から作る
  • 移動量は速度と時間から作る
  • 回転は移動方向から作る