複雑な入力処理やランタイム制御の再マッピングなど、より高度な入力機能が必要とされる Unreal Engine 5 (UE5) プロジェクトでは、Enhanced Input を使用すると、Unreal Engine 4 (UE4) のデフォルトの入力システムに対するアップグレード パスと後方互換性がデベロッパーに提供されます。
Enhanced Input では、放射状のデッド ゾーン、コード アクション、コンテキスト対応入力、優先順位付けなどの機能、および アセット ベースの環境で生の入力データのフィルタリングや処理を拡張する機能が実装されています。
動的入力マッピングとコンテキスト対応入力マッピング
Enhanced Input を使用すると、プレイヤーに対する マッピング コンテキスト をランタイム時に追加および削除できます。そうすることで、多数の アクション を管理することが容易になります。特定の入力がどのように動作するかを、プレイヤーの現在の状態に応じて変化させることができます。
たとえば、歩行し、ダッシュし、うつぶせになることができるプレイヤー キャラクターがある場合に、キャラクターの動きのそれぞれのタイプに応じてマッピング コンテキストを切り替えて、Ctrl キーで異なるアクションを行うようにすることができます。歩行中に Ctrl キーを押すと、しゃがみ、ダッシュ中に Ctrl キーを押すと、滑り込み、うつぶせになっているときに Ctrl キーを押すと、立ち上がるように切り替えることができます。
Enhanced Input アセットを作成する
Enhanced Input はデフォルトで有効になっています。入力アセットを作成するには、コンテンツ ブラウザで [Add (追加)](+) をクリックし、[Input (入力)] カテゴリに移動します。
主要概念
Enhanced Input システムには、入力アクション、入力マッピング コンテキスト、入力モディファイア、入力トリガー という 4 つの主要概念があります。
入力アクション
入力アクション は、Enhanced Input システムとプロジェクトのコードの間の通信リンクです。入力アクションは、データ アセットであること以外は、概念上は アクション と 軸 マッピング名に相当します。各入力アクションは、「しゃがむ」や「武器を発射する」など、ユーザー行うことができることを表します。入力アクションの状態が変化したときに、ブループリントまたは C++ のいずれかで 入力リスナー を追加できます。
入力アクションには、その動作を決定するいくつかのタイプがあります。単純なブール型アクションにすることも、もっと複雑な 3D 軸にすることもできます。アクションのタイプによって値が決まります。ブール型アクションには 1 つの単純な ブール 値があり、Axis1D は 浮動小数点 値であり、Axis2D は FVector2D であり、Axis3D は FVector 全体です。
オンとオフの状態がある入力にはブール型アクションを使用します。これは、レガシー入力システムでの旧式のアクション マッピングに相当します。ゲームパッドのサムスティック値のようなコントロールでは、2D 軸アクションを使用して、サムスティック位置の X と Y の値を保持できます。3D 軸アクションを使用すると、モーション コントローラーのような、もっと複雑なデータを保持できます。
たとえば、「アイテムを拾う」アクションでは、ユーザーがキャラクターに何かを拾わせるかどうかを表すオンとオフの状態のみが必要であり、「歩行」アクションでは、ユーザーがキャラクターを歩かせる方向と速度を表現する 2 D 軸が必要です。
トリガー状態
トリガー状態 は、アクションの現在の状態を表しており、Started、Ongoing、Triggered、Completed、Canceled などがあります。多くの場合、「Triggered」状態が使用されます。C++ とブループリントのどちらでも、特定の状態をバインドすることができます。
-
Triggered (トリガー済み): アクションはトリガーされました。これは、すべてのトリガー要件の評価が完了していることを意味します。たとえば、「押して離す」トリガーは、ユーザーがキーを離したときに送られます。
-
Started (開始済み): トリガーの評価を始めるイベントが発生しました。たとえば、「ダブルタップ」トリガーの最初のタップでは、「Started」が 1 度だけ呼び出されます。
-
Ongoing (継続中): トリガーは引き続き処理されています。たとえば、「押したままにする」アクションは、ユーザーがボタンを押している間は、指定された時間が経過するまで継続中です。トリガーによっては、入力値を受け取ってからアクションが評価されている間は、このイベントがティックごとに発行されることがあります。
-
Completed (完了済み): トリガーの評価が完了しています。
-
Canceled (キャンセル済み): トリガーはキャンセルされました。たとえば、ユーザーが「押したままにする」アクションがトリガーされる前に、ボタンを離した場合です。
入力リスナーを追加する
ブループリントで入力アクションリスナーを追加するには、ブループリントのイベント グラフ内で右クリックし、入力アクションのデータ アセットの名前を入力します。
C++ で次のようにして入力アクションをバインドすることもできます。
void AFooBar::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
UEnhancedInputComponent* Input = Cast<UEnhancedInputComponent>(PlayerInputComponent);
// "ETriggerEvent" 列挙値を変更することで、ここで任意のトリガーイベントにバインドすることができます。
Input->BindAction(AimingInputAction, ETriggerEvent::Triggered, this, &AFooBar::SomeCallbackFunc);
}
void AFooBar::SomeCallbackFunc(const FInputActionInstance& Instance)
{
// 入力アクションの値を取得します。
FVector VectorValue = Instance.GetValue().Get<FVector>();
FVector2D 2DAxisValue = Instance.GetValue().Get<FVector2D>();
float FloatValue = Instance.GetValue().Get<float>();
bool BoolValue = Instance.GetValue().Get<bool>();
// ここで、クールなことをやってみましょう!
}
入力マッピング コンテキスト
入力マッピング コンテキスト は、プレイヤーが入る可能性がある特定のコンテキストを表す入力アクションのコレクションであり、指定された入力アクションを何に対してトリガーするかというルールが記述されています。マッピング コンテキストの追加、削除、優先順位付けをユーザーごとに動的に行うことができます。
入力マッピング コンテキストを作成するには、コンテキスト ブラウザ 内で右クリックし、[Input] オプションを展開して [Input Mapping Context (入力マッピング コンテキスト)] を選択します。
入力マッピング コンテキストの基本的な構造は、最上位のレベルに入力アクションのリストがある階層です。入力アクション レベルの下には、キー、ボタン、移動軸などの各入力アクションをトリガーできるユーザー入力のリストがあります。
最下位のレベルには、各ユーザー入力に対する入力トリガーと入力モディファイアのリストがあります。このリストを使用して、生の入力値をどのようにフィルタリングまたは処理するか、および階層の最上位にある入力アクションを制御するためにどのような制約を満たす必要があるかを決定することができます。
Enhanced Input Local Player Subsystem を使用して 1 つまたは複数のコンテキストをローカル プレイヤーに適用でき、コンテキストに優先順位を付けることで、同じ入力を消費しようとする複数のアクション間での競合を解消できます。
ここでは、実際のキーの入力アクションへのバインディングを実装し、各アクションに対して追加のトリガーまたはモディファイアを指定します。入力マッピング コンテキストを Enhanced Input サブシステムに追加するときに、優先順位を指定することもできます。同じ入力アクションにマッピングされているコンテキストが複数ある場合に、その入力アクションがトリガーされると、優先順位が最も高いコンテキストで処理され、他のコンテキストは無視されます。
その例として、泳ぎ、歩行、車両の運転を行うことができるキャラクターに複数の入力マッピング コンテキストを適用するとします。利用可能で常に同じユーザー入力にマッピングされる共通アクションに 1 つのコンテキストを適用し、その他のコンテキストは個々の移動モードに適用します。
そして、デベロッパーは車両に関連する入力アクションを個別の入力マッピング コンテキストに配置し、ローカル プレイヤーが車両に乗り込むときにそのコンテキストを追加し、車両を降りるときにローカル プレイヤーから取り除きます。
そうすることで、不適切な入力アクションが実行されないようになるため、最適化およびバグの防止に役立ちます。また、相互に排他的な入力マッピング コンテキストを使用すると、入力の競合を回避できるため、ユーザー入力をさまざまな入力アクションに使用している場合に、ユーザー入力によって誤ったアクションが偶然にトリガーされることがなくなります。
入力マッピング コンテキストは、ブループリントと C++ のどちらでもプレイヤーに追加できます。
// ヘッダファイルのプロパティとしてマッピングコンテキストを公開します。
UPROPERTY(EditAnywhere, Category="Input")
TSoftObjectPtr<UInputMappingContext> InputMapping;
// cpp の中で...
if (ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(Player))
{
if (UEnhancedInputLocalPlayerSubsystem* InputSystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
{
if (!InputMapping.IsNull())
{
InputSystem->AddMappingContext(InputMapping.LoadSynchronous(), Priority);
}
}
}
入力モディファイア
入力モディファイア は、UE が受信した生の入力値を、入力トリガーに送信する前に変更するプリプロセッサです。Enhanced Input プラグインには、軸の順序の変更、「デッド ゾーン」の実装、軸入力のワールド空間への変換などのタスクを実行するためのさまざまな入力モディファイアがあります。
入力モディファイアは、感度設定の適用、複数のフレームにわたる入力の平滑化、プレイヤーの状態に基づいて入力がどのように動作するかを変化させるのに便利です。独自のモディファイアを作成する際に UPlayerInput
クラスにアクセスできるため、所有するプレイヤー コントローラーにアクセスでき、必要とするゲーム ステートを取得できます。
独自の入力モディファイアを C++ またはブループリントで作成するには、UInputModifier
クラスのサブクラスを作成し、ModifyRaw_Implementation
関数をオーバーライドします。
また、入力モディファイア を親として使用して、新しい 子ブループリントクラス を作成することで、独自の入力モディファイアを作成することもできます。
次に、[My Blueprint (マイブループリント)] > [Functions (関数)] > [Override (オーバーライド)] を選択し、ドロップダウン メニュー で Modify Raw 関数を選択します。
出力パラメータは 入力アクション値 であり、この値には 3 つの Float 値があり、Vector によく似ています。この関数の入力パラメータには、Player Input オブジェクト、入力ハードウェアまたは直前の入力モディファイアからの Current Value、Delta Time 値があります。
Modify Raw から返される入力アクション値は、次の入力モディファイアがあれば、そのモディファイアに入り、なければ、最初の入力トリガーに入ります。
Lyra サンプル ゲームで使用されている入力モディファイアの例を以下に示します。
/** Applies an inversion of axis values based on a setting in the Lyra Shared game settings */
UCLASS(NotBlueprintable, MinimalAPI, meta = (DisplayName = "Lyra Aim Inversion Setting"))
class ULyraInputModifierAimInversion : public UInputModifier
{
GENERATED_BODY()
protected:
virtual FInputActionValue ModifyRaw_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue CurrentValue, float DeltaTime) override
{
{
ULyraLocalPlayer* LocalPlayer = LyraInputModifiersHelpers::GetLocalPlayer(PlayerInput);
if (!LocalPlayer)
{
return CurrentValue;
}
ULyraSettingsShared* Settings = LocalPlayer->GetSharedSettings();
ensure(Settings);
FVector NewValue = CurrentValue.Get<FVector>();
if (Settings->GetInvertVerticalAxis())
{
NewValue.Y *= -1.0f;
}
if (Settings->GetInvertHorizontalAxis())
{
NewValue.X *= -1.0f;
}
return NewValue;
}
}
};
方向入力
入力モディファイアの良い活用例は、1 つの入力アクションを使用した 2 方向入力です。マウスやゲームパッドのアナログ スティックでの 2 方向の動きを読み取るのは、2 つ以上の軸をサポートする入力アクションを作成し、適切な入力を入力マッピング コンテキストに追加するだけで済みます。
Enhanced Input では、キーボードの矢印キーやよく使われる「WASD」キー コンフィギュレーションのような 1 方向のソースからの入力がサポートサポートされているため、正しい入力モディファイアを適用することで、そのようなコントロール スキームを実現できます。具体的には、Negate を使用して、一部のキーを負の値として登録し、Swizzle Input Axis Values を使用して、一部のキーをデフォルトの X 軸ではなく Y 軸として登録します。
文字キー | 矢印キー | 目的とする入力解釈 | 必要な入力モディファイア |
---|---|---|---|
W キー | 上向き矢印キー | 正の Y 軸 | Swizzle Input Axis Values (YXZ または ZXY) |
A キー | 左向き矢印キー | 負の X 軸 | Negate |
S キー | 下向き矢印キー | 負の Y 軸 | Negate Swizzle Input Axis Values (YXZ または ZXY) |
D キー | 右向き矢印キー | 正の X 軸 | (なし) |
各キーでは正の 1 方向の値が伝えられるため、この値では常に X 軸が使用され、どのティックでも 0.0 または 1.0 の値を持ちます。左向き矢印と下向き矢印の入力の値を反転し、入力の X 軸値が上向き矢印と下向き矢印の入力の Y 軸値になるように軸の順序を切り替えることで、入力モディファイアを使用して 1 組の 1 方向の入力を 1 つの 2 方向の入力値として解釈するようにできます。
入力トリガー
入力トリガーは、ユーザー入力が入力モディファイアの任意指定のリストを通過した後に、その入力マッピングコンテキスト内で対応する入力アクションをアクティブにするかどうかを決定します。ほとんどの入力トリガーでは、つまり、最小作動値をチェックし、短いタップ、長押し、または通常の「押す」または「離す」イベントなどのパターンを検証することでして、入力自体が分析されます。このルールの例外の 1 つが「Chorded Action」入力トリガーであり、これは別の入力アクションでのみトリガーされます。デフォルトでは、入力に関するすべてのユーザー アクティビティは、ティックごとにトリガーされます。
入力トリガーには次の 3 つのタイプがあります。
-
Explicit タイプでは、入力トリガーが成功した場合に、入力が成功します。
-
Implicit タイプでは、入力トリガーと他の Implicit タイプの入力トリガーすべてが成功した場合にのみ、入力が成功します。
-
Blocker タイプでは、入力トリガーが成功した場合に、入力が失敗します。
各トリガー タイプが、他のトリガー タイプとの状況でどのように影響を及ぼし合うかの論理的な例を以下に示します。
Implicits == 0 かつ Explicits == 0:値が 0 でなければ常に発行される。
Implicits == 0 かつ Explicits > 0:1 つ以上の Explicit トリガーが発行されている。
Implicits > 0 かつ Explicits == 0:すべての Implicit トリガーが発行されている。
Implicits > 0 かつ Explicits > 0:すべての Implicit トリガーと 1 つ以上の Explicit トリガーが発行されている。
Blocker:他のすべてのトリガーをオーバーライドして、トリガーを失敗させる。
ユーザー入力を処理すると、入力トリガーは次の 3 つの状態のいずれかを返します。
-
None は、入力トリガーの条件が満たされていないために、入力トリガーが失敗していることを表します。
-
Ongoing は、入力トリガーの条件が部分的に満たされていて、入力トリガーが処理中であるが、まだ成功していないことを表します。
-
Triggered は、入力トリガーの条件がすべて満たされていて、入力トリガーが成功していることを表します。
独自の入力トリガーを作成するには、基本入力トリガー クラス、つまり Input Trigger Timed Base を拡張します。
Input Trigger Timed Base は、入力が一定時間押されたままであることを確認してから、それを受け入れ、Ongoing 状態に戻ります。
付属の Input Trigger Timed Base クラスは Triggered 状態を返すことはありません。入力トリガーの新しい子クラス内でこれらの関数をオーバーライドして、ユーザー入力に対してどのように反応するかを決定します。関数 Get Trigger Type は入力トリガーのタイプを取得します。Update State は、プレイヤーの入力オブジェクト、現在の入力アクション値、デルタ タイムを受け取り、None、Ongoing、Triggered のいずれかの状態を返します。
C++ での例として、InputTriggers.h
にある UInputTriggerHold
の実装を参照してください。
UInputTriggerHold.H
/** UInputTriggerHold
Trigger fires once input has remained actuated for HoldTimeThreshold seconds.
Trigger may optionally fire once, or repeatedly fire.
*/
UCLASS(NotBlueprintable, MinimalAPI, meta = (DisplayName = "Hold"))
class UInputTriggerHold final : public UInputTriggerTimedBase
{
GENERATED_BODY()
bool bTriggered = false;
protected:
virtual ETriggerState UpdateState_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue ModifiedValue, float DeltaTime) override;
public:
virtual ETriggerEventsSupported GetSupportedTriggerEvents() const override { return ETriggerEventsSupported::Ongoing; }
// トリガーを発生させるために、入力をどのくらい保持する必要があるか?
UPROPERTY(EditAnywhere, Config, BlueprintReadWrite, Category = "Trigger Settings", meta = (ClampMin = "0"))
float HoldTimeThreshold = 1.0f;
// このトリガーは一度だけ発火させるべきか、それともホールドタイム閾値が満たされた時点で毎フレーム発火させるべきか?
UPROPERTY(EditAnywhere, Config, BlueprintReadWrite, Category = "Trigger Settings")
bool bIsOneShot = false;
virtual FString GetDebugState() const override { return HeldDuration ? FString::Printf(TEXT("Hold:%.2f/%.2f"), HeldDuration, HoldTimeThreshold) : FString(); }
};
UInputTriggerHold.cpp
ETriggerState UInputTriggerHold::UpdateState_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue ModifiedValue, float DeltaTime)
{
// Helduration を更新し、基本状態を抽出します
ETriggerState State = Super::UpdateState_Implementation(PlayerInput, ModifiedValue, DeltaTime);
// HeldDuration が閾値に達したときにトリガーをします
bool bIsFirstTrigger = !bTriggered;
bTriggered = HeldDuration >= HoldTimeThreshold;
if (bTriggered)
{
return (bIsFirstTrigger || !bIsOneShot) ? ETriggerState::Triggered : ETriggerState::None;
}
return State;
}
プレイヤー マッピング可能入力コンフィグ (PMI)
マッピング可能コンフィグは、マッピングの 1 つの「コンフィグ」または「プリセット」を表す入力マッピング コンテキストのコレクションです。たとえば、エイミング用の異なる入力マッピング コンテキストが入っている「Default」と「Southpaw」のマッピング可能コンフィグを使用することができます。
これらのコンフィグを使用して 1 組のコンテキストとその優先順位を事前定義し、それらすべてを一度に追加できます。入力マッピング コンテキストの配列を手作業で追加する必要はありません。マッピングでは、UI 設定画面のプログラミングを容易にすることができるメタデータ オプションのさまざまな選択内容が提供されます。
デバッグ コマンド
作業を行っている入力関連の動作をデバッグするのに利用できる、入力に関連するいくつかのデバッグ コマンドがあります。
showdebug enhancedinput
コマンドを使用すると、利用可能な入力アクションとプロジェクトで使用されている軸マッピングが表示されます。
コマンド showdebug devices
を使用している例
インジェクション入力
Enhanced Input では、「インジェクション入力」の概念もプレイヤーに提供されます。インジェクション入力によって、ブループリントまたは C++ で関数を呼び出すか、コンソール コマンドを使用して、プレイヤーの入力をシミュレートする手段が提供されます。Input.+key
コンソール コマンドを使用して、入力のシミュレートを開始できます。
Gamepad_Left2D
キーを設定する例を以下に示します。
Input.+key Gamepad_Left2D X=0.7 Y=0.5
Input.-key Gamepad_Left2D
キー名には、実際の FKey 名のどれでも指定できます。FKey 名は、InputCoreTypes.cpp
ファイルに記載されています。この名前は、表示されているキー名から空白文字を取り除けばキー ピッカー ウィジェットでも見つかります。
UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PC->GetLocalPlayer());
UEnhancedPlayerInput* PlayerInput = Subsystem->GetPlayerInput();
FInputActionValue ActionValue(1.0f); // This can be a bool, float, FVector2D, or FVector
PlayerInput->InjectInputForAction(InputAction, ActionValue);
プラットフォーム設定
プラットフォームごとに異なる入力設定が必要になることがあります。Nintendo Switch のフェース ボタンの回転や、モバイル デバイスで利用可能なアクションの変更などです。Enhanced Input では、それを簡単に行うことができる、プラットフォームごとの マッピング コンテキスト リダイレクト が提供されています。 ~ Enhanced Input Platform Data クラスに基づいたブループリントを作成できます。このクラスは、プラットフォーム特有のオプションをゲームに追加するために使用できる基本クラスです。デフォルトでは、入力マッピング コンテキストの 1 つのマップが入っており、コンテキストを別のコンテキストにリダイレクトできます。 ~ 特定のプラットフォームでマッピング コンテキストが参照されると常に、そのマッピングが再構築されるときにそのマップにある値で置き換えられます。
このリダイレクトを適用するには、[Project Settings (プロジェクト設定)] > [Enhanced Input] > [Platform Settings (プラットフォーム設定)] > [Input Data (入力データ)] に追加します。
これらのプロジェクト設定はプロジェクトの DefaultInput.ini
に追加されるため、ホットフィックス対応であり、簡単に変更できます。プラットフォーム設定では UEnhancedInputPlatformData
の基本クラスが提供されているため、ブループリントまたは C++ で独自のサブクラスを作成し、その設定にどこからでもアクセスできるようにすることで、カスタム プラットフォーム設定を作成できます。