🐥

[UEFN][Verse]UIをマルチプレイヤー対応する

2023/10/06に公開

はじめに



UEFNでUIを作成しマルチプレイヤーに対応させるには、通常のプラットフォームとは少し違った考え方が必要になります
今回はUIをマルチプレイヤーに対応させる方法について紹介します

こちらの島は公開されているので動作を確認したい方は遊んでみてください
0596-9617-1560

コードだけ必要な方は完成品まで飛んでください

参考にしたコード


using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Fortnite.com/UI }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }

height_display := class(creative_device):

    @editable
    GroundLevelOffset: float = 0.0

    @editable
    Text:  string = "Height:  "

    @editable
    TextPosition: text_position = text_position.UpperCenter

    var DisplayMap: [player]?height_widget = map{}

    OnBegin<override>()<suspends>:void=
        
        Sleep(0.0)
        CheckForPlayers()

    CheckForPlayers()<suspends>: void =
        loop:
            Sleep(1.0)
            AllPlayers := GetPlayspace().GetPlayers()
            for (Player : AllPlayers):
                HeightWidgetInstance := height_widget{VerseDevice := Self, InstancePlayer := Player}
                PlayerHeight := GetHeight(Player)
                if (not DisplayMap[Player], set DisplayMap[Player] = option{HeightWidgetInstance}):
                    HeightWidgetInstance.CreateUIForPlayer(PlayerHeight)
                    if (Agent := agent[Player]):
                        HeightWidgetInstance.ShowUI(Agent)

    GetHeight(Player: player): int =
        if (FortCharacter := Player.GetFortCharacter[]):
            PlayerLocation := FortCharacter.GetTransform().Translation
            PlayerHeight := PlayerLocation.Z + GroundLevelOffset*100.0
            if (HeightInt := Floor[PlayerHeight/100.00]):
                return HeightInt

        return 0

height_widget := class():

    var VerseDevice : height_display = height_display{}
    InstancePlayer: player

    HeightText<localizes>(Text: string, Height: int) : message = "{Text} {Height}"
    HeightWidget : button_quiet = button_quiet{}

    var UIPerPlayer : [player]?canvas = map{}

    var XMinPos: float = 0.4
    var XMaxPos: float = 0.6
    var YMinPos: float = 0.0
    var YMaxPos: float = 0.0

    AdjustTextPos(): void =
        case (VerseDevice.TextPosition):

            text_position.LowerLeft =>
                set XMinPos = 0.0
                set XMaxPos = 0.2
                set YMinPos = 0.7
                set YMaxPos = 0.0

            text_position.LowerRight =>
                set XMinPos = 0.6
                set XMaxPos = 0.8
                set YMinPos = 0.7
                set YMaxPos = 0.0

            text_position.BottomCenter =>
                set XMinPos = 0.4
                set XMaxPos = 0.6
                set YMinPos = 0.7
                set YMaxPos = 0.0

            _ =>

    CreateUIForPlayer(Height: int):canvas=

        AdjustTextPos()
        
        HeightWidget.SetText(HeightText(VerseDevice.Text, Height))
        MyCanvas : canvas = canvas:
            Slots := array:
                canvas_slot:
                    Anchors := anchors{Minimum := vector2{X := XMinPos, Y := YMinPos}, Maximum := vector2{X := XMaxPos, Y := YMaxPos}}
                    Offsets := margin{Top := 100.0, Left := 0.0, Right := 0.0, Bottom := 0.0}
                    Alignment := vector2{X := 0.5, Y := 0.5}
                    SizeToContent := false
                    Widget := HeightWidget

    ShowUI(Agent: agent): void =
        if (Player := player[Agent], PlayerUI := GetPlayerUI[Player]):
            if (MyUI := UIPerPlayer[Player]?):
                PlayerUI.RemoveWidget(MyUI)
                if {set UIPerPlayer[Player] = false}
            else:
                NewUI := CreateUIForPlayer(0)
                PlayerUI.AddWidget(NewUI, player_ui_slot{InputMode := ui_input_mode.None})
                if (set UIPerPlayer[Player] = option{NewUI}) {}
                spawn:
                    UpdateHeight()

    UpdateHeight()<suspends>: void =
        loop:
            Sleep(0.1)
            PlayerHeight := VerseDevice.GetHeight(InstancePlayer)
            HeightWidget.SetText(HeightText(VerseDevice.Text, PlayerHeight))

text_position := enum:

    UpperCenter
    BottomCenter
    LowerRight
    LowerLeft

https://dev.epicgames.com/community/snippets/rmmj/fortnite-height-meter-for-onlyup-style-maps

こちらのコードはPlayerの高さを画面上部に表示するVerseコードになります
このコードそのままでも動作に問題は無いのですが、creative_deviceheight_widgetが相互参照しておりコードとしてはイマイチです
リファクタしながらコードを読み解いていきます

完成品


using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Fortnite.com/UI }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
 
height_display := class(creative_device):
 
    @editable
    GroundLevelOffset: float = 0.0
 
    @editable
    Text:  string = "Height:  "
 
    #PlayerをKeyにして、HeightWidgetをValueにしたMap
    var DisplayMap: [player]?height_widget = map{}
 
    #Deviceの起動
    OnBegin<override>()<suspends>:void=
        Sleep(0.0)
        CheckForPlayers()
 
    #Player情報を1秒周期でチェックする
    CheckForPlayers()<suspends>: void =
        loop:
            Sleep(1.0)
            AllPlayers := GetPlayspace().GetPlayers()
            for (Player : AllPlayers):
                #各Playerに対して、HeightWidgetを作成する
                HeightWidgetInstance := height_widget{InstancePlayer := Player, BaseText := Text, Offset := GroundLevelOffset}
                #DisplayMapに該当のPlayerが存在しない場合、HeightWidgetを作成する
                if (not DisplayMap[Player], set DisplayMap[Player] = option{HeightWidgetInstance}):
                    if (Agent := agent[Player]):
                        HeightWidgetInstance.ShowUI(Agent)
 

 
height_widget := class():
    InstancePlayer: player
    BaseText : string
    Offset : float

    HeightText<localizes>(Text: string, Height: int) : message = "{Text} {Height}"
    HeightWidget : button_quiet = button_quiet{}

    #PlayerをKeyにして、CanvasをValueにしたMap
    var UIPerPlayer : [player]?canvas = map{}
 
    #PlayerのUIを作成する.つまりCanvasとWidgetを作成する
    CreateUIForPlayer(Height: int):canvas=
        Print("CreateUIForPlayer{Height}")
        HeightWidget.SetText(HeightText(BaseText, Height))
        XMinPos: float = 0.4
        XMaxPos: float = 0.6
        YMinPos: float = 0.0
        YMaxPos: float = 0.0
        MyCanvas : canvas = canvas:
            Slots := array:
                canvas_slot:
                    Anchors := anchors{Minimum := vector2{X := XMinPos, Y := YMinPos}, Maximum := vector2{X := XMaxPos, Y := YMaxPos}}
                    Offsets := margin{Top := 100.0, Left := 0.0, Right := 0.0, Bottom := 0.0}
                    Alignment := vector2{X := 0.5, Y := 0.5}
                    SizeToContent := false
                    Widget := HeightWidget
 
    #UIの表示
    ShowUI(Agent: agent): void =
        Print("ShowUI")
        #AgentからPlayerUIを取得する
        if (Player := player[Agent], PlayerUI := GetPlayerUI[Player]):
            if (MyUI := UIPerPlayer[Player]?):
                Print("Removing UI")
                PlayerUI.RemoveWidget(MyUI)
                if {set UIPerPlayer[Player] = false}
            else:
                Print("Create UI")
                NewUI := CreateUIForPlayer(0)
                PlayerUI.AddWidget(NewUI, player_ui_slot{InputMode := ui_input_mode.None})
                if (set UIPerPlayer[Player] = option{NewUI}) {}
                spawn:
                    UpdateHeight()
 
    #高さを監視する
    UpdateHeight()<suspends>: void =
        loop:
            Sleep(0.1)
            PlayerHeight := GetHeight(InstancePlayer)
            HeightWidget.SetText(HeightText(BaseText, PlayerHeight))

    #Playerの高さを取得する
    GetHeight(Player: player): int =
        if (FortCharacter := Player.GetFortCharacter[]):
            PlayerLocation := FortCharacter.GetTransform().Translation
            PlayerHeight := PlayerLocation.Z + Offset*100.0  #cm単位に変換
            if (HeightInt := Floor[PlayerHeight/100.00]):
                return HeightInt
    
        return 0

UI作成時に必要な情報をcreative_deviceから渡し、相互参照が起きないように改良しました

基本的なロジックとしてはCheckForPlayers()を1秒に1回実行しまだUIを作っていないプレイヤーを探します
もしUIを作っていないプレイヤーが居たらそのプレイヤーにUIを作成します。

UIを作成した後にheight_widget内で非同期なUpdateHeight()を実行し0.1秒ごとにプレイヤーの位置を監視してUIを更新しています

終わりに

このコードを改造することでプレイヤー情報を監視しながらUIを更新することができます
是非役立ててください

参考

Height Meter for OnlyUp style maps
https://dev.epicgames.com/community/snippets/rmmj/fortnite-height-meter-for-onlyup-style-maps

[UEFN][Verse] UIにImage/Textureを貼る実装。これからVerseやっていきな人へ
https://qiita.com/iwaken71/items/c118737da5cb0ff38f54

Discussion