UE5 Common UI お試し ② デフォルトナビゲーション (Default Click Action)

UE5 Common UI お試し ① ボタンのアイコンを出し分ける - You are done!

上記の続きです。

前回は公式のクイックスタートをなぞる形でした。今回は機能別サンプルをもう少し眺めていきたいと思います。

コード

Release blog20221026 · dany1468/UE_CommonUISample · GitHub

blog20221026 でタグを打ってあります。

CommonButtonBase の InputActionWidget

前回ボタンの parent に指定した CommonButtonBase ですが、非常に多くの機能があるようです。その中に InputActionWidget というプロパティが存在します。

/**
    * Optionally bound widget for visualization behavior of an input action;
    * NOTE: If specified, will visualize according to the following algorithm:
    * If TriggeringInputAction is specified, visualize it else:
    * If TriggeredInputAction is specified, visualize it else:
    * Visualize the default click action while hovered
    */
UPROPERTY(BlueprintReadOnly, Category = Input, meta = (BindWidget, OptionalWidget = true, AllowPrivateAccess = true))
UCommonActionWidget* InputActionWidget;

前回の記事では以下のようにボタンの BP を記述しました。

一方で機能別サンプルの方では以下のように一見すると設定が足りないような内容になっています。(ボタンを構成する Widget 群は同じなのに関わらずです)

これには理由があり、機能別サンプルの方はボタンに配置している CommonActionWidgetInputActionWidget という名前に変更し、CommonBaseButtonInputActionWidget に設定する形にしているのです。そのためか、機能別サンプルの方ではグラフの方に CommonActionWidget の Variables が表示されません。
継承されている変数の方に入っています。

そして、この変数名 (InputActionWidget) に合わせることで以下の処理が実行されます。

void UCommonButtonBase::UpdateInputActionWidget()
{
    // Update the input action state of the input action widget contextually based on the current state of the button
    if (GetGameInstance() && InputActionWidget)
    {
        // Prefer visualizing the triggering input action before all else
        if (!TriggeringInputAction.IsNull())
        {
            InputActionWidget->SetInputAction(TriggeringInputAction);
        }
        // Fallback to visualizing the triggered input action, if it's available
        else if (!TriggeredInputAction.IsNull())
        {
            InputActionWidget->SetInputAction(TriggeredInputAction);
        }
        // 以下略
    }
}

つまり、CommonButtonBase 側に設定された TriggeringInputActionInputActionWidget にも設定されているということです。

前回のノードを見てもらうと分かる通り、SetInputAction を BP 側で設定する処理が C++ 側で行われていることが分かります。この UpdateActionWidgetActivate 時にも呼ばれるため、機能別サンプルのノードはあれだけで問題なく動作するということです。

自分のサンプルの方も変更

外部からボタンの文字列 (Text) を指定しているかどうかで分岐するようにしています。(TriggeringInputAction を指定しない場合にText を指定するという感じで)

加えて、InputActionWidget に設定を入れる UpdateInputActionWidget 関数が Pre Construct では呼び出されないので、Construct に変更しています。

デフォルトナビゲーション

デフォルトナビゲーションに関しては 3. デフォルトのナビゲーション アクションの設定 | Unreal Engine の共通 UI クイックスタート ガイド | Unreal Engine ドキュメント を前回スキップしました。

ただ、設定することは関してはクイックスタートの通りです。

ただ、今回はお試しでもあるので、Default Click Action に関しては、異なるキーを割り当てることにしました。

追加した TestGlobalForward 、キーボードの P と、ゲームパッドL を割り当て。

NavigationInputDataExampleDefault Click Action にそちらを割り当て。

これらの設定は以下の UCommonUIInputData のコードが対応します。

/* Derive from this class to store the Input data. It is referenced in the Common Input Settings, found in the project settings UI. */
UCLASS(Abstract, Blueprintable, ClassGroup = Input, meta = (Category = "Common Input"))
class COMMONINPUT_API UCommonUIInputData : public UObject
{
    GENERATED_BODY()

public:
    virtual bool NeedsLoadForServer() const override;

public:
    UPROPERTY(EditDefaultsOnly, Category = "Properties", meta = (RowType = CommonInputActionDataBase))
    FDataTableRowHandle DefaultClickAction;

    UPROPERTY(EditDefaultsOnly, Category = "Properties", meta = (RowType = CommonInputActionDataBase))
    FDataTableRowHandle DefaultBackAction;
};

また、これらを取得するコードは UCommonInputSettings にあります。

UCLASS(config = Game, defaultconfig)
class COMMONINPUT_API UCommonInputSettings : public UDeveloperSettings
{
    FDataTableRowHandle GetDefaultClickAction() const;
    FDataTableRowHandle GetDefaultBackAction() const;

Default Click Action の利用箇所

上記の GetDefaultClickAction を調べると、先程の UCommonButtonBase::UpdateInputActionWidget のみが該当しました。

void UCommonButtonBase::UpdateInputActionWidget()
{
    // Update the input action state of the input action widget contextually based on the current state of the button
    if (GetGameInstance() && InputActionWidget)
    {
        // Prefer visualizing the triggering input action before all else
        if (!TriggeringInputAction.IsNull())
        {
            InputActionWidget->SetInputAction(TriggeringInputAction);
        }
        // Fallback to visualizing the triggered input action, if it's available
        else if (!TriggeredInputAction.IsNull())
        {
            InputActionWidget->SetInputAction(TriggeredInputAction);
        }
        // Visualize the default click action when neither input action is bound and when the widget is hovered
        else if (bShouldUseFallbackDefaultInputAction)
        {
            FDataTableRowHandle HoverStateHandle;
            if (IsHovered())
            {
                // 💡 ここが該当箇所
                HoverStateHandle = ICommonInputModule::GetSettings().GetDefaultClickAction();
            }
            InputActionWidget->SetInputAction(HoverStateHandle);
        }
        else
        {
            FDataTableRowHandle EmptyStateHandle;
            InputActionWidget->SetInputAction(EmptyStateHandle);
        }

        UpdateInputActionWidgetVisibility();
    }
}

これは CommonButtonBase になるのですが、TriggeringInputAction が Null であり、かつ bShouldUseFallbackDefaultInputAction が True であることがまず前提条件になるようです。

前回の記事では Triggering Input Action に指定することでボタンのアイコン表示や入力への反応を設定しましたが、あえて未指定にします。 Should Use Fallback Default Input Action は前回も true にしていました。

つまり、対応する Input Action が存在せず、Fallback が有効になっている時に代わりに Input Action として設定されるのが Default Click Action ということになりそうです。

加えて IsHovered が条件になっているので分かる通り、ホバーされている場合のみ表示されます。

UI を作る

前回の WBP_Menu の右端に Default Click Action 用のボタンを足します。

先程も Input 部分は説明しましたが、Default Click Action 用のボタンの設定は以下のようにします。

ホバー無し ホバー時
Keyboard
Gamepad

入力には反応しない

ホバー時の表示としてはアイコンが表示されるのですが、P や Gamepad の L を押してもクリックイベントは反応しません。
機能別サンプルでこれが使われている箇所を見ても、おそらく特別なキーを割り当てたりはせず、Mouse & keyboard ではマウス、Gamepad ではデフォルトの確定&Confirm キーを指定するのだと思います。

以下は Construct から呼び出される関数になるのですが、TriggeringInputActionUCommonButtonBase に指定されていない場合には UCommonButtonBase::HandleButtonClicked が紐付けられないため、入力に反応できません。

void UCommonButtonBase::BindTriggeringInputActionToClick()
{
    if (TriggeringInputAction.IsNull() || !TriggeredInputAction.IsNull())
    {
        return;
    }

    if (!TriggeringBindingHandle.IsValid())
    {
        // 💡ここでイベントを紐づけないとクリックをハンドルできない
        FBindUIActionArgs BindArgs(TriggeringInputAction, false, FSimpleDelegate::CreateUObject(this, &UCommonButtonBase::HandleButtonClicked));
        BindArgs.OnHoldActionProgressed.BindUObject(this, &UCommonButtonBase::NativeOnActionProgress);
        BindArgs.bIsPersistent = bIsPersistentBinding;

        BindArgs.InputMode = InputModeOverride;
        
        TriggeringBindingHandle = RegisterUIActionBinding(BindArgs);
    }
}

まとめ

Default Back Action までまとめてやりたかったんですが、Default Click Action の入力が反応しないことに引っかかって時間がかかってしまいここで一旦打ち切りになりました 😅

Default Click Action に関しては、クイックスタート通りに、Keyboard の場合は無し、Gamepad の場合は Gamepad Face Button Bottom を指定しておくのが無難なのではないかと思います。(Switch 等は別だと思いますが)