【UEFN】VerseでUIを作ってみる

## 背景
最近の上位に出てきている島には大体オリジナルUIがあります。そこでどのように実装するのか知りたくなったので書き残しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692976/b5346f2b-c4a2-2222-684e-f643879719c5.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692976/192f44d5-5d44-3f75-2654-b4b1fb449aff.png)


## やること
プレイヤーごとにスコアを表示するオリジナルUIを表示させるようにしました。

### 動画
<iframe width="560" height="315" src="https://www.youtube.com/embed/3TIROIjB6VM?si=U4ZL3qwmw1gckUoC" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>


## 実装
### スコアが増えるトリガーの実装
トリガーとスコアマネージャーを配置
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692976/f0d12012-0479-b2a3-833d-98367b783f04.png)

スコアマネージャーとトリガーを結び付けてトリガーが押されたらスコアが追加されるようにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692976/97486b48-1025-c7c6-ef80-be1b37051d82.png)

### UI実装部分全体コード

**以下の記事を参考にしました。**

https://zenn.dev/tkada/articles/55dd2eacba8c90

https://qiita.com/iwaken71/items/c118737da5cb0ff38f54

#### player_widgetクラス
```
using { /Fortnite.com/UI }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Colors }
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/SpatialMath }

player_widget := class():

    score_manager:score_manager_device

    ScoreText<localizes>(Score: int) : message = "{Score}"
    score_widget : text_block = text_block{DefaultTextColor:= color{R:=1.0, G:=1.0, B:=1.0},DefaultJustification:=text_justification.Center}

    CreateWidget(Score : int) : canvas =
        score_widget.SetText(ScoreText(Score))
        MyCanvas : canvas = canvas:
            Slots := array:
                canvas_slot:
                    Anchors := anchors{Minimum := vector2{X := 0.0, Y := 0.5}, Maximum := vector2{X := 0.0, Y := 0.5}}
                    Offsets := margin{Top := 24.0, Left := 150.0, Right := 100.0}
                    Alignment := vector2{X := 0.0, Y := 0.0}
                    SizeToContent := false
                    ZOrder := {Z := 2}
                    Widget := score_widget

    ShowWidget(Agent : agent) : void =
        Print("ShowUI")
        #AgentからPlayerUIを取得する
        if (Player := player[Agent], PlayerUI := GetPlayerUI[Player]):
            Print("Create UI")
            NewUI := CreateWidget(0)
            PlayerUI.AddWidget(NewUI, player_ui_slot{InputMode := ui_input_mode.None})
            spawn {UpdateWidget(Agent)}

    UpdateWidget(Agent : agent)<suspends>: void =
        loop:
            Sleep(1.0)
            score := score_manager.GetCurrentScore(Agent)
            score_widget.SetText(ScoreText(score))
```
このクラスでUIにどのような要素をどのように配置するか指定しています。
```
 MyCanvas : canvas = canvas:
            Slots := array:
```
配列型で指定してるということでここに入れたいUIの要素とそれをどのような場所に入れるのか指定しています。UIをもう一つ追加したいなら以下のようになります。

```
MyCanvas : canvas = canvas:
            Slots := array:
                canvas_slot:
                    Anchors := anchors{Minimum := vector2{X := 0.0, Y := 0.5}, Maximum := vector2{X := 0.0, Y := 0.5}}
                    Offsets := margin{Top := 24.0, Left := 150.0, Right := 100.0}
                    Alignment := vector2{X := 0.0, Y := 0.0}
                    SizeToContent := false
                    ZOrder := {Z := 2}
                    Widget := score_widget
                canvas_slot:
                    Anchors := anchors{Minimum := vector2{X := 0.0, Y := 0.5}, Maximum := vector2{X := 0.0, Y := 0.5}}
                    Offsets := margin{Top := 20.0, Left := 150.0, Right := 100.0}
                    Alignment := vector2{X := 0.0, Y := 0.0}
                    SizeToContent := false
                    ZOrder := {Z := 2}
                    Widget := sample_widget
```

今回は以下のようなただのテキストブロックだけですがスライダーやボタンなどもUIに追加できたりします。またの機会に紹介します。
```
score_widget : text_block = text_block{DefaultTextColor:= color{R:=1.0, G:=1.0, B:=1.0},DefaultJustification:=text_justification.Center}
```
``UpdateWidget``ではUIに表示するスコアの更新をしています。一秒ごとにスコアを確認しそれをUIに代入している感じです。

#### player_widget_managerクラス
```
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

player_widget_manager := class(creative_device):

    @editable
    score_manager:score_manager_device=score_manager_device{}

    var PlayerModel: [player]?player_widget = map{}

    OnBegin<override>()<suspends>:void=
        spawn {CheckForPlayers()}

    CheckForPlayers()<suspends>: void =
        loop:
            Sleep(1.0)
            AllPlayers := GetPlayspace().GetPlayers()
            for (Player : AllPlayers):
                if (not PlayerModel[Player]):
                    if (Agent := agent[Player]):
                        PlayerWidgetInstance := player_widget { score_manager := score_manager }
                        if (set PlayerModel[Player] = option{PlayerWidgetInstance}):
                            PlayerWidgetInstance.ShowWidget(Agent)
                            spawn {PlayerWidgetInstance.UpdateWidget(Agent)}
```
このクラスはUIを生成するエントリーポイントを提供しています。``CheckForPlayers``という関数で現在同じセッションにいるすべてのプレイヤー情報を取得して``PlayerModel``という``map型``に登録しています。すでに``PlayerModel``に登録されUIを生成しているプレイヤーに重ねてUIを表示しないように``if (not PlayerModel[Player]):``で生成されないようになっています。

## まとめ
UIに関しては要素の位置調整がとても大変です。``margin``や``anchors``の数字を少しずつずらしてはフォートナイト起動の繰り返しになります（笑）このあたりもテンプレート化できそうなのでもっと調査していきたいと思います。

## 余談
この度UEFN/Verseに関するオープンコミュニティサーバーを建ち上げました。ちょっとでも興味があれば奮ってご参加くださいませ。

https://discord.gg/MehcNhyxdq