【意外とカンタン】世界一わかりやすいInput Systemの実装解説!コントローラーでゲームを動かそう!

ころもちゃん

ゲームをコントローラーを使って動かしたい!どうやったらいいのかな?

本記事の内容

・Unityでコントローラーから入力を受け付ける方法
・Input Systemの基本的な使い方
・Input Systemの応用方法(ゲームシーンでの操作ととUI操作を切り替える)

目次

ゲームを操作する方法のあれこれ

ゲームの操作方法といえば...?

ゲームの基本としては、何かしらの操作をユーザが行うと、ゲームが反応する、ですよね。
この基本の「ユーザが操作を行うと処理を行う」には色んな方法があります。主な操作方法を挙げると以下のとおり。

操作方法概要
キーボード入力WASDで移動、スペースキーでジャンプ、みたいな操作
マウス操作マウス操作でカメラの向きを変更する、クリックで弾を発射するみたいな操作
タップ・フリック操作 画面内をタップ、スワイプなどの操作
コントローラー入力コントローラーの左スティックで移動、右スティックでカメラ変更、みたいな操作

これらの操作を統合的に管理することが出来るのがInputSystemです。InputSystemを使うことで、複数の操作方法に簡単に対応出来るようになるので、多様なプラットフォームへの展開やキーコンフィグの実装などが非常にラクになります。

そもそもInput Systemってなに?

Input Systemは、Unityが提供している、入力周りの操作をサポートしてくれるパッケージのことです。
2020年以前は、Input Managerというパッケージが提供されており、WEB上にはInput Systemよりも、Input Managerについての解説記事が多くあると感じます。
名前も似ているからどっちが最新かわからなくなったら、アルファベットが後(MよりS)の方が新しい方と覚えておくのがよいでしょう。

Input Managerを使った実装は古い実装となっており、今後Unityからサポートされづらくなりますし、なによりもInput Systemの方が使いやすい(主観)ので、これからゲームを作ろうという方には、オススメしません。

Input Systemを使うことで、以下のようなことが比較的簡単に実装することが可能になります。
【マルチプラットフォーム対応】キーボード入力と、コントローラーの入力どちらでも操作可能にする
【複雑な入力検知】単押し、長押し、同時押しなどの多様な操作コマンドの実装
【操作の切替】キャラクターの操作と、メニューを開いた時のUI操作に切り替える

ただ、Input Systemは困ったことに、少々のとっつきづらさと、日本語の解説が少ない、という欠点があります。
一度理解してしまえば、そこまで難しくないのですが、最初の理解までがちょっと難しかったりします。

そのため、できるだけ噛み砕いて、分かりやすくInput Systemの使い方を解説していきます。

Input Systemの基本的な使い方

インストール方法

このInput Systemですが、デフォルトのプロジェクトではインストールされていません。
デフォルトはInput Managerの方が有効になっているんですね。そのため、まずはInput Systemをインストールする必要があります。

インストールはいたって簡単。パッケージマネージャーから、Unityレジストリを選択し、

Input Systemと検索窓に入れてインストールをしましょう。

インストールボタンを押すと、以下のようなポップアップが表示されるので、Yesを押して進みましょう。
これは、前述したとおりデフォルトだと、旧式のInput Manager前提になっているので、新しいInput Systemに色々と書き換えますよ、という警告です。
Yesを押してエディタを再起動させましょう。

Yesを押して再起動

余談ですが、Input Systemをインストールすると、ビルド設定の有効な入力が、「InputManager(Old)」から、「Both」に自動で切り替わります。

Input Systemインストール後のビルド設定

これでインストール作業は完了です!

参考:チュートリアルプロジェクト

本題に入る前に、参考として。

Input Systemを使って、キャラクター操作を可能とするサンプルプロジェクトがUnityから無料で配布されています。
以下のUnity公式アセットをインストールすると、Input System実装済みプロジェクトを自由に使えることが出来ます。

あわせて読みたい
Starter Assets - ThirdPerson | Updates in new CharacterController package | Essentials | Unity Asset... Get the Starter Assets - ThirdPerson | Updates in new CharacterController package package from Unity Technologies and speed up your game development process. Fi...

アセットストアからのインストール方法がわからない人はこちらの記事がオススメです。

シンプルな操作の人間を操作する3Dゲームだったら、このアセットの内容を流用するだけで十分だったりします。
キャラクターの歩き、ダッシュ、ジャンプ、カメラ操作などが、コントローラー操作とスマホでの操作、マウス・キーボード操作のすべてに対応しています。もちろんすべてのソースコードを見ることが出来ます。

解説読むより実際のモノを見た方が早いんじゃ、という人はインストールしてみて、色々といじってみると勉強になるでしょう。

今回紹介実装の全体イメージと事前準備

では早速、本題のコントローラーによる操作を受け付けるセットアップをしていきます。
今回は、以下の操作が出来ることをゴールにしていきます。

なお、ボタン配置は、XBoxのコントローラーをベースにしています。

XBoxコントローラーのボタン配置
括りアクション操作内容
プレイ時の操作・【コントローラー】Aボタン
・【キーボード】スペース
ボールを上下左右に移動・【コントローラー】左スティック
・【キーボード】WASD
ボールを高速に上下左右に移動・【コントローラー】Bボタン+左スティック
・【キーボード】Shift+WASD
ボールを消滅・【コントローラー】L+Rボタン
ボールを吹っ飛ばす・【コントローラー】Xボタン長押し
UI操作UIの選択カーソルを移動(省略)
操作一覧


なお、Amazonや家電量販店などで、2000円程度でPC用のコントローラーは販売されているので、コントローラー操作を試す場合は、まずコントローラーを準備しておきましょう。

ちなみに、コントローラーの入力規格には、「Direct Input」と「X Input(もともとXBox用に開発された新しい方)」という2つの規格があり、ゲーム開発をするのであれば、どちらの規格でも動くかを確認出来るように、規格を切り替えられるコントローラーがオススメです。

私は以下のコントローラーを使っています。
https://amzn.asia/d/be8l9x3

コントローラーの準備が出来たら、実際のInput Systemの実装していきましょう!

Input Actionsの実装:①Input Actionsの登録

Input Systemを使った実装を行うには、Input Actionsというものをプロジェクト内に作る必要があります。
とりあえず作ってみましょう。
プロジェクトウィンドウの適当なフォルダに移動し、右クリックを押して、「作成」 > 「Input Actions」と進みます。

Input Actionsの作成

アセット名は私は「MyControls」としました。これが後続のスクリプトで使用するクラス名になります。

この作成したInput Actionsを次にダブルクリック(インスペクタのEdit assetからでも可能)すると、以下のような画面が表示されます。

このウィンドウをよくみると、Action MapsとActionsというワードがあります。
これは先程の表で見ると以下の画像のような対応関係です。

この表に則り、Action Mapsと、Actionsを順番に作っていきます。
+ボタンを押すことでActionMapsとActionsを登録出来ます。まずは一番シンプルなJumpというアクションを登録しました。

+ボタンからActionMapsとActionsを追加

JumpのAction TypeはButonを設定しています。

Action Typeを設定


次にBindingの登録です。
これは、登録した「Jump」というアクションをどの操作で制御するか、を決める項目です。
今回なら、キーボードとコントローラーでの制御になるので、それぞれのBindingを登録していきます。

まずはキーボード入力のBindingの設定です。
<No Bindling>を選択して、Binding内のPath > Listenボタンを選択(以下のGIFだとListenボタンは隠れた位置にいます、なぜか私の環境はListernボタンが隠れます...)。
その後、設定したいキーボードのキー(今回はスペースキー)を入力すると、候補が表示されます。
もちろんお好みのボタンでもOKです、このボタンの設定が違ってもコードの内容は変わりません。

キー入力を紐づける手順

これで、キーボードの設定は完了です。

続いて同じ手順で、コントローラーのAボタンによるジャンプも登録します。
Bindingを追加し、コントローラーがPCと接続されていることを確認して同様の手順を行いましょう。
設定すると以下のようになるはずです。

Spaceキーとボタン入力の両方がActionのBindingに登録された

これで「Jump」というアクションと操作キーの紐づけが完了しました。
最後に上部のSave Assetで保存しましょう。

Save Assetを忘れずに!

ここまでで、JumpというActionがSpaceキーとコントローラーのAボタン(下にあるボタン)とひも付きました。

Input Actionsの実装:②Input Actionsと実際の処理との紐づけを行う

InputActionsの登録が出来たら、これを実際にジャンプを行う処理のスクリプトから呼び出せるようにしてあげる必要があります。

これを行うには、Input Actionsのアセットを選択し、インスペクタからGenerate C# Classというチェックボックを押す必要があります。
地味なので忘れがちですが、忘れずに実施しましょう。

「適用する(Apply)」ボタンを押すと、プロジェクトウィンドウに、スクリプトが追加されます。
これでOKです。

これで、自分が作ったスクリプトとInput Systemのキー入力受付を紐づける準備が整いました。

ジャンプさせるオブジェクトを適当に用意して、

Planeとボールだけのシーン

ジャンプさせたいオブジェクトに以下のスクリプトをアタッチします。
今回はボールのオブジェクトにスクリプトをアタッチしました。

ボールのインスペクタの設定

スクリプトは以下の通り。

using UnityEngine;
using UnityEngine.InputSystem;

public class BallMover : MonoBehaviour
{
    [SerializeField] private float jumpForce = 5.0f;
    private Rigidbody rb;
    private MyControls controls;

    private void Awake()
    {
        // Rigidbodyコンポーネントを取得
        rb = GetComponent<Rigidbody>();

        // MyControlsのインスタンスを作成
        controls = new MyControls();

        // ActionMaps[Player]の中の[Jump]というActionに紐づくイベントリスナーを登録
        controls.Player.Jump.performed += OnJumpPerformed;
    }

    private void OnEnable()
    {
        // Inputアクションを有効化
        controls.Enable();
    }

    private void OnDisable()
    {
        // Inputアクションを無効化
        controls.Disable();
    }

    private void OnJumpPerformed(InputAction.CallbackContext context)
    {
        // Rigidbodyに上方向の力を加える
        if (rb != null)
        {
            rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
        }
    }
}

スクリプト内容を理解したい場合は、コピペしてChat-GPTに解説してもらった方がわかりやすいと思うので、ここでは簡単に。

InputSystemと紐づいている先ほど生成したスクリプト、「MyControls」を使えるようにインスタンス化(実体化)し、対応するアクションが実行されたことを受け取って、OnJumpPerformedというメソッドが動きます。

一番のポイントはココです。

controls.Player.Jump.performed += OnJumpPerformed;

performed、すなわちJumpボタンが押されたときに、OnJumpPerformedというメソッドが動かすよ、という動きになります。

performed以外にも、以下のパターンがあります。
performed:処理が完全に成功したタイミング。例えば長押しの設定なら、長押し判定が成功したタイミングで実行される。
started:処理が開始したタイミング。例えば、長押しボタンを押し始めたタイミングで実行される。
canceled:処理がキャンセルされた場合。ボタンを離した瞬間や、移動入力が無くなった時などに使う。

アタッチ出来たら実行し、スペースキーか、登録したコントローラーのボタンを押してみてください。

Jump & Jump!

スペースキーを押した時と、コントローラーのAボタンの両方で反応していれば成功です!
思っていたよりかは難しくなかった...ですよね...?

Input Actionsの実装:③上下左右への移動を行う

先程のジャンプの処理は、キーを押したら1つの処理を実行するだけなので、シンプルでした。
次に、移動の処理を実装してみましょう。

コントローラー操作の場合、スティックを傾ける角度によって、移動する方向が変わります。
なので、どれだけ左右・上下どちらに傾けられているかの情報を取得して、その方角にキャラクターを移動させる必要があります。

これを踏まえて、先程のInput Actionsに「Move」の処理を追加していきます。
Actionsから新規アクションを追加するところまでは先程と同じですが、今回は
Action Typeが値(Value)、Control TypeがVector2Dになります。

Bindingには、WASDキーと、コントローラーの左スティックを登録していきます。
このとき、ポイントとしては上下左右をWASDキーを使って受け取る場合は、Bindingから、「Up\Down\Left\Right\Composite」というBindingを選択する必要があります。

すると、以下のとおり上下左右に対応したBindingが表示されます。

上下左右移動に対応したBinding

Bindingを、先程のJumpの時と同じ要領で、コントローラーとキーボード(WASDキー)を設定すると以下のようになります。

これで、Input System側の設定は完了です。あとは先程のスクリプトに、移動を行う処理を追加しています。
追加後のスクリプトは以下の通りです。

using UnityEngine;
using UnityEngine.InputSystem;

public class BallMover : MonoBehaviour
{
    [SerializeField] private float jumpForce = 5.0f;
    [SerializeField] private float moveSpeed = 5.0f;

    private Rigidbody rb;
    private MyControls controls;

    private Vector2 moveInput;

    private void Awake()
    {
        // Rigidbodyコンポーネントを取得
        rb = GetComponent<Rigidbody>();

        // MyControlsのインスタンスを作成
        controls = new MyControls();

        // ActionMaps[Player]の中の[Jump]というActionに紐づくイベントリスナーを登録
        controls.Player.Jump.performed += OnJumpPerformed;

        // Moveアクションにリスナーを追加
        controls.Player.Move.performed += OnMovePerformed;
        controls.Player.Move.canceled += OnMoveCanceled;
    }

    private void OnEnable()
    {
        // Inputアクションを有効化
        controls.Enable();
    }

    private void OnDisable()
    {
        // Inputアクションを無効化
        controls.Disable();
    }

    private void OnJumpPerformed(InputAction.CallbackContext context)
    {
        // Rigidbodyに上方向の力を加える
        if (rb != null)
        {
            rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
        }
    }

    //Moveの入力を受け取り、Rigidbodyを使ってボールを動かす
    private void FixedUpdate()
    {
        // 前後左右への移動を処理
        if (rb != null)
        {
            Vector3 move = new Vector3(moveInput.x, 0, moveInput.y) * moveSpeed * Time.fixedDeltaTime;
            rb.MovePosition(rb.position + move);
        }
    }

    private void OnMovePerformed(InputAction.CallbackContext context)
    {
        // Moveアクションの値を取得
        moveInput = context.ReadValue<Vector2>();
    }

    private void OnMoveCanceled(InputAction.CallbackContext context)
    {
        // Moveの入力が無くなったら移動を止める
        moveInput = Vector2.zero;
    }
}

同様にこちらも実行して、WASDキーの移動とコントローラーの入力の両方を試してみます。


上記のようにボールが動けば成功です!

Input Systemの応用的な使い方

応用①:ボタンを押しながらスティックでダッシュ移動を実装

ここからは、少ーしだけ応用的な内容です。
まずは、Bボタンなどを押しながら移動すると、ダッシュ移動をする実装です。

何かを押しながら...という処理も、ここまでの内容を応用すれば実装が出来ます。

先ほどと同様にInputActionsを編集して、「Dash」というアクションを追加。
ActionTypeは、Jumpの時と同様に「ボタン」にします。

Bindingには、Shiftキーと、コントローラーのBボタンを設定します。

そして、スクリプトを以下のように書き換えました。
これはDashボタンが押されている間、isDashというフラグがTrueになり、isDashがTrueの間はDashSpeedの速度で移動するよ、となっています。

using UnityEngine;
using UnityEngine.InputSystem;

public class BallMover : MonoBehaviour
{
    [Header("Jumpパラメータ")]
    [SerializeField] private float jumpForce = 5.0f;
    [Header("Moveパラメータ")]
    [SerializeField] private float moveSpeed = 5.0f;

    [Header("Dashパラメータ")]
    [SerializeField] private float dashSpeed = 10.0f;
    [SerializeField] private bool isDashing;

    private Rigidbody rb;
    private MyControls controls;

    private Vector2 moveInput;

    private void Awake()
    {
        // Rigidbodyコンポーネントを取得
        rb = GetComponent<Rigidbody>();

        // MyControlsのインスタンスを作成
        controls = new MyControls();

        // ActionMaps[Player]の中の[Jump]Actionに紐づくイベントリスナーを登録
        controls.Player.Jump.performed += OnJumpPerformed;

        // Moveアクションにリスナーを追加
        controls.Player.Move.performed += OnMovePerformed;
        controls.Player.Move.canceled += OnMoveCanceled;

        // Dashアクションにリスナーを追加
        controls.Player.Dash.performed += OnDashPerformed;
        controls.Player.Dash.canceled += OnDashCanceled;
    }

    private void OnEnable()
    {
        // Inputアクションを有効化
        controls.Enable();
    }

    private void OnDisable()
    {
        // Inputアクションを無効化
        controls.Disable();
    }

    private void OnJumpPerformed(InputAction.CallbackContext context)
    {
        // Rigidbodyに上方向の力を加える
        if (rb != null)
        {
            rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
        }
    }

    //Moveの入力を受け取り、Rigidbodyを使ってボールを動かす
    private void FixedUpdate()
    {
        // isDashがtrueになっているいれば、dashSpeedで移動
        float currentSpeed = isDashing ? dashSpeed : moveSpeed;

        // 前後左右への移動を処理
        if (rb != null)
        {
            Vector3 move = new Vector3(moveInput.x, 0, moveInput.y) * currentSpeed * Time.fixedDeltaTime;
            rb.MovePosition(rb.position + move);
        }
    }

    private void OnMovePerformed(InputAction.CallbackContext context)
    {
        // Moveアクションの値を取得
        moveInput = context.ReadValue<Vector2>();
    }

    private void OnMoveCanceled(InputAction.CallbackContext context)
    {
        // Moveの入力が無くなったら移動を止める
        moveInput = Vector2.zero;
    }

    // isDashフラグの切り替え
    private void OnDashPerformed(InputAction.CallbackContext context)
    {
          isDashing = true;
    }
    private void OnDashCanceled(InputAction.CallbackContext context)
    {
          isDashing = false;
    }
}

これで実行して、Shiftキーを押しながら、もしくは、Bボタン(右のボタン)を押しながら左スティックを傾けるとダッシュで移動するようになりました。

Dash!

応用②:ボタン同時押し

次に、ボタン同時押しの実装です。
最近のアクションゲームは、LRボタン+□ボタンなどでショートカットアクションを出せたりしますが、そういうやつです。

今回はLトリガーとRトリガー同時押しでボールを消滅させてみます。
まずは、Killというアクションを追加します。

同時押しを反応させるには、Binding With One Modifierを使用します。これは2つのボタンの同時押し判定です。

以下のようにModifierとBindingを設定します。
アクションゲームの例だと、Modifier側がLボタンで、Bindingが□ボタンです。

スクリプトは以下のコードを追加しました。

Awake内の追記

    private void Awake()
    {
        // Rigidbodyコンポーネントを取得
        rb = GetComponent<Rigidbody>();

        // MyControlsのインスタンスを作成
        controls = new MyControls();

        // ActionMaps[Player]の中の[Jump]Actionに紐づくイベントリスナーを登録
        controls.Player.Jump.performed += OnJumpPerformed;

        // Moveアクションにリスナーを追加
        controls.Player.Move.performed += OnMovePerformed;
        controls.Player.Move.canceled += OnMoveCanceled;

        // Dashアクションにリスナーを追加
        controls.Player.Dash.performed += OnDashPerformed;
        controls.Player.Dash.canceled += OnDashCanceled;

        // Killアクションにリスナーを追加
        controls.Player.Kill.performed += OnKillPerformed;
    }

Killメソッドの追加

    private void OnKillPerformed(InputAction.CallbackContext context)
    {
        Destroy(gameObject);
    }

これで実行すると、Lボタンを押しながらRボタンを押すと、ボールが消滅します。(KillじゃなくてDieが正しいかったかも...)

L+Rでボールが消滅

応用③:長押しの実装

次に、長押しでボールを吹っ飛ばす処理を実装します。

Xボタンを2秒以上押して、ボタンを離したらボールが吹っ飛ぶようにします。

・ボタンを2秒以上押していたら、長押し判定が成功する
・ボタンを離したタイミングで処理(ボールを吹っ飛ばす)を行う

ボタンを2秒以上押さないと長押しという判定にならないようにするには、Bindingの設定で行います。
先ほどと同様にActionsを追加します。今回は「Launch」というActionにし、Bindingも同様に登録します。

長押し判定を取る場合は、Bindingの設定のInteractionsのHold Timeの値を変更してください。
Hold Timeが2の場合、2秒押しつづけたら、長押し判定となります。


なお、Press Pointは押し込みの強さです。結構凝ったアクションとかでなければ、個人的にはいじる必要はない気がします(有用な場面があればコメントしてくださいね、定期)

以下の通り、長押し時の処理をスクリプトに追加してみました。2秒以上長押し後、ボタンを離したらボールが吹っ飛ぶようにしています。

using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Interactions;

public class BallMover : MonoBehaviour
{
    [Header("Jumpパラメータ")]
    [SerializeField] private float jumpForce = 5.0f;
    [Header("Moveパラメータ")]
    [SerializeField] private float moveSpeed = 5.0f;

    [Header("Dashパラメータ")]
    [SerializeField] private float dashSpeed = 10.0f;
    [SerializeField] private bool isDashing;

    [Header("Launchパラメータ")]
    [SerializeField] private float launchForce = 15.0f;

    private Rigidbody rb;
    private MyControls controls;

    private Vector2 moveInput;
    private bool isPressed;

    private void Awake()
    {
        // Rigidbodyコンポーネントを取得
        rb = GetComponent<Rigidbody>();

        // MyControlsのインスタンスを作成
        controls = new MyControls();

        // ActionMaps[Player]の中の[Jump]Actionに紐づくイベントリスナーを登録
        controls.Player.Jump.performed += OnJumpPerformed;

        // Moveアクションにリスナーを追加
        controls.Player.Move.performed += OnMovePerformed;
        controls.Player.Move.canceled += OnMoveCanceled;

        // Dashアクションにリスナーを追加
        controls.Player.Dash.performed += OnDashPerformed;
        controls.Player.Dash.canceled += OnDashCanceled;

        // Killアクションにリスナーを追加
        controls.Player.Kill.performed += OnKillPerformed;

        // Launchアクションにリスナーを追加
        controls.Player.Launch.performed += OnLaunchPerformed;
        controls.Player.Launch.canceled += OnLaunchCanceled;
    }

    private void OnEnable()
    {
        // Inputアクションを有効化
        controls.Enable();
    }

    private void OnDisable()
    {
        // Inputアクションを無効化
        controls.Disable();
    }

    private void OnJumpPerformed(InputAction.CallbackContext context)
    {
        // Rigidbodyに上方向の力を加える
        if (rb != null)
        {
            rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
        }
    }

    //Moveの入力を受け取り、Rigidbodyを使ってボールを動かす
    private void FixedUpdate()
    {
        // isDashがtrueになっているいれば、dashSpeedで移動
        float currentSpeed = isDashing ? dashSpeed : moveSpeed;

        // 前後左右への移動を処理
        if (rb != null)
        {
            Vector3 move = new Vector3(moveInput.x, 0, moveInput.y) * currentSpeed * Time.fixedDeltaTime;
            rb.MovePosition(rb.position + move);
        }
    }

    private void OnMovePerformed(InputAction.CallbackContext context)
    {
        // Moveアクションの値を取得
        moveInput = context.ReadValue<Vector2>();
    }

    private void OnMoveCanceled(InputAction.CallbackContext context)
    {
        // Moveの入力が無くなったら移動を止める
        moveInput = Vector2.zero;
    }

    // isDashフラグの切り替え
    private void OnDashPerformed(InputAction.CallbackContext context)
    {
          isDashing = true;
    }
    private void OnDashCanceled(InputAction.CallbackContext context)
    {
          isDashing = false;
    }

    private void OnKillPerformed(InputAction.CallbackContext context)
    {
        Destroy(gameObject);
    }

    private void OnLaunchPerformed(InputAction.CallbackContext context)
    {
        // 長押しインタラクションの開始
        if (context.interaction is HoldInteraction)
        {
            Debug.Log("長押し判定:有効");
        }

        isPressed = true;
    }

    private void OnLaunchCanceled(InputAction.CallbackContext context)
    {       
        if(isPressed)
        {
            Vector3 launchDirection = (transform.forward + transform.up).normalized;
            rb.AddForce(launchDirection * launchForce, ForceMode.Impulse);
            isPressed = false;
        }
        else
        {
            Debug.Log("長押しではない");
        }
    }
}

これで実行してみると、以下のGIF画像のように、ボタンを離した瞬間にボールが飛んでいくはずです。

長押しして、ボタンを離したらボールを吹っ飛ばす

余談ですが、この溜めて発射する操作は結構爽快だなと感じました。VSシリーズのチャージショットとか、個人的に好きなんですよね。

応用③:キャラクターの操作とメニューの操作を切り替える

最後に、Action Mapsを切り替える方法です。
コントローラーを前提としている場合、メニューなどのUIを操作時と、プレイヤーを操作時では操作内容が全然異なることが多いと思います。
そうした時は、Action Maps自体を切り替えるのがよいです。実際にやってみます。

Action Mapsの+ボタンを押して、UIと名付けたAction Mapsを追加しました。
Actionsはとりあえず作りましたがこの後の解説では出てきません。

さらに、PlayerのActionsにメニューUIを開くボタンを追加しました。
コントローラーのStartボタンを押したらPlayerのActionMapsは使わず、UIのActionMapsに切り替えるとします。

Startボタンを押したらメニューを開く

そして、以下の通りコードを修正しました。

    private void Awake()
    {
        // Rigidbodyコンポーネントを取得
        rb = GetComponent<Rigidbody>();

        // MyControlsのインスタンスを作成
        controls = new MyControls();

        // ActionMaps[Player]の中の[Jump]Actionに紐づくイベントリスナーを登録
        controls.Player.Jump.performed += OnJumpPerformed;

        // Moveアクションにリスナーを追加
        controls.Player.Move.performed += OnMovePerformed;
        controls.Player.Move.canceled += OnMoveCanceled;

        // Dashアクションにリスナーを追加
        controls.Player.Dash.performed += OnDashPerformed;
        controls.Player.Dash.canceled += OnDashCanceled;

        // Killアクションにリスナーを追加
        controls.Player.Kill.performed += OnKillPerformed;

        // Launchアクションにリスナーを追加
        controls.Player.Launch.performed += OnLaunchPerformed;
        controls.Player.Launch.canceled += OnLaunchCanceled;

        controls.Player.OpenMenu.performed += OnStartButtonPressed;
    }

    private void OnEnable()
    {
        // Inputアクションを有効化
        controls.Enable();

        // PlayerInputを有効化, UIInputを無効化
        EnablePlayerInput();
    }

    private void EnablePlayerInput()
    {
        controls.Player.Enable();
        controls.UI.Disable();
        canvas.enabled = false;
    }
    private void EnableUIInput()
    {
        controls.Player.Disable();
        controls.UI.Enable();
        canvas.enabled = true;
    }

    private void OnStartButtonPressed(InputAction.CallbackContext context)
    {
        EnableUIInput();
    }

Canvasには、インスペクタから表示させたいUIのCanvasを割り当てます。

この状態で実行すれば、Startボタンを押すとUIが表示され、UI表示中はプレイヤー操作(MoveやJumpなど)が効かなくなります。

補足:UIのボタンとActionを紐づけるには?

たとえば、UIを表示している時に、Bボタンを押したらUIに表示されている「戻る」ボタンと同じ処理をさせるとします。

この場合、「戻る」ボタンがアタッチされているGame Objectに、On-Screen Buttonというコンポーネントをアタッチし、対応するボタンを割り当てることで実装出来ます。

補足:今回の解説で使ったスクリプト

今回、解説を分かりやすくするために、1つのクラスにすべてのコードを詰め込みましたが、これはあまり良い設計ではありません。
実際に実装する場合は、Input Systemからイベントを受け取るクラスと、実際のPlayerやUIの挙動を行うクラスは、それぞれ別のクラスで定義するのが良いでしょう。

という前提は付きますが、作成したスクリプトの最終形を共有します。

using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Interactions;

public class BallMover : MonoBehaviour
{
    [Header("Jumpパラメータ")]
    [SerializeField] private float jumpForce = 5.0f;
    [Header("Moveパラメータ")]
    [SerializeField] private float moveSpeed = 5.0f;

    [Header("Dashパラメータ")]
    [SerializeField] private float dashSpeed = 10.0f;
    [SerializeField] private bool isDashing;

    [Header("Launchパラメータ")]
    [SerializeField] private float launchForce = 15.0f;

    [SerializeField] Canvas canvas;

    private Rigidbody rb;
    private MyControls controls;

    private Vector2 moveInput;
    private bool isPressed;

    private void Awake()
    {
        // Rigidbodyコンポーネントを取得
        rb = GetComponent<Rigidbody>();

        // MyControlsのインスタンスを作成
        controls = new MyControls();

        // ActionMaps[Player]の中の[Jump]Actionに紐づくイベントリスナーを登録
        controls.Player.Jump.performed += OnJumpPerformed;

        // Moveアクションにリスナーを追加
        controls.Player.Move.performed += OnMovePerformed;
        controls.Player.Move.canceled += OnMoveCanceled;

        // Dashアクションにリスナーを追加
        controls.Player.Dash.performed += OnDashPerformed;
        controls.Player.Dash.canceled += OnDashCanceled;

        // Killアクションにリスナーを追加
        controls.Player.Kill.performed += OnKillPerformed;

        // Launchアクションにリスナーを追加
        controls.Player.Launch.performed += OnLaunchPerformed;
        controls.Player.Launch.canceled += OnLaunchCanceled;

        controls.Player.OpenMenu.performed += OnStartButtonPressed;
    }

    private void OnEnable()
    {
        // Inputアクションを有効化
        controls.Enable();

        // PlayerInputを有効化, UIInputを無効化
        EnablePlayerInput();
    }

    private void OnDisable()
    {
        // Inputアクションを無効化
        controls.Disable();
    }

    private void OnJumpPerformed(InputAction.CallbackContext context)
    {
        // Rigidbodyに上方向の力を加える
        if (rb != null)
        {
            rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
        }
    }

    //Moveの入力を受け取り、Rigidbodyを使ってボールを動かす
    private void FixedUpdate()
    {
        // isDashがtrueになっているいれば、dashSpeedで移動
        float currentSpeed = isDashing ? dashSpeed : moveSpeed;

        // 前後左右への移動を処理
        if (rb != null)
        {
            Vector3 move = new Vector3(moveInput.x, 0, moveInput.y) * currentSpeed * Time.fixedDeltaTime;
            rb.MovePosition(rb.position + move);
        }
    }

    private void OnMovePerformed(InputAction.CallbackContext context)
    {
        // Moveアクションの値を取得
        moveInput = context.ReadValue<Vector2>();
    }

    private void OnMoveCanceled(InputAction.CallbackContext context)
    {
        // Moveの入力が無くなったら移動を止める
        moveInput = Vector2.zero;
    }

    // isDashフラグの切り替え
    private void OnDashPerformed(InputAction.CallbackContext context)
    {
          isDashing = true;
    }
    private void OnDashCanceled(InputAction.CallbackContext context)
    {
          isDashing = false;
    }

    private void OnKillPerformed(InputAction.CallbackContext context)
    {
        Destroy(gameObject);
    }

    private void OnLaunchPerformed(InputAction.CallbackContext context)
    {
        // 長押しインタラクションの開始
        if (context.interaction is HoldInteraction)
        {
            Debug.Log("長押し判定:有効");
        }

        isPressed = true;
    }

    private void OnLaunchCanceled(InputAction.CallbackContext context)
    {       
        if(isPressed)
        {
            Vector3 launchDirection = (transform.forward + transform.up).normalized;
            rb.AddForce(launchDirection * launchForce, ForceMode.Impulse);
            isPressed = false;
        }
        else
        {
            Debug.Log("長押しではない");
        }
    }

    private void EnablePlayerInput()
    {
        controls.Player.Enable();
        controls.UI.Disable();
        canvas.enabled = false;
    }
    private void EnableUIInput()
    {
        controls.Player.Disable();
        controls.UI.Enable();
        canvas.enabled = true;
    }

    private void OnStartButtonPressed(InputAction.CallbackContext context)
    {
        EnableUIInput();
    }
}

まとめ

自分の周りでもInput Systemアレルギーを持っている人を何人か見ていたので、実際触ってみるとそこまで難しくないよ~ということが、少しでも伝わると嬉しいです。

個人的には、入力周りはUnreal EngineよりもUnityの方がわかりやすい気がします。Input Systemを使いこなして、いろんな操作を実現しましょう。

それでは、素敵なゲーム制作ライフを!

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

ゲーム制作の敷居を下げ、もっと多くの人にゲーム作りを楽しんでもらうために、ゲームをカンタンに作る方法を”網羅的に”解説しています。
よかったらブックマークお願いします。
Twitter(X)もよければフォローお願いします。

コメント

コメントする

目次