Common UI : Common Button Base を利用したホールド表現

UE5

前回は、UE5 : CommonUI プラグラインの中でも比較的簡単に利用可能な Common Acgtion Widget を使用して、入力デバイスによるボタンの表示切替えを行った。

今回は CommonUI で以下のようなホールド表現を実装してみる。

ボタン:ホールド進行中
ボタン:ホールド完了時

今回のようなホールド表現を CommonUI の機能で実装したい場合、Common Action Widget だけではなく Common Button Base を利用する必要がある。

実際には、Common Action Widget だけでも実装は不可能ではないが、C++ 側で入力処理をつなげる実装が必要が出てくる。
今回はあえて C++ は利用せず、 Common Button Base を使用して BP のみで対応を検討してみる。

Common Button Base を BP 継承したボタンを作成

Common Button Base とは、ボタンの基本的なイベント (Selected/Deselected, Hovered/Unhovered, Clicked, DoubleClicked, …) を管理できるようにした C++ クラスである。

Common Button Base には、Input Action を割り当てることができる。割り当てた Input Action に登録されたキーが入力された際、各種ボタン用イベントが発生するようにロジックが組まれている。

今回の目的のホールド表現は、ホールド中の入力をイベント通知として受け取って処理する必要があるので、Common Button Base を使用することになる。

Input Action にホールド用の設定

前準備として、ホールド用の Input Action の設定を行う。

ホールドの設定を行う場合は、Action Requires Hold を True にする。その下のプロパティ、Hold Time でホールドする時間を設定することができる。

Action Requires Hold = True とした場合、Input Action の名前は、Display Name ではなく Hold Display Name が採用されるので注意すること。

Common Button Base 継承 BP を作成

Common Button Base を継承した BP を作成する。
通常利用する際は、もう一段 C++ で継承をしてから継承 BP を作成することを推奨するが、あえて今回は直接継承でトライしてみる。
(Lyra Starter Game サンプルでも、C++ で継承して独自のボタン実装が行われているので、参考にしてみるとよい)

Common Button Base の階層の中に CommonActionWidget を置く。
このとき、CommonActionWidget の名前を “InputActionWidget” にすること。

“InputActionWidget” と名前をつける必要があるのは、CommonButtonBase の C++ 側では、 CommonActionWidget が BindWidget メタデータ指定で定義されているため。
BindWidget メタデータは、変数名と同じ UMG デザイナー上に配置された UI 要素を C++ 側で参照・操作するための仕組み。

	UPROPERTY(BlueprintReadOnly, Category = Input, meta = (BindWidget, OptionalWidget = true, AllowPrivateAccess = true))
	UCommonActionWidget* InputActionWidget;

配置した CommonActionWidget は、基本的に C++ 側で自動で管理される。
前回、 CommonActionWidget のプロパティには InputAction の設定をしたが、今回はスルーしておく(あとで CommonButtonBase 側で設定する)。

ホールド進行度表現のためのマテリアル設定

CommonActionWidget には、ホールド進行度表現用のマテリアルを設定する項目がある。せっかくなので使ってみる。

Lyra Starter Game サンプルにある M_UI_HollowCircularProgressBar_Base マテリアルが、ホールドの進行度を表現する例としてちょうどよさそうなので、今回これを利用することにする。

M_UI_HollowCircularProgressBar_Base の Percentage パラメータを 0 ~ 1 で変化させることで進行度の表現が可能
UI/Foundation/Materials/ 以下にある M_UI_HollowCircularProgressBar_Base を Migrate

M_UI_HollowCircularProgressBar_Base を自分のプロジェクトに Migrate してきたら、CommonActionWidget 以下のパラメータを設定する。

  • Progress Material Brush
    • ホールド表現用のブラシを設定する。
    • 今回の場合は、M_UI_HollowCircularProgressBar_Base
  • Progress Material Param
    • 進行度(0.0~1.0) を指定するマテリアルパラメータ名を設定する。
    • M_UI_HollowCircularProgressBar_Base の場合は、”Percentage” と設定

なお、Progress の他に Icon Rim Brush もある。CommonActionWidget では奥から、

  • Icon Rim Brush
  • Input Action で設定されたアイコン (UMGデザイナーでは白い矩形の部分)
  • Progress Material Brush

の順で描画される。つまり、Rim はボタンアイコンよりも後ろの装飾デザインをする際に利用するブラシということ。

進行度をマテリアル以外で表現したい場合

例えば、ProgressBar など、他の方法で進行度の表現等をしたい場合は、On Action Progress イベントを使用することで簡単に実装することができる。

また、Percent が 1.0 になった時、自動で On Clicked イベントが呼ばれる。
そのため、例えば、「ホールドが完了した瞬間に完了したことを示すアニメーションを再生する」といった使い方ができる。

動画の例で”OK!” の文字点滅は上のような BP を組んで実装した

Common Button Style の設定

ところで、CommonButtonBase では、階層内に1つでも UI 要素を配置すると、勝手に背景が灰色の表示になってしまうことに気づくと思う。

Horizontal Box しか配置してないにもかかわらず、後ろに灰色の背景が表示される

これは、CommonButtonBase の C++ 側でボタンの描画を行っているため起きる現象。実行時には、ボタンの状態に合わせて自動で背景色等が自動で描画される。

これらの状態に応じた、CommonButtonBase のボタンに関するビジュアルの設定は、CommonButtonStyle という専用クラスを継承した BP で設定を行う。

CommonButtonBase の スタイルカテゴリに Style プロパティがあるので、+ ボタンから CommonButtonStyle の作成。

プロパティがたくさん並んでいるが、プロパティ名を見れば大体想像がつく
(画像クリックで拡大)

Padding, Text の設定、選択時や Hover 時のサウンド設定も可能。また、選択時 (Selected)、無効時 (Disabled) などの状態に応じて、個別に見た目を変更ができるようにもなっている。

今回は、ボタンそのものとして使うというより、HUD で常時表示する UI として実装したいだけなので、Single Material にチェックを入れて Tint を Alpha = 0.0 にして透明な見た目にしておく。

自作したボタンを配置して入力をつなぐ

以上で CommonButtonBase によるボタンの作成が終わった。
ここからは、作ったボタンを Widget に配置し、そのボタンにおける入力設定を行う。
(ただし、今回の例では、ボタンとしての表示ではなく、 HUD 上のホールド表示として使用するため、選択状態などの細かい調整は行わない)

Viewport Client クラスを CommonGameViewportClient に変更

前回の記事では設定しなくても動作するので説明を省いたが、CommonUI 固有の入力イベントを駆動させるためには、プロジェクト設定で Viewport Client クラスを変更する必要がある。

Just a moment...

公式のクイックスタートにも書いてあるが、「プロジェクト設定 – 基本設定」にある Viewport Client クラスを CommonUI 専用の CommonGameViewportClient に変更しておく。

変更したら再起動しないと有効にならないので注意。

HUD に作成した Common Button Base を配置

作成したボタンを前回作った HUD に追加する。

このとき、配置された Common Button Base のインスタンスに、以下のパラメータ設定を行う。

ボタンに対して Input Action を設定

前回は Common Action Widget に直接 Input Action の設定をしていた。
が、今回のように、Common Button Base を使用する場合に関しては、 Common Button Base を配置したインスタンスに対して個別に Input Action の設定を行う。
こうすることで、配置したボタンごとに割り当てるキーを変更することができる。

詳細設定の Is Persistent Binding を True

Is Persistent Binding を True にすると、インスタンスが配置された段階で無条件に入力イベントがバインディングされる。

CommonUI では、Widget ごとに入力イベントのアクティベーションを管理する機能がある。
Is Persistent Binding は、そのアクティベーション管理を無視して入力イベントを常に発生させるフラグなので、多用すると本来の CommonUI の利便性が失われてしまう。使用する場合は注意。

完成

以上で設定が完了。後は、作った HUD を表示するだけ。

動作確認のため、On Action Progress イベントで上部の ProgressBar を更新、完了後の On Click イベントで “OK!” というテキストが点滅するアニメーションを再生

ここまで使ってみた所感

  • PlayerController などもいじることなく、CommonUI の機能のみで入力イベントが管理できる点はよい
    • Input Aciton に応じて、ボタンテクスチャのブラシ管理も同時に行える点も便利
  • ただし、ボタンのホールド時間に関しても Input Action として管理されてしまうのは必ずしも良いとは言えない
    • 細かく状況に応じてホールド時間を微調整したいといったケースで不便
    • 動的にホールド時間を変更したい場合は C++ 側で対応する必要がありそう
  • CommonButtonBase のスタイル機能は便利
    • 一通り必要なパラメータがそろってそうで良さげな印象を受けた
  • CommonButtonBase はやはり C++ の継承クラス挟んで C++ 側で機能調整できるようにしておく方が無難
    • BP のみだと微妙に手が届かない部分がある
    • 特に今回のような通常のボタンではないような使い方をする場合、マウスによるボタン機能のみ無効化して、CommonActionWidget だけ有効化するような使い方ができない
  • CommonButtonBase の Progress の完了時、OnClicked が呼ばれるのは不便
    • マウスによるクリックなのか、Progress の完了なのかが区別がつかない
    • そもそも、マウスでもホールド入力したい場合って考慮されてなさそう?

マウスによる Click 無効化に関する考察

今回のような HUD での使い方で、マウスによるクリックを阻止したい場合、マウス機能のみの無効化ができないことや OnClicked の仕様の問題で、BP だけだと上記のような回りくどい実装が必要になってしまった(そもそも、HUD での利用が、通常の CommonButtonBase の使い方ではないということだろう)

今回の検証で、今回の HUD 表示でホールド表現したい場合は、CommonButtonBase を使うのではなく、自前で C++ で組んだ方が無駄のない綺麗な実装になりそうだなぁと感じた。
CommonButtonBase::BindTriggeringInputActionToClick メソッドにて、CommonUI の管理する入力イベントにバインドしている。おそらく、これを実装した自前の Widget を作るのがよさそう。

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);
	}
}

コメント