In my Verse UI canvas, any widgets that are defined outside of MakeCanvas() are removed from the first player’s screen when a second player presses a button device to display the UI canvas on their screen. I can’t just define all of my UI widgets inside of MakeCanvas(), because I need to reference some of the widgets for other functions.
Could someone explain to me how to assign variables like UI widgets to only the player who presses the button device to initiate their own canvas, instead of to everybody? Thanks!
This works great for widgets that I don’t need to reference in other functions, like my unchanging title text (WidgetTitle in my snippet below). However, I need to define my widgets for the changeable WidgetRow1 text and for the clickable WidgetUIButton outside of the MakeCanvas() because I them for HandleUIButtonClick() and UpdateText(). The UI widgets are not accessible to me for my functions if I trap them in MakeCanvas(). That’s why I defined WidgetRow1 and WidgetUIButton at the beginning of the script, as global variables. But now I see that I don’t want them global, I want them to apply per player.
In addition, since my variable currentTextPart is global, changes with “set currentTextPart” apply to all players, which is bad because I want it to only change for the player who clicks the UI button.
The only way I’ve been able to achieve this personally so far is putting the UI into a class and then creating a class object per player, which I store in a map using player as the key. Just as an example, if it helps, this is a snippet I’m doing that in: Height Meter for OnlyUp style maps | Uefn Code Snippet
Oof I think I’m out of my depth. I haven’t done much with maps. Would this tutorial help? I just went through it today actually and it showed how to make a UI class but I wasn’t sure how to combine it with my other script. Custom Countdown Timer
I’m not sure if that one will show on the screen for more than one player, honestly. If you ever want I’m totally willing to jump into a call at some point and go over maps with you if you are interested. I’m about out the door right now but I can explain a bit more when I’m back home later.
Hey Twin! I tried to integrate your OnlyUp script into my dialog script and I got partially somewhere but it’s still pretty wacky. My goal is a little different from yours because I have a UI button click. I made the class concrete so that I could use editable variables for it, hope that’s ok.
This is what I have so far. What happens in testing is that the canvas appears the first time on Button Device press, which is good. But then the button click gets weird because the sequence goes “Row 1 Part 1” then “Row 1 Part 3” then Part 1 again, then Part 3 again, then deletes the canvas. Instead of going Part 1, Part 2, Part 3, remove canvas. And after the canvas is gone the first time I can’t get it to come back again on button device press
What do you think is going on here?
using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Simulation }
ui_dialog_simple1 := class(creative_device):
@editable
ButtonUIAdd : button_device = button_device {}
@editable
Title : string = "Title"
@editable
TextRow1: []string = array:
"Row 1 Part 1"
"Row 1 Part 2"
"Row 1 Part 3"
var DisplayMap: [player]?dialog_widget = map{}
OnBegin<override>()<suspends>:void=
Print("UI Popup NextExit Device Began")
ButtonUIAdd.InteractedWithEvent.Subscribe(HandleButtonUIAddInteraction)
HandleButtonUIAddInteraction(Agent:agent):void=
Print("Button Device Interacted With")
AllPlayers := GetPlayspace().GetPlayers()
for (Player : AllPlayers):
DialogWidgetInstance := dialog_widget:
VerseDevice := Self
InstancePlayer := option{Player}
DialogTitle := Title
DialogRow1 := TextRow1
if (not DisplayMap[Player], set DisplayMap[Player] = option{DialogWidgetInstance}):
DialogWidgetInstance.CreateMyUI()
#if (Agent := agent[Player]):
var currentTextPart : int = 0
DialogWidgetInstance.AddCanvas(Agent)
DialogWidgetInstance.UpdateText(currentTextPart)
#===== Dialog Widget Class ==========================================================
dialog_widget := class<concrete>():
StringToMessage<localizes>(value:string) : message = "{value}"
var VerseDevice : ui_dialog_simple1 = ui_dialog_simple1{}
InstancePlayer: ?player = false ###
WidgetRow1:text_block = text_block {}
WidgetUIButton:button_regular = button_regular {}
var MaybeMyUIPerPlayer : [player]?canvas = map{}
var currentTextPart : int = 0
DialogTitle : string = ""
DialogRow1: []string = array{}
HandleUIButtonClick(Message: widget_message):void=
Print("UI Button Clicked")
if (currentTextPart = DialogRow1.Length):
RemoveCanvas(Message)
set currentTextPart = 0
else:
if (currentTextPart <= DialogRow1.Length - 1):
if (currentTextPart = DialogRow1.Length - 1):
UpdateText(currentTextPart)
else:
UpdateText(currentTextPart)
UpdateText(Index:int):void=
Print("Update Text")
if(currentStr := DialogRow1[Index]):
WidgetRow1.SetText(StringToMessage(currentStr))
set currentTextPart += 1
AddCanvas(Agent:agent):void=
Print("Add Canvas")
if (InPlayer := player[Agent], PlayerUI := GetPlayerUI[InPlayer]):
if (MyUI := MaybeMyUIPerPlayer[InPlayer]?):
PlayerUI.RemoveWidget(MyUI)
if (set MaybeMyUIPerPlayer[InPlayer] = false) {}
else:
NewUI := CreateMyUI()
PlayerUI.AddWidget(NewUI, player_ui_slot{InputMode := ui_input_mode.All})
if (set MaybeMyUIPerPlayer[InPlayer] = option{NewUI}) {}
CreateMyUI():canvas=
Print("Create UI")
WidgetUIButton.OnClick().Subscribe(HandleUIButtonClick)
WidgetTitle:text_block = text_block:
DefaultText := {StringToMessage(DialogTitle)}
NewCanvas := canvas:
Slots := array:
canvas_slot:
Anchors := anchors:
Minimum := vector2{X := 0.5, Y := 0.25}
Maximum := vector2{X := 0.5, Y := 0.25}
Alignment := vector2{X := 0.5, Y := 0.5}
SizeToContent := true
Widget := stack_box:
Orientation := orientation.Vertical
Slots := array:
stack_box_slot:
Widget := WidgetTitle
stack_box_slot:
Widget := WidgetRow1
stack_box_slot:
Widget := WidgetUIButton
return NewCanvas
RemoveCanvas(Message: widget_message):void=
if (PlayerUI := GetPlayerUI[Message.Player], MyUI := MaybeMyUIPerPlayer[Message.Player]?, SelectedButton := text_button_base[Message.Source]):
Print("Remove Canvas")
PlayerUI.RemoveWidget(MyUI)
if (set MaybeMyUIPerPlayer[Message.Player] = false) {}
Here’s my current fix that @Twin01 helped me with. It appears to work. I created a class called dialog_widget, and created a class object per player which is stored in a map using player as the key. Thanks so much, Twin!
I think there are probably more ways than one to accomplish multiplayer UI though. Has anyone else handled this differently? Does this seem like the best option?
using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Simulation }
ui_dialog_simple1 := class(creative_device):
@editable
ButtonUIAdd : button_device = button_device {}
@editable
Title : string = "Title"
@editable
TextRow1: []string = array:
"Row 1 Part 1"
"Row 1 Part 2"
"Row 1 Part 3"
var DisplayMap: [player]?dialog_widget = map{}
OnBegin<override>()<suspends>:void=
Print("UI Popup NextExit Device Began")
ButtonUIAdd.InteractedWithEvent.Subscribe(HandleButtonUIAddInteraction)
HandleButtonUIAddInteraction(Agent:agent):void=
Print("Button Device Interacted With")
if (Player := player[Agent]):
DialogWidgetInstance := dialog_widget:
VerseDevice := Self
InstancePlayer := option{Player}
DialogTitle := Title
DialogRow1 := TextRow1
if (set DisplayMap[Player] = option{DialogWidgetInstance}):
DialogWidgetInstance.Init()
DialogWidgetInstance.CreateMyUI()
DialogWidgetInstance.AddCanvas(Agent)
DialogWidgetInstance.UpdateText(DialogWidgetInstance.currentTextPart)
else:
Print("Error")
#===== Dialog Widget Class ==========================================================
dialog_widget := class<concrete>():
StringToMessage<localizes>(value:string) : message = "{value}"
var VerseDevice : ui_dialog_simple1 = ui_dialog_simple1{}
InstancePlayer: ?player = false ###
WidgetRow1:text_block = text_block {}
WidgetUIButton:button_regular = button_regular {}
var MaybeMyUIPerPlayer : [player]?canvas = map{}
var currentTextPart : int = 0
DialogTitle : string = ""
DialogRow1: []string = array{}
Init():void=
WidgetUIButton.OnClick().Subscribe(HandleUIButtonClick)
HandleUIButtonClick(Message: widget_message):void=
Print("UI Button Clicked")
if (currentTextPart = DialogRow1.Length):
RemoveCanvas(Message)
set currentTextPart = 0
else:
if (currentTextPart <= DialogRow1.Length - 1):
if (currentTextPart = DialogRow1.Length - 1):
UpdateText(currentTextPart)
else:
UpdateText(currentTextPart)
UpdateText(Index:int):void=
Print("Update Text")
if(currentStr := DialogRow1[Index]):
WidgetRow1.SetText(StringToMessage(currentStr))
set currentTextPart += 1
AddCanvas(Agent:agent):void=
Print("Add Canvas")
if (InPlayer := player[Agent], PlayerUI := GetPlayerUI[InPlayer]):
if (MyUI := MaybeMyUIPerPlayer[InPlayer]?):
PlayerUI.RemoveWidget(MyUI)
if (set MaybeMyUIPerPlayer[InPlayer] = false) {}
else:
NewUI := CreateMyUI()
PlayerUI.AddWidget(NewUI, player_ui_slot{InputMode := ui_input_mode.All})
if (set MaybeMyUIPerPlayer[InPlayer] = option{NewUI}) {}
CreateMyUI():canvas=
Print("Create UI")
Print("subscribe")
WidgetTitle:text_block = text_block:
DefaultText := {StringToMessage(DialogTitle)}
NewCanvas := canvas:
Slots := array:
canvas_slot:
Anchors := anchors:
Minimum := vector2{X := 0.5, Y := 0.25}
Maximum := vector2{X := 0.5, Y := 0.25}
Alignment := vector2{X := 0.5, Y := 0.5}
SizeToContent := true
Widget := stack_box:
Orientation := orientation.Vertical
Slots := array:
stack_box_slot:
Widget := WidgetTitle
stack_box_slot:
Widget := WidgetRow1
stack_box_slot:
Widget := WidgetUIButton
return NewCanvas
RemoveCanvas(Message: widget_message):void=
if (PlayerUI := GetPlayerUI[Message.Player], MyUI := MaybeMyUIPerPlayer[Message.Player]?, SelectedButton := text_button_base[Message.Source]):
Print("Remove Canvas")
PlayerUI.RemoveWidget(MyUI)
if (set MaybeMyUIPerPlayer[Message.Player] = false) {}
Nice! Ya it worked for me! It’s kinda complicated, for such a basic feature that most people need for UI. I think even Epic might agree multiplayer UI is complicated, because the pizza delivery game they’re teaching in the “Managing and Displaying Score” Verse UI tutorial is singleplayer. But now that I know how to do it I’m set.