What is the proper way to do UI?

Ok so for UI I have been making custom UMG widgets using the tracker and stat creator device view models, and placing those widgets into the devices custom UI. This works for now but I’m running into the issue where sometimes it doesn’t look right, or there’s too many trackers at once so they don’t render, etc.

I am thinking about converting to a fully UMG UI. Where basically I would get the values from those devices with verse, then update the UI. I am planning on placing smaller widgets into one singular parent widget that I constantly update with verse. Is this the right idea?

Also side question, for updating UIs is it better to just Add the UI to the player each time its updated, or remove their UI then readd it?

To get values you can use:

@editable Tracker : tracker_device = tracker_device{}
@editable StatCreator : stat_creator_device = stat_creator_device{}


# Later in the code to get values
if: 
    TrackerAgentValue := Tracker.GetValue[Agent] # for each player
    TrackerGlobalValue :=Tracker.GetValue[] # global value ( all players )

    StatCreatorAgentValue := StatCreator.GetValue[Agent] # for each player
    StatCreatorGlobalValue := StatCreator.GetValueForMatch[] # global value
then:
    # use it 

To use them in UMG, create widget first in content browser, add verse variable to use values from verse.
For example, lets say widget name is TestWidgetUMG and verse variable name is WidgetValue:

# if your widget is located in UI folder for example, then it will be
# var UMGWidget : UI.TestWidgetUMG = UI.TestWidgetUMG{}
var UMGWidget : TestWidgetUMG = TestWidgetUMG{} # 

To update the widgets you don’t need to add and remove them everytime you want to update.

In my opinion, best way is to create a class for each type of widget:

tracker_widget<public> := class:
    @editable Tracker : tracker_device = tracker_device{}

    var TrackerValue : int = 0

    var PlayersMap<private> : [player]TestWidgetUMG = map{}

    AddPlayer<public>(Player:player):void=
        if:
            Player.IsActive[]
            not PlayersMap[Player]
            PlayerUI := GetPlayerUI[Player]
        then:
            NewWidget : TestWidgetUMG = TestWidgetUMG{}
            if. set PlayersMap[Player] = NewWidget 
            then:
                PlayerUI.AddWidget(NewWidget)
                UpdateWidget()

    RemovePlayer<public>(Player:player):void=
        if:
            Player.IsActive[]
            PlayerWidget := PlayersMap[Player]
            PlayerUI := GetPlayerUI[Player]
        then:
            PlayerUI.RemoveWidget(PlayerWidget)
            RemoveFromMap(Player)

    UpdateWidget<public>():void=
        for (Key -> PlayerWidget : PlayersMap):
            if. set PlayersWidget.WidgetValue = Tracker.GetValue[Key]

    RemoveFromMap(Player:player):void=
        if. PlayersMap[Player]
        then:
            var NewData:[player]TestWidgetUMG = map{}
            for (Key -> Value : PlayersMap, Key <> Player):
                if. set NewData[Key] = value
            set PlayersMap = NewData

To use it:

TestManager := class(creative_device):
    @editable TrackerWidget : tracker_widget = tracker_widget{}
    
    OnBegin<override>()<suspends>:void=
        for (Player : GetPlayspace().GetPlayers())
        do:
            TrackerWidget.AddPlayer(Player)

Now you can use TrackerWidget.UpdateWidget() each time you want to update, or use a loop.

1 Like

Thanks! This was a great help! On eclarification if you could. So in unreal engine UMG widgets are technically a class. So the reason you can’t just declare one instance like Widget1 : testumgwidget = testumgwidget{} At the top where the variables and editables go is because that literally just puts one instance of Widget1 into memory right?

Like if you do PlayerUI.AddWidget(Widget1). Everyone would be looking at the same widget in memory and the player map wouldn’t even matter correct? Because even if you do the player map everyone has the literally the same exact Widget1. Everyone is looking at the same memory address for their widget. (figuratively, idk how it’s actually stored but)

It’s kinda hard to test because I don’t have an easy way to login as another test user but from my understanding is adding a widget is pretty much adding instances of a class to players correct? I did forget to change the map to the individual instances of the widgets so it wasn’t updating correctly which makes it feel like that’s because I was updating the global instance and not the individual ones for each player. (I originally just did the widget1 setup at the top because I thought UMG handled the separate instances automatically lol)

Not a big deal, the code works and everything great but was just trying to wrap my head around it all.

Each widget instance can only be assigned to one player. If you loop over all players and assign the same widget instance, only the last player will have the widget on their screen. The system was probably designed like that, because UI is typically customized per player. Even if you just want to display the same thing, players cannot share the same instance.

1 Like

Gotcha yeah I didn’t know that so i guess my UIs have just been broken forever lol.

Is there anywhere in the docs that says this? I’ve read through the examples but I guess it doesn’t explicitly say it? Like I get why it works that way and it makes sense with how instances and everything work but, how would I know that initially. Just trial and error?

Not sure about the reason, but I can guess that due to that widgets can access players name, skin icon, squad/team info etc. To display this info every widget has to be “binded” to only one player exclusively. For example if you enable streamer mode in game, then you will see your name in UI normally, but players from other teams will see Anonymous[xxx] instead of your name.

2 Likes

I didn’t go through all documentation pages, but this page hints at it. I feel like we need a FAQ page for each system. I learned about this from the community on Discord.

1 Like