前回、Common Button Base を使用して、Common Action Widget のホールド表現を行った。
が、Common Button Base はあくまでボタンのとしての機能を提供するものなので、HUD に表示したいだけの用途としては片手落ち感があった。
今回は、独自に C++ で HUD 表示向けのホールド表現が可能な Widget を CommonUI を使って自作してみる。
実装した内容
結論から。基本的には以下の問題を解決する実装を行うことができた。
- CommonButtonBase にしか入力イベントのバインドが実装されていないため、HUD 表示だけしたい場合に面倒なことになる問題
- → 内部に Button クラスを保持せず、入力イベントのみ接続するシンプルな Widget を新規に実装
- ホールドの進捗が完了したときに、Click イベントが呼ばれる
- → シンプルに Complete イベントとして呼ぶように変更
- ホールド時間が InputAction の設定固定であり、状況に応じた上書きができない
- → Widget 上から上書きできるような Override 設定を追加
- (ただし、この実装は Private なヘッダーファイルの参照が必要だったので利用は自己責任)
- → Widget 上から上書きできるような Override 設定を追加
実装過程
せっかくなので今回作ったもののコードは参考用として 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 のノードを用意してみる。
なお、Set Hold Time に関しては、変更時にホールド中の場合だった場合には失敗扱いとした。
サンプルアセット
今回作った UInputBindingActionWidget の利用例として、UInputBindingActionWidget を継承したクラスを2つ用意しておく。
完成
できたー。
Set Hold Time ノードの確認も兼ねて Hold Time を変更しつつ確認できるようにした。
X, Y を同時押しているように、Y のホールド時間が上書きされてることがわかる。
コメント