【UEFN】ボタンに反応するUIをVerseで作成する(Verseの学習⑨)
VerseのUI機能
VerseのUI機能は現状で決して充実しているとは言えず、今後のアップデートが期待されている項目の一つです。 今後、機能拡張があるとはいえ、基本的には現状ある機能の流れを踏襲した形になると思われ、現状の機能を理解しておくことは、今後の開発の大きな手助けになるはずです。
(また、UMG ViewModelの機能が、24.20までは使用できていたのですが、記事作成時点の24.40バージョンでは、バグにより使用できず、Verseなしで、ボタンを持ったGUIを独自にカスタムすることが現状できなくなっているように思われます)
EPIC本家のページでもUIに関するVerseのドキュメントはありますが、ボタンとの連携といった部分はないようでしたので、今回は、ボタンを持ったPopupDialogとしての使用方法を中心に説明していきたいと思います。
今回開発するもの
- ゲーム上のボタンデバイスを押した際に、PopUpDialogでUIを表示する
- PopUpされたUIには、上部のメッセージと、2つのボタンがある
- ボタンを押した際に、そのボタンに応じた処理を実行する
簡単なテキストをVerseから表示するプログラム
まず、最初に、テキストが表示されるまでの部分を組んでみます。
using { /Fortnite.com/Devices } using { /Fortnite.com/Characters } using { /Fortnite.com/UI } using { /Verse.org/Simulation } using { /Verse.org/Colors } using { /UnrealEngine.com/Temporary/Diagnostics } using { /UnrealEngine.com/Temporary/UI } using { /UnrealEngine.com/Temporary/SpatialMath } using { /Fortnite.com/Devices/CreativeAnimation } StringToMessage<localizes>(value:string)<computes> : message = "{value}" fruit_select_ui_device := class(creative_device): @editable _FruitSelectButton : button_device = button_device{} OnBegin<override>()<suspends>:void= _FruitSelectButton.InteractedWithEvent.Subscribe(OnInteractedWithFruitsSelectButton) OnInteractedWithFruitsSelectButton(Agent : agent) : void= if: Player := player[Agent] PlayerUI := GetPlayerUI[Player] then: FruitWidget := canvas: Slots := array: canvas_slot: Widget := text_block{DefaultText := StringToMessage("Hello World!")} PlayerUI.AddWidget(FruitWidget) return
ポイントとしては、この部分です。
文字列をMessage型に変換するための簡単な関数を準備しています。
StringToMessage<localizes>(value:string)<computes> : message = "{value}"
文字列のままでは画面に表示することができないため、こうしてmessage型に変換することが必要となります。
Verseで、UIを作成しようとすると非常によく使うコードですので、この書き方は覚えておいた方がいいかと思います。
Widgetの形でUIパーツをまず作成し、ボタンに触ったプレイヤーのUIに作成したWidgetを追加するという流れになります。
canvas_slotで区切られた部分がtextのパーツ部分です。このようにslotと呼ばれる単位でパーツを追加していきます。
canvas_slot:
Widget := text_block{DefaultText := StringToMessage("Hello World!")}
VerseをビルドしてできるCreativeDeviceに、適当なボタンを割り付け、実行してみます。
ボタンを押した際に、画面左上に”Hello, Worldと表示されれば正解です。
ボタン2個とテキスト1個を表示するプログラム
では、次にボタンを2個追加し、レイアウトも調整します。
OnInteractedWithFruitsSelectButton_step01(Agent : agent) : void= if: Player := player[Agent] PlayerUI := GetPlayerUI[Player] then: FruitWidget := canvas: Slots := array: canvas_slot: Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}} Offsets := margin{Left := -175.0, Top := -50.0, Right := 350.0, Bottom := 50.0} Alignment := vector2{X := 0.0, Y := 0.0} SizeToContent := false Widget := text_block: DefaultText := StringToMessage("Which Do You Like?") DefaultTextColor := NamedColors.Black DefaultJustification := text_justification.Center canvas_slot: Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}} Offsets := margin{Left := -300.0, Top := 35.0, Right := 280.0, Bottom := 130.0} Alignment := vector2{X := 0.0, Y := 0.0} SizeToContent := false Widget := button_loud{DefaultText := StringToMessage("Ringo")} canvas_slot: Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}} Offsets := margin{Left := 20.0, Top := 35.0, Right := 280.0, Bottom := 130.0} Alignment := vector2{X := 0.0, Y := 0.0} SizeToContent := false Widget := button_loud{DefaultText := StringToMessage("Banana")} InputMode := player_ui_slot: InputMode := ui_input_mode.All PlayerUI.AddWidget(FruitWidget, InputMode) return
テキスト1つとボタン2つになったため、canvas_slotの部分が3つに増えています。
ボタンの部分は以下のようになっており、様々な調整項目がセットされています。
この部分を例にとり、解説していきます。
canvas_slot: Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}} Offsets := margin{Left := -300.0, Top := 35.0, Right := 280.0, Bottom := 130.0} Alignment := vector2{X := 0.0, Y := 0.0} SizeToContent := false Widget := button_loud{DefaultText := StringToMessage("Ringo")}
上記の部分のプロパティは、WidgetBlueprintで作成する場合の値と同一の値になるため、WidgetBlueprintを仮に作成して、レイアウトの調整はGUI上で実施すればデザインしやすいかと思います。
どういったことか説明するために、WidgetBlueprintを一つ作成し、そこにCanvasとボタンを一つ配置して説明していきます。
Verse上のコードと、WidgetBlueprintの対応関係はこのようになっています。
このような形で、WidgetBlueprint上で、レイアウトを行って、その値をVerseコードに持っていけば、スムーズにレイアウトすることができます。
コードを実行し、ボタンを押した際に、以下のように表示されれば、正常に動作しています。
ただ、このままでは、ボタンを押すことができません。ここで、ボタンを押せるようにさらにプログラムを改修していきます。
ボタンを押すとメッセージを表示するプログラム完成版
2つのパーツに分けて解説していきます。 まず、前半部分を以下のように変えます。
fruit_select_ui_device := class(creative_device): @editable _FruitSelectButton : button_device = button_device{} _RingoButton : button_loud = button_loud{} _BananaButton : button_loud = button_loud{} OnBegin<override>()<suspends>:void= _RingoButton.SetText(StringToMessage("Ringo")) _RingoButton.OnClick().Subscribe(OnRingoButtonClicked) _BananaButton.SetText(StringToMessage("Banana")) _BananaButton.OnClick().Subscribe(OnBananaButtonClicked) _FruitSelectButton.InteractedWithEvent.Subscribe(OnInteractedWithFruitsSelectButton)
RingoButtonとBananaButtonをまず、事前に準備しています。
そのボタンを押したときの動作としてOnRingoButtonClicked関数と、OnBananaButtonClicked関数を登録しています。
次に後半部分です。
## map var _FruitWidgetMap : [player]widget = map{} OnInteractedWithFruitsSelectButton(Agent : agent) : void= if: Player := player[Agent] PlayerUI := GetPlayerUI[Player] then: FruitWidget := canvas: Slots := array: canvas_slot: Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}} Offsets := margin{Left := -175.0, Top := -250.0, Right := 350.0, Bottom := 50.0} Alignment := vector2{X := 0.0, Y := 0.0} SizeToContent := false Widget := text_block: DefaultText := StringToMessage("Which Do You Like?") DefaultTextColor := NamedColors.Black DefaultJustification := text_justification.Center canvas_slot: Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}} Offsets := margin{Left := -300.0, Top := -60.0, Right := 280.0, Bottom := 130.0} Alignment := vector2{X := 0.0, Y := 0.0} SizeToContent := false Widget := _RingoButton canvas_slot: Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}} Offsets := margin{Left := 20.0, Top := -60.0, Right := 280.0, Bottom := 130.0} Alignment := vector2{X := 0.0, Y := 0.0} SizeToContent := false Widget := _BananaButton InputMode := player_ui_slot: InputMode := ui_input_mode.All PlayerUI.AddWidget(FruitWidget, InputMode) ### save widget info if: set _FruitWidgetMap[Player] = FruitWidget OnRingoButtonClicked(WidgetMessage : widget_message) : void= HideWidget(WidgetMessage.Player) ### Process for Ringo Print("Ringo") return OnBananaButtonClicked(WidgetMessage : widget_message) : void= HideWidget(WidgetMessage.Player) ### Process for Banana Print("Banana") return HideWidget(Player : player) : void = if: PlayerUI := GetPlayerUI[Player] FruitWidget := _FruitWidgetMap[Player] then: PlayerUI.RemoveWidget(FruitWidget) return
Widgetはボタンを押したプレイヤーにのみ、表示する必要があります。
そのため、ボタンを押し、Widgetが表示されたプレイヤーがどのWidgetを保持しているのか、保存しておく必要があります。そのため、これを覚えておくための変数を準備しています。
var _FruitWidgetMap : [player]widget = map{}
canvas_slot登録時は事前に作成しておいたボタンを登録するように変えています。
canvas_slot: (中略) Widget := _RingoButton
ボタンが入力を受け取れるように、InputModeを変更して登録しています。
InputMode := player_ui_slot: InputMode := ui_input_mode.All PlayerUI.AddWidget(FruitWidget, InputMode)
ボタンを押したプレイヤーと、Widgetの関係を記録しておきます。
if: set _FruitWidgetMap[Player] = FruitWidget
ボタンを押した際には、Widgetをプレイヤーから削除しています。
HideWidget(Player : player) : void = if: PlayerUI := GetPlayerUI[Player] FruitWidget := _FruitWidgetMap[Player] then: PlayerUI.RemoveWidget(FruitWidget) return
あとは、適宜、ボタンを押した際の処理を、OnRingoButtonClicked関数とOnBananaButtonClicked関数の中に実装していけば完成となります。
自分の作例では、リンゴとバナナを出現させる処理を書いています。
まとめ
今回は、VerseからUIを作成する方法について解説しました。Verseからは画像が使用できず、まだまだ機能として十分とは言えませんが、今後、UI周りは大きく改善していくと思われ、一度、現状でできることを確認しておくのがいいのではないでしょうか。
やや、後半、コードの難易度があがってしまっており、あまりに記事が長くなってしまったため、そこを駆け足で説明してしまっています。
説明が足りていない部分もあるかと思いますので、わからない点があれば、ご質問ください。どういった点を説明すればいいのか、参考にさせていただければと思います。
UEFN関連の投稿がかなりたまってきたため、UEFN投稿のまとめページを作ってみました。
ぜひほかの記事も見ていただけると嬉しいです。
RingoGamesではUEFNに関するさまざまな情報を発信していきます。Twitterでお知らせしていきますので、よろしければ、Twitterのフォローをしていただけると幸いです。
Twitterはこちら