0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Unity 初心者】InputSystem - プレイヤーの入力方法と入力状態の管理について -

Posted at

はじめに

当方も初心者ですので、文章に誤りがある可能性があります。
実際に手元でも動かしてチェックを行い、関連する内容についてはなるべく公式ドキュメントのリンクを添えていますが、読み間違い(あるいは、理解できてないこと)があるかもしれません。そちらをご了承頂ければ幸いです。

調べた経緯

Unity 2D ゲームを作成していたところ、「操作を受け付けない状態」を実装する際に調べたところ、現在の実装で複雑化している部分を簡素化できそうな便利そうな機能を見つけたため、詳細を調べることに。
ついでに、理解を深めるために記事とさせていただきました。

概要

Unity 公式 InputSystemを参考に以下2点についてまとめます。
1.プレイヤーの入力はどうやったらプログラム側で認識されるか?
2.ボタンを押す、離す、長押しするといった状態をどうやったら取得できるか?

公式ドキュメントを参考に、たまーにChatGPTを用いつつ内容をまとめていきます。
以下公式さま
https://docs.unity3d.com/ja/Packages/com.unity.inputsystem@1.4/manual/index.html

Input System

0.Input SystemとInput

旧 Inputクラス 現 Input System Package らしい
引用 公式ドキュメント
https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Input.html

Note: The Input class is part of the legacy Input Manager feature, and not recommended for new projects. The documentation provided below is to support legacy projects that use the old Input Manager and Input class. For new projects you should use the Input System Package.

また、Input Systemのページではこのように述べられています。
引用 公式ドキュメント(日本語)
https://docs.unity3d.com/ja/Packages/com.unity.inputsystem@1.4/manual/index.html

Input System パッケージは、任意の種類の Input Device を使用して Unity のコンテンツを制御するシステムを実装します。これは、Unity の従来の Input Manager (UnityEngine.Input クラス) よりも、より強力で柔軟性が高く、設定しやすい代替となることを目的としています。

Inputクラスについては詳しく調べていませんが、新規プロジェクトならInputSystemを使えば問題なし、Inputはレガシーだから使わないけど、見かける機会はあるかもだから混同しないように気をつけよ、くらいで大丈夫と思います。
インストール方法については公式を参考に(新規プロジェクトなら自動で有効化されてるかも?)
https://docs.unity3d.com/ja/Packages/com.unity.inputsystem@1.4/manual/Installation.html

以下、InputSystemの使用を前提としてまとめていきます。

1.入力の取得方法

3種類から選べる
a.MonoBehaviorスクリプト内でデバイスの操作を取得する
b.PlayerInputコンポーネントを使用する
c.MonoBehaviorスクリプト内でInputActionをpublicで宣言し、Inspectorウィンドウで設定する
これらを二種類に分けると
a:入力元のデバイスに依存する 例:Spaceキー⇨ジャンプ WASD⇨移動
bc: 動作に依存する 例:ジャンプキー⇨ジャンプ 移動キー⇨移動

前者はキーとアクションが結び付けられるため抽象化されるため、複雑になるとわかりにくくなったり、思い込み(例えば、プレイヤーのインベントリ(バッグ)を開くボタンにI派とB派がいる際)によってぐちゃぐちゃになる恐れがある
後者は論理的に「インベントリ開くキーがインベントリ開く」とできるため、その恐れがない
また、後者の方だとジャンプアクションにキーボードにおけるジャンプキーとゲームパッドにおけるジャンプボタンの両方を当てることができ、マルチデバイスへの対応が簡単になる
一方aでマルチデバイス対応する場合、スクリプト内でデバイス毎の操作を設定する必要がある

各取得方法の例

以下、例
詳しい部分を端折ってるのでなんとなくで読んでもらえるといいと思います
詳細は3.入力の動作への反映 にて

aの場合

スクリプトを作成

aの場合
using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerMoveScript : MonoBehaviour
{
    void Update()
    {
        // デバイスの情報を取得
        var gamepad = Gamepad.current;
        var keyboard = Keyboard.current;

        // デバイスが接続されているかチェック
        if (keyboard != null)
        {
            // デバイスの入力をチェック
            if (keyboard.aKey.wasPressedThisFrame)
            {
                Debug.Log("キーボードのAが押されたので左へ移動します。");
            }
        }
        // デバイスが接続されているかチェック
        if (gamepad != null)
        {
            // デバイスの入力をチェック
            if (gamepad.dpad.left.wasPressedThisFrame)
            {
                Debug.Log("ゲームパッドの十字左キーが押されたため左へ移動します。");
            }
        }
    }
}
bの場合

Player Inputコンポーネントを追加し、Actionsをダブルクリック

スクリーンショット 2025-02-15 11.59.38.png

元々設定されているアクションが初めから入っているが今回は使用しない
ので、独自のMoveLeftアクションを作成

スクリーンショット 2025-02-15 12.00.15.png

そうしたら、同じゲームオブジェクト内の任意のスクリプトでOn[アクション名]によりアクションを呼び出せる

bの場合
using UnityEngine;

public class PlayerMoveScriptForInputSystem : MonoBehaviour
{
    // MoveLeftの入力が通知されると実行される
    public void OnMoveLeft()
    {
        Debug.Log("左へ移動します。");
    }
}

(呼び出せない場合はPlayerInputコンポーネントのBehaviorがSend Messagesになっているか確認)

cの場合

スクリプトを作成

cの場合
using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerMoveScriptForInputAction : MonoBehaviour
{
    // スクリプト上で入力を取得
    public InputAction MoveLeftAction;

    void Start()
    {
        // 起動時にInputActionを明示的に有効化
        MoveLeftAction.Enable();
    }

    void Update()
    {
        // 入力をチェック
        if (MoveLeftAction.WasPerformedThisFrame())
        {
            Debug.Log("左へ移動します。");
        }
    }
}

スクリプト記述後にInspectorウインドウで入力キーが設定できるので行う

スクリーンショット 2025-02-15 11.35.01.png

公式だと
a.スクリプトの作成をすばやく始める方法
bc.Input Action (入力アクション) を使用して入力を間接的に取得する方法
としている模様
個人的には
a.とりあえず作りたい
b.コードはあんま打ちたくない
c.ハイブリッドorコードだけで管理したい

こんな感じで区別すればよさげだと思います。
ただaだと使えない機能があるため、その点は注意が必要だと思います。詳しくは次項で

2.入力の状態

参照 Actionについて 
https://docs.unity3d.com/ja/Packages/com.unity.inputsystem@1.4/manual/Actions.html#responding-to-actions

入力の状態について、例えばボタンだと、おおまかに3段階
1.押したときの段階(Started)
2.押し続けている段階(Performed)
3.離した段階(Canceled)

加えて、以下の2段階
4.入力を受け付けない(Disabled)
5.入力を受け付ける(Waiting)
合計5段階の状態が存在する

上記のイメージ画像

image.png

これらの状態はInputAction.phaseで確認可能(まんま上の文章が出てくる)

cの場合
        Debug.Log(MoveLeftAction.phase);

あるいは単純に
1.どのくらい押されてる(0~1) or スティックがどのくらい倒されてる(0~1)
の1段階で管理できる
この状態管理についてはポーリングと呼ばれ、4.(ex)入力の動作の監視(ポーリング)でまとめる
ちなみに、a(デバイスに依存する場合)はこの方法しか取れない

ということでまず5段階の状態について、
1.Interactions(長押し・短押しなどの条件)
2.ActionType(ボタンやスティック動作など)
この二つによって状態が変化される。
まずは1から順番に

2-1.Interactions

公式がわかりやすいのでこちらも
https://docs.unity3d.com/ja/Packages/com.unity.inputsystem@1.4/manual/Interactions.html#%E5%AE%9A%E7%BE%A9%E6%B8%88%E3%81%BF%E3%81%AE-interaction
基本、以下の5種類を用いて制御する

Press:ボタンの押す強さで判定 押すor離す時をperformedとする
Hold: 押す時間で判定 一定時間以上押すとperformedとする
Tap: Holdの逆(一定時間以内に離すとperformedとする)
SlowTap: Holdの親戚(一定時間以上押した上で離すとperformedとする)
MultiTap: 一定の間隔以内に一定回数連続で押すとperformedとする

それぞれ押す強さ・時間・回数などがプロジェクト毎にデフォルト値が設定されており、別に個別に設定することも可能
なおボタンの押す強さは0~1で スティックならめいっぱい倒すと1になる

スクリプトで実装する場合 まず前提として、すでに存在するキーバインド(例えば、Inspectorウインドウ上で設定したキーバインド)のInteractionsを上書きすることはできない(Interactionsプロパティにはreadonlyが付与されているため、上書きすることができない) そのため、スクリプト内でキーバインドを設定する必要がある
InputAction宣言時に宣言
    void Start()
    {
        // InputActionを作成するとともに、キーバインドとInteractionsを付与する
        MoveRightAction = new InputAction(binding: "<Keyboard>/d", interactions: "hold(duration=0.5)");
        // 起動時にInputActionを明示的に有効化
        MoveRightAction.Enable();
    }

あるいは

キーバインド追加時に宣言
        // 空のInputActionを作成
        MoveRightAction = new InputAction();
        // キーバインドとInteractionsを付与
        MoveRightAction.AddBinding("<Keyboard>/d")
        .WithInteractions("hold(duration=0.5)");

前者の場合はこのInputAction全体に付与されるため、例えばAddBindingでFキーを追加した場合でも適応される(基本的にはこれでよさそう)
後者の場合はDキーにのみ適応されているため、後で追加されたキーには適応されない(holdを除いて例えばチャージ撃ちと単発を明確に分ける場合なんかで使えそう?)

あと上記両方を併用した場合、後発で個別に設定したInteractionsで完全に上書きされる(Inspectorウインドウ上で設定した場合と異なる動作を行うため混同しないよう注意)

組み合わせ
    void Start()
    {
        MoveRightAction = new InputAction(interactions: "hold(duration=0.5)");
        MoveRightAction.AddBinding("<Keyboard>/d")
        // ここで個別にInteractionsを設定
        .WithInteractions("tap(duration=0.3)");
        // 起動時にInputActionを明示的に有効化
        MoveRightAction.Enable();
    }

    void Update()
    {
        // ここではhold(duration=0.5)が返される
        Debug.Log("全体のInteractions" + MoveRightAction.interactions);
        // ここではtap(duration=0.3)が返される(holdは適応されなくなる)
        Debug.Log("fキーのInteractions" + MoveRightAction.bindings[0].interactions);
    }

ちょっとわからないとこなので折りたたみで下に

overrideInteractionsで上書きする InputBinding.overrideInteractionsというプロパティを用いると特定のキーバインドのInteractionsを上書きできる模様 仕組みとしては「overrideInteractionsプロパティにnull以外の値が与えられた時に、Interactionsプロパティを自動で上書きする」らしい https://docs.unity3d.com/ja/Packages/com.unity.inputsystem@1.4/api/UnityEngine.InputSystem.InputBinding.html#UnityEngine_InputSystem_InputBinding_overrideInteractions
    void Start()
    {
        // キーバインド(aキー)を取得
        InputBinding aKey = MoveLeftAction.bindings[0];
        // overrideInteractionsに入れたいInteractionsをセット
        aKey.overrideInteractions = "tap";

        // 起動時にInputActionを明示的に有効化
        MoveLeftAction.Enable();
    }

これでoverrideInteractions=tapになる
しかし、interactionsを見ると元々設定されているHoldのままで、どうも上書きされてない模様?
Start()時にInteractionsを付与するために使用するのには使えないのだろうか?わかんないけどとりあえずこの場合の実装では使えない様子

Inspector/InputSystemで実装する場合(個人的にこっちのがおすすめ) Interactionsを付与したいアクションを開く
bの場合 PlayerInputのとこからActionsのとこをダブルクリック

スクリーンショット 2025-02-16 9.20.57.png

cの場合 スクリプトからアクションを選ぶ 全体を選ぶ場合は歯車マーク(画像だとMove Left Actionのとこ) 特定の入力(例えば、キーボード入力)だけ適応したければその入力のとこをダブルクリック(A[Keyboard] のとこ)

スクリーンショット 2025-02-16 9.20.41.png

あとはInteractionsの付与を行う

スクリーンショット 2025-02-16 9.16.00.png

画像はbの場合
Interactionsの項目の右上の"+"をクリックして任意のInteractionを選択すると追加できる
cの場合はDefault値のみ可能 bの場合はDefaultのチェックを外すことで任意の設定が可能に(ウインドウめっちゃ広げないと出ないかも)
Defaultから変更した場合、ctrl(macならcommand)+Cで保存して閉じる
また、MoveLeftActionにHold、A[Keyborad]にMultiTapをそれぞれ付与すると、A[Keyborad]はHoldとMultiTapの両方が付与される(基本的に使うことなさそう?)

2-2.ActionType

以下の三種類から選択
1.Value:デフォルト 入力の強さによって変化する(スティック操作など)
2.Button:OnとOffのどちらかの状況のみを保持
3.Pass-through:Valueのちょい違う版

ValueとPass-throughの違いは「競合解決」の有無とのこと
競合解決はある入力に対して競合がある場合、最も値の大きいもののみを使って解決するやり方だそう
複数のコントローラが接続されてて全部の操作受け取るか一個だけ受け取るか、みたいなのらしい
よくわからなかったのでChatGPTに聞いたのでその内容を置いておきます。ただ、結局いまいちピンとこなかったので読まなくてもいいかも

詳細 公式より 競合解決について https://docs.unity3d.com/ja/Packages/com.unity.inputsystem@1.4/manual/ActionBindings.html#conflicting-inputs Button および Value タイプの Action では、常に競合解決が行われます。ただし、バインドされた Control から単純にすべての入力を収集する目的で Action を使用する状況では、この動作は望ましくない可能性があります。例えば、以下の Action は、使用可能なすべてのゲームパッドの A ボタンを監視します。

Pass-throughの説明
https://docs.unity3d.com/ja/Packages/com.unity.inputsystem@1.4/manual/Actions.html#action-%E3%82%BF%E3%82%A4%E3%83%97
Pass-Through Action では、上記の Value Action で説明した 競合解決 のプロセスはバイパスされ、特定の Control が Action を駆動するという概念は使用されません。バインドされているいずれかの Control に何らかの変更があると、その Control の値と共にコールバックがトリガーされます。これは、一連の Control からの入力をすべて処理する必要がある場合に便利です。

Pass-throughについてChatGPTに聞いた結果
質問内容はPass-throughの説明の分の例を出して、と

その1:エレベーター操作 「Pass-Through(パススルー)」アクションは、Input Systemの特徴的な機能の一つで、通常のValue Actionとは異なり、競合解決のプロセスをバイパスします。これをもっと具体的に、そしてわかりやすく説明するために、実際のゲームのシナリオを例にしてみます。

Pass-Through(パススルー)の理解
まず、通常のValue Actionは、入力を処理する際に複数のコントロール(ボタン、スティック、トリガーなど)からの入力が競合しないように「競合解決」を行います。つまり、どれか一つのコントロールが優先され、他の入力が無視されることがあります。

一方で、Pass-Throughは、この競合解決をバイパスし、複数のコントロールからの入力をすべて「そのまま通す」ことができます。これにより、複数のコントロールからの値を同時に処理したり、どれか一つが入力された場合に即座に反応できるようになります。

例:エレベーターの操作
ここで例えを使って説明します。

例1: 通常の入力処理(Value Action)
ゲーム内で「エレベーターのボタン」を操作するシナリオを考えてみましょう。あなたはゲームパッドで操作をしており、上に行きたい時はスティックを上に倒すか、エレベーターの上ボタンを押すか、どちらかを選べます。

Value Actionでは、競合解決が行われます。スティックを上に倒した場合、ボタンが押されてもその入力は無視され、スティックの入力だけが反映されます。
例2: Pass-Through(パススルー)
今度は、Pass-Throughを使ってエレベーターのボタンを設定します。この場合、ゲームパッドのスティックで上に行くことができ、さらにエレベーターの上ボタンも押すことができ、両方の入力が同時に反映されます。つまり、スティックで上に行きながら、ボタンでも上に行ける状態が作れます。

このように、Pass-Throughを使うと、複数のコントロールが同時に操作できるため、競合解決が行われず、ユーザーの入力に柔軟に反応できるようになります。

わかりやすいまとめ
通常のValue Actionでは、複数のコントロール(スティック、ボタンなど)の入力が競合すると、どれか一つが優先され、他の入力が無視されます。
Pass-Throughでは、複数のコントロールの入力をすべて処理します。つまり、ユーザーが複数のコントロールを同時に使っても、それぞれの入力を反映させることができます。
使いどころ
Pass-Throughは、たとえばゲームの操作で複数の入力を同時に受け付けたい場合に非常に便利です。例えば、プレイヤーが両方のスティックを同時に使って移動したい、あるいは複数のボタンを同時に押して特殊な動作を実行したい場合などです。
普通のValue Actionは、入力が競合しないようにしたい場合に適しています。例えば、ジャンプボタンを押したらジャンプするが、同時に方向を変える入力は無視したい、というような場合です。
結論
Pass-Throughは、入力が競合してもそのまま複数のコントロールからの入力を受け入れて、すべて反映させるため、特定のシナリオでは非常に便利です。上記のエレベーターのボタン操作の例のように、複数の入力が同時に作用する場面で活用できます。

その2:ターゲットロック時 競合する処理のシナリオ まず、競合する処理とは、複数の入力が同時にアクションに関連していて、その結果、入力が競り合う状況を指します。この場合、Pass-Throughを利用して、すべての入力を通し、競合を解決しない形で入力を処理することができます。

例えば、次のようなシナリオを考えます。

シナリオ: ターゲットロックと移動
ターゲットロック(ボタン): ゲーム内でプレイヤーがターゲットをロックするボタン(例えば、L2/R2やRT/LTなど)を使います。このボタンが押されている間は、プレイヤーはターゲットの向きに追従して移動します。

移動(スティックまたはキーボードの方向入力): 移動操作により、プレイヤーキャラクターがワールド内で移動します。この移動はスティックやキーボードの方向によって決定されます。

ジャンプ(ボタン): ジャンプ操作もボタン入力でトリガーされます。

競合する部分:
ターゲットロックが有効になっている間、プレイヤーはキャラクターがターゲットを向くようにしたいけれど、移動とターゲットの向きが競合する場合があります。
移動は通常、プレイヤーのスティックやキーボード入力に基づいて行われますが、ターゲットロック時にキャラクターの移動方向がターゲットの位置に向かないと不自然になります。
ジャンプと移動が同時に行われる場合、ターゲットロックをしている状態でキャラクターがジャンプする場合の動きや挙動も微妙に影響します。
Pass-Throughを使う理由と効果
このようなシナリオでは、ターゲットロックの状態で移動とターゲットの向きが競合することがあり、Pass-Throughを使用してターゲットロック中でも移動操作を競合なしに通すことができます。具体的には、ターゲットロック状態で移動入力を受け取り、その移動操作がターゲットの向きを無視して行われるわけではなく、ターゲットロック時でも両方の入力を同時に処理できるようにします。
(以下コード省略)

それぞれのActionTypeについて、状態管理は以下のように設定されている
以下Action Typeについて参考
https://docs.unity3d.com/ja/Packages/com.unity.inputsystem@1.4/api/UnityEngine.InputSystem.InputActionType.html

A Value action starts (started) as soon as its input moves away from its default value. After that it immediately performs (performed) and every time the input changes value it performs again except if the input moves back to the default value -- in which case the action cancels (canceled).

要約すると
デフォルト値から変わる(started)⇨その直後(performed)⇨値が変わる度(performed)⇨デフォルト値に戻る(canceled)
とのこと
Buttonに関しては押されてるor押されてないの状態のみなので値が変わることはない(performedは一度しか起こらない)

3.入力の動作への反映

以下の状態へ変化した際にコールバックを用いて特定の動作を引き起こすことができる
1.押したときの段階(Started)
2.押し続けている段階(Performed)
3.離した段階(Canceled)
コールバックは必要な時にだけ呼びだされる関数のこと

bの場合

公式 PlayerInputsについて参考に
https://docs.unity3d.com/ja/Packages/com.unity.inputsystem@1.4/manual/Components.html#notification-behaviors
まずはコールバックが使えるようにBehaviorをSendMessage⇨Invoke Unity Eventsに変更する

スクリーンショット 2025-02-16 11.31.51.png

各Behaviorについて詳しくは↓

軽くそれぞれのBehaviorについて Send Messages: 同じゲームオブジェクト内で呼び出せるようになる 例:MoveLeftの入力をOnMoveLeft()を呼び出せる

Broadcast Messages:
上記+αで子オブジェクトでも呼び出せるようになる
例えばシューティングゲームでプレイヤーに追従するサブユニットがあるとして
プレイヤーの攻撃時にサブユニットからも攻撃を行いたい場合に使えそう

Invoke Unity Events:
各イベントについて動作を設定できる
アクションが呼び出された時に、どのオブジェクトのどの動作を行うかを設定できる

Invoke CSharp Events:
上記のスクリプト内でのみいじれる版
onActionTriggered (プレイヤーのすべてのアクションに対応する集合的なイベント)
onDeviceLost
onDeviceRegained
これらを使ってイベントを取得する

スクリプトも修正して引数にコールバックを受け取らせる

bの場合
using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerMoveScriptForInputSystem : MonoBehaviour
{
    // MoveLeftの入力が通知されると実行される
    public void OnMoveLeft(InputAction.CallbackContext ctx)
    {
        Debug.Log("左へ移動します。");
    }
}

using UnityEngine.InputSystemを忘れずに(InputAction.CallbackContextってなに?ってエラーが出るので)

次にInputActionにコールバック関数を関連付させて、特定のキーを押した時にコールバック関数が走るようにイベントを設定する
Inspectorウインドウの処理 Behaviorの下に▼Eventsの項目が増えてるのでそこからEvents>Playerを開く(デフォルトのセットを使用している場合)

スクリーンショット 2025-02-16 12.00.35.png

各入力についてイベントが出るので、対象のイベント(今回はMoveLeft)を探して左下の+を押して空のイベントを追加する

スクリーンショット 2025-02-16 12.20.55.png

各項目について設定を行う
黄色枠:
イベントの発火について 基本的にはRuntime Onlyでokみたい

黄枠の詳細(ChatGPT様のお言葉) UnityのPlayerInputコンポーネントで、Behaviorを「Invoke Unity Events」に設定した場合、アクションにイベントを追加する際に表示される「Off/Editor And Runtime/Runtime Only」の項目は、イベントがどのタイミングで発火するかを制御するオプションです。

具体的には以下の通りです:

Off:

このオプションは、イベントが発火しないことを意味します。アクションに関連するイベントは、どのタイミングでも呼び出されません。
Editor And Runtime:

このオプションは、イベントがエディタと実行時の両方で発火することを意味します。つまり、Unityエディタでのプレイ中や、ゲームの実行時に両方でイベントが呼び出されます。
エディタ上でもテストや確認ができるようにする場合に使用します。
Runtime Only:

このオプションは、イベントがゲームが実行されている時のみ発火することを意味します。エディタでプレイしているときには発火しませんが、ゲームがビルドされて実行されている時にはイベントが発生します。
ゲーム本番の挙動に関連したイベントのみを呼び出す場合に使います。

紫枠:
イベントが行われるオブジェクト
基本的には自身を選べば問題ないはず
緑枠:
イベントの本体
紫枠で設定したオブジェクト内のコンポーネントから選択可能
今回であればPlayerMoveScriptForInputSystemを選択
その中から「OnMoveLeft」を探して選択する

スクリーンショット 2025-02-16 12.00.50.png

これでMoveLeftに当てられているキーを押すとOnMoveLeftがコールバックされるようになる
ただし、現状では各段階を区別していないため条件分岐を追加する必要がある

bの場合
    public void OnMoveLeft(InputAction.CallbackContext ctx)
    {
        if (ctx.started)
        {
            Debug.Log("Started状態の処理");
        }
        else if (ctx.performed)
        {
            Debug.Log("Performed状態の処理");
        }
        else if (ctx.canceled)
        {
            Debug.Log("Canceled状態の処理");
        }
    }

これにより渡された引数からどの段階かを読み取って処理が可能になる
ctx.startedなどについては以下を参考に(InputAction.CallbackContextのプロパティ)
https://docs.unity3d.com/ja/Packages/com.unity.inputsystem@1.4/api/UnityEngine.InputSystem.InputAction.CallbackContext.html?q=Inputaction#UnityEngine_InputSystem_InputAction_CallbackContext_canceled

cの場合 Start()に各コールバックが行われた際の動作を追加する
cの場合
    void Start()
    {
        // 起動時にInputActionを明示的に有効化
        MoveLeftAction.Enable();
        MoveLeftAction.started += ctx => Debug.Log(ctx + "左移動キーが押されました。");
        MoveLeftAction.performed += ctx => Debug.Log("左へ移動します。");
        MoveLeftAction.canceled += ctx => Debug.Log("左移動キーが離されました。");
    }

これによって各段階に移行し、コールバックが行われる際にメッセージが表示されるようになる

スクリーンショット 2025-02-16 10.45.27.png

上記はボタンを押して離した場合 started⇨performed⇨canceledの順番で動作しているのがわかる

また、スティック操作など強弱がある場合にはどのくらい倒してるかをReadValueを用いて取得することができる

cの場合
    void Start()
    {
        // 起動時にInputActionを明示的に有効化
        MoveLeftAction.Enable();
        MoveLeftAction.started += ctx => Debug.Log("左移動キーが押されました。");
        MoveLeftAction.performed += ctx => OnMoveLeftPerformed(ctx); // 関数を呼び出す
        MoveLeftAction.canceled += ctx => Debug.Log("左移動キーが離されました。");
    }

    // 左へ移動する関数
    private void OnMoveLeftPerformed(InputAction.CallbackContext ctx)
    {
        float move = ctx.ReadValue<float>();
        Debug.Log("左へ移動します。 移動距離:" + move);
    }

ctxいらなければ引数に入れなくてok(ボタン操作の場合なんか)

cの場合
    void Start()
    {
    // ...省略
        MoveLeftAction.canceled += ctx => OnMoveLeftCanceled(); // 引数は取らない
    }

    // 左へ移動を終了する関数
    private void OnMoveLeftCanceled()
    {
        Debug.Log("左移動キーが離されました。");
    }

4.(ex)入力の動作の監視(ポーリング)

a.デバイスの操作を取得する 場合についてはこの方法のみ
一応b,cでも使用可能

aでポーリングしか使えない理由(ChatGPT君の回答)

Q.UnityのGamepadクラスについて この入力について、コールバックかポーリングどちらの手法を利用できますか?

A.UnityのGamepadクラスは、主にポーリング方式でゲームパッドの入力を取得します。ポーリングとは、フレームごとにゲームパッドの状態を確認する方法です。この場合、特定のボタンが押されたかどうか、またはスティックの位置がどうなっているかを毎フレームチェックします。

< 以下略 >

ポーリングは「システムに定期的に問い合わせを行う」という意味とのこと
IT用語辞典様より https://e-words.jp/w/%E3%83%9D%E3%83%BC%E3%83%AA%E3%83%B3%E3%82%B0.html#google_vignette
UnityのInputActionにおけるポーリングはUpdate()内でフレームごとにReadValueによって入力状態を取得することで実装可能

c(polling)の場合
using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerMoveLeftPolling : MonoBehaviour
{
    public InputAction MoveLeftAction;
    void Start()
    {
        // 起動時にInputActionを明示的に有効化
        MoveLeftAction.Enable();
    }

    void Update()
    {
        // 入力を監視する
        float move = MoveLeftAction.ReadValue<float>();
        Debug.Log("現在の移動距離(左)" + move);
    }
}

コールバックでは呼び出しに応じてコールバックが呼び出されていたのに比べ、Update()内のポーリングでは毎フレームごとに呼び出されている

公式曰く以下とのこと
https://docs.unity3d.com/ja/Packages/com.unity.inputsystem@1.4/manual/Actions.html#action-%E3%81%B8%E3%81%AE%E5%BF%9C%E7%AD%94

コールバックを使用する代わりに、コード内で必要になったときに Action の状態をポーリングする方が簡単である場合があります。

3Dゲームの視点移動とか常に処理する場合は有用なのかな?といった印象

あと、ポーリングと合わせて以下のメソッドで段階を表現できる

メソッド一覧 WasPerformedThisFrame(): アクションが有効になった瞬間のフレーム中に1度だけtrueを返す(Performedと同様)

以下ボタン限定
InputAction.IsPressed() :
ボタンが押されている間常にtrueを返し続ける(ReadValueとほぼ同様)
InputAction.WasPressedThisFrame():
ボタンが押された瞬間のフレーム中に一度だけtrueを返す(Performedと同様)
InputAction.WasReleasedThisFrame():
ボタンが離された瞬間のフレーム中に一度だけtrueを返す(InteractionsのPressのBehaviorがReleaseOnlyの時のPerformedと同様)

ただ、このメソッドを利用してコールバックのそれを再現しようとすると、別で複数のフラグを管理をする必要があるため、段階的な処理を行いたいなら大人しくコールバックで実装する方が良さそう 以下おまけ
CallbackをPollingで実装しようとしてみた
Callback vs Polling
using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerMoveLeftPollingVSCallback : MonoBehaviour
{
    bool isStarted;
    bool isPerformed;
    bool isCanceled;
    bool isWaiting;
    public InputAction MoveLeftAction;
    void Start()
    {
        // 起動時にInputActionを明示的に有効化
        MoveLeftAction.Enable();

        // コールバックの場合------------------
        MoveLeftAction.started += ctx => Debug.Log("左移動キーが押されました。");
        MoveLeftAction.performed += ctx => Debug.Log("左へ移動します。");
        MoveLeftAction.canceled += ctx => Debug.Log("左移動キーが離されました。");
    }

    void Update()
    {
        // ポーリングの場合----------------
        float move = MoveLeftAction.ReadValue<float>();
        if (move > 0)
        {
            if (isWaiting)
            {
                isStarted = true;
                isWaiting = false;
            }
            else if (MoveLeftAction.WasPerformedThisFrame())
            {
                isStarted = false;
                isPerformed = true;
            }
        }
        if (move <= 0)
        {
            if ((isStarted || isPerformed))
            {
                isStarted = false;
                isPerformed = false;
                isCanceled = true;
                isWaiting = true;
            }
            else
            {
                isCanceled = false;
            }
        }
        if (isStarted)
        {
            Debug.Log("左移動キーが押されました");
        }
        if (isPerformed)
        {
            Debug.Log("左へ移動します。");
        }
        if (isCanceled)
        {
            Debug.Log("左移動キーが離されました。");
        }
    }
}

ただこの実装の場合、Performedの状態の場合、常にPeformedの処理が走ってしまうため、厳密にはさらにフラグが必要だと思われる
もっといい実装もあるだろうけど、別にコールバックで十分かなーといった印象

5.おわり

私が実装の参考にしていたUnityLearnで行ったチュートリアルにおいて、少し古いものだったからか、理由ははっきりわからないですが各入力をReadValue()を使って、ポーリング形式で操作を管理していました。
そのため、実装を行うにあたって私も同じ形式を採用していました。
今回詳しく調べて、コールバックを採用したらここらへんのフラグ管理が簡単にまとめられそうだなーとか、今後の新たな操作を実装するにあたっても使えそうだなーとか、色々学べてよかったです。
もしこちらの内容が他の方にも参考になれば嬉しいです。
それでは

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?