CommonUI : Common Action Widget のホールド表現のみ有効化する拡張

UE5

前回、Common Button Base を使用して、Common Action Widget のホールド表現を行った。
が、Common Button Base はあくまでボタンのとしての機能を提供するものなので、HUD に表示したいだけの用途としては片手落ち感があった。

今回は、独自に C++ で HUD 表示向けのホールド表現が可能な Widget を CommonUI を使って自作してみる。

実装した内容

結論から。基本的には以下の問題を解決する実装を行うことができた。

  • CommonButtonBase にしか入力イベントのバインドが実装されていないため、HUD 表示だけしたい場合に面倒なことになる問題
    • → 内部に Button クラスを保持せず、入力イベントのみ接続するシンプルな Widget を新規に実装
  • ホールドの進捗が完了したときに、Click イベントが呼ばれる
    • → シンプルに Complete イベントとして呼ぶように変更
  • ホールド時間が InputAction の設定固定であり、状況に応じた上書きができない
    • → Widget 上から上書きできるような Override 設定を追加
      • (ただし、この実装は Private なヘッダーファイルの参照が必要だったので利用は自己責任)

実装過程

せっかくなので今回作ったもののコードは参考用として github にプラグインとしてあげておいた。
(たぶん、保守はしないと思う・・・)

GitHub - negimochi/CommonUIExtension: UE5 - Common UI Extension Plugin (Sample)
UE5 - Common UI Extension Plugin (Sample). Contribute to negimochi/CommonUIExtension development by creating an account on GitHub.

Dependency Module の設定

今回は CommonUI のプラグインの UCommonUserWidget クラスを継承して作成する。
結果的には、ビルドするには以下のモジュールが必要だった。(GameplayTags は正直どこに作用)

  • CommonUI
  • UMG
  • CommonInput
  • GameplayTags
		PublicDependencyModuleNames.AddRange(
			new string[]
			{
				"Core",
				// ... add other public dependencies that you statically link with here ...
				"CommonUI",
			}
			);			
		
		PrivateDependencyModuleNames.AddRange(
			new string[]
			{
				"CoreUObject",
				"Engine",
				"Slate",
				"SlateCore",
				// ... add private dependencies that you statically link with here ...	
				"UMG",
				"GameplayTags",
				"CommonInput",
			}
			);

今回はプラグイン化しちゃったので、プラグインの Build.cs に記述しているが、直接プロジェクトにソース追加する場合は、プロジェクト側の Build.cs に似たような Module の追加が必要になる。

UInputBindingActionWidget

UCommonUserWidget を継承して、UCommonAcitonWidget を取り扱う UInputBindingActionWidget を作成する。
基本的には、UCommonButtonBase から、ボタン機能由来の余計な操作をすべて外しつつ、ホールド機能だけそのまま持ってきているだけ。

以下、需要なメソッドだけ解説。

BindTriggeringInputAction メソッド

このクラスにおいて最も重要な CommonUI 管理の入力イベントへのバインドは、 BindTriggeringIntputAction メソッドでおこなっている。
以下のように、UCommonUserWidget::RegisterUIActionBinding メソッドを使用すると、入力イベントに対してバインドが可能。

void UInputBindingActionWidget::BindTriggeringInputAction()
{
	if (TriggeringInputAction.IsNull())
	{
		return;
	}

	if (!TriggeringBindingHandle.IsValid())
	{
		FBindUIActionArgs bindUIActionArgs(TriggeringInputAction, false, 
									FSimpleDelegate::CreateUObject(this, &UInputBindingActionWidget::NativeOnActionComplete));
		bindUIActionArgs.OnHoldActionProgressed.BindUObject(this, &UInputBindingActionWidget::NativeOnActionProgress);
		bindUIActionArgs.bIsPersistent = bIsPersistentBinding;
		bindUIActionArgs.InputMode = InputModeOverride;

		TriggeringBindingHandle = RegisterUIActionBinding(bindUIActionArgs);
	}
}

ここで、FBindUIActionArgs は、RegisterUIActionBinding に必要な引数で、Delegate の設定などをおこなうことができる。
今回の場合は、

  • 完了イベント: NativeOnActionComplete
    • BP 上では OnActionComplete イベント
  • ホールド中の Progress イベント: NativeOnActionProgress
    • BP 上では OnActionProgress イベント

というようにそれぞれイベントを登録している。

bIsPersistent は前回も解説したように、入力ルーティングを無視した永続化されたバインド設定を意味する。

InputMode に関しては、現状の実装だと、C++ で UCommonActivatableWidget を継承して使用しない限りは無用なオプション。LyraStarterGame サンプルで一部使用されている。

である。

UpdateInputActionWidgetVisibility メソッド

UCommonButtonBase では、キーボード入力だった場合に非表示にするオプション(bHideInputActionWithKeyboard) があったため同じように実装しておく。

void UInputBindingActionWidget::UpdateInputActionWidgetVisibility()
{
	if (InputActionWidget)
	{
		bool bHidden = false;

		auto* CommonInputSubsystem = GetInputSubsystem();

		if (CommonInputSubsystem != nullptr) 
		{
			bHidden = bHideInputActionWithKeyboard && CommonInputSubsystem->GetCurrentInputType() != ECommonInputType::Gamepad;
		}

		InputActionWidget->SetHidden(bHidden);
	}
}

UCommonUserWidget を継承していると、CommonInputSubsystem から入力の状態を簡単に知ることができる。

HoldTime を強制的に上書きするオプション (Private ヘッダー使用)

HoldTime を動的に変更するには、CommonUI の Private フォルダ以下にある Input/UIActionRouterTypes.h にアクセスする必要がある。
通常、Private フォルダ以下のヘッダーファイルはインクルード不可だが、Build.cs に小細工をすれば無理やりできないことはない。

		// HoldTime の上書き実装を行うため、
		// 無理やり CommonUI の Private ヘッダーを参照可能にする設定
		PublicDefinitions.Add("COMMON_UI_PRIVATE_ACCESS");

		if (PublicDefinitions.Contains("COMMON_UI_PRIVATE_ACCESS"))
        {
			string engine_path = Path.GetFullPath(Target.RelativeEnginePath);
			string common_ui_path = engine_path + "Plugins/Experimental/CommonUI/Source/CommonUI/";
			PrivateIncludePaths.Add(common_ui_path + "Private");
		}

念のため、”COMMON_UI_PRIVATE_ACCESS” という名前で define を設定した。
PublicDefinitions.Add("COMMON_UI_PRIVATE_ACCESS"); 自体をコメントアウトしてビルドすると、この機能自体を無効化できるようにしておく。

UInputBindingActionWidget では、以下のコードで HoldTime の上書きを行っている。
FUIActionBinding::FindBinding でハンドルからバインドの実体を取得して、ホールドのマッピング情報を無理やり上書きする。

void UInputBindingActionWidget::OverwriteHoldTime()
{
#ifdef COMMON_UI_PRIVATE_ACCESS
	if (TriggeringBindingHandle.IsValid())
	{
		if (auto binding = FUIActionBinding::FindBinding(TriggeringBindingHandle))
		{
			for (FUIActionKeyMapping& holdMapping : binding->HoldMappings)
			{
				holdMapping.HoldTime = HoldTimeOverride;
			}
		}
	}
#endif // COMMON_UI_PRIVATE_ACCESS
}

HoldTime の変更インタフェースとして、プロパティから Hold Time Override で設定、または、Set Hold Time のノードを用意してみる。

Hold Time Override プロパティ
Set Hold Time ノード

なお、Set Hold Time に関しては、変更時にホールド中の場合だった場合には失敗扱いとした。

サンプルアセット

今回作った UInputBindingActionWidget の利用例として、UInputBindingActionWidget を継承したクラスを2つ用意しておく。

WBP_SimpleInputBindingActionWidget
CommonActionWidget が1つだけ置かれたシンプルな Widget
WBP_DisplayNameInputBindingActionWidget
CommonActionWidget と InputAction 名を表示する Widget

完成

できたー。

Set Hold Time ノードの確認も兼ねて Hold Time を変更しつつ確認できるようにした。
X, Y を同時押しているように、Y のホールド時間が上書きされてることがわかる。

コメント