Multiplayer UI button issue.


Hey Everyone, I am having problems with UI working for single player instead of multiplayers. So I’m almost finished with a project I’m working in UEFN and the problem that I’m trying to fix is that each player presses the UI button, and the game starts and leaving the other players be stuck in the dialog until pressing the UI button to get started in the middle of the game. So I have a video example of the issue, and let me know if y’all have any helpful thoughts, tips, advices. It would be really helpful, because I’ve tried figuring out what to make it work, but nothing’s working.

Hello @NoahD1 how are you?

It seems that when a player clicks on the button, it starts the match for every player but it only removes the UI for the player that clicked the button.

This problem should be solved if you use a “for” to disable the UI for every player that is currently in your island when anybody click on the button.

Other thing you can do, is to check if all the players already clicked the button before starting the match.

If you need more help, please share your code here, so we can check it and make the proper corrections for you!

1 Like

Hello @BRGJuanCruzMK, I still have the same problem after using for in verse and it’s still not working for multiplayers. But the good thing is it works for one player, but not for other players that are joining the private playtest session and even includes a public release game, and I still need more help. So I’ll share the playtest code from Fortnite Creator Portal, and thank you so much for tips.

Here’s the code:

3780-3345-8791

1 Like

Hello again @NoahD1 !

When I said “share your code” I meant the Verse code you are using for this feature, so I can check where the error is and fix it for you.

I understand the problem you are having, but I cannot help if I don’t know your Verse code.

I’ll be waiting for it!

Oh, I’m sorry about that, man. I thought you mean like Island code or playtest code, but my bad. I’ll go ahead and share the verse code. Well, I have nine of them in my island, six of them are creative devices and three of them are NPC behaviors.

Here’s two verse code scripts:

title_screen_handler script:

using { /Fortnite.com/Devices }

using { /Verse.org/Simulation }

using { /UnrealEngine.com/Temporary/Diagnostics }

# Device to handle the title screen dialog logic and respond to button presses

title_screen_handler := class(creative_device):

\# Editable reference to the main title screen dialog device

@editable

TitleScreenDialog : popup_dialog_device = popup_dialog_device{}



\# Editable reference to the tower defense dialog device

@editable

TowerDefenseDialog : popup_dialog_device = popup_dialog_device{}



\# Editable reference to the end game device

@editable

EndGameDevice : end_game_device = end_game_device{}



@editable

Player1Spawner : player_spawner_device = player_spawner_device{}

@editable

Player2Spawner : player_spawner_device = player_spawner_device{}

@editable

Player3Spawner : player_spawner_device = player_spawner_device{}

@editable

Player4Spawner : player_spawner_device = player_spawner_device{}



\# Indices for dialog buttons

StartGameButtonIndex : int = 0

ExitGameButtonIndex : int = 1



\# Track whether the title screen dialog has been shown already

var TitleScreenShown : logic = false

var TowerDefenseDialogShown : \[agent\]logic = map{}



\# Subscribe to button press events from the TitleScreenDialog when the device starts

OnBegin<override>()<suspends>:void=

    if (TitleScreenShown = false):

        TitleScreenDialog.Show() # Show only once

        set TitleScreenShown = true

        TitleScreenDialog.RespondingButtonEvent.Subscribe(OnTitleButtonPressed)



\# Handle button presses in the title screen dialog

OnTitleButtonPressed(Data:tuple(agent, int)):void=

    Agent := Data(0)

    ButtonIndex := Data(1)

    if (ButtonIndex = 0):

        \# Close the dialog for the player who pressed the button

        TitleScreenDialog.Hide(Agent)



    \# Show TowerDefenseDialog only from Start Game button

    if (ButtonIndex = StartGameButtonIndex):

        Player1Spawner.Enable()

        Player2Spawner.Enable()

        Player3Spawner.Enable()

        Player4Spawner.Enable()

        if (not TowerDefenseDialogShown\[Agent\]?):

            Print("Start_Game_Button pressed - enabling and showing TowerDefenseDialog for agent.")

            TowerDefenseDialog.Enable()

            TowerDefenseDialog.Show(Agent)

            Print("Start_Game_Button - TowerDefenseDialog enabled/shown.")

            if (set TowerDefenseDialogShown\[Agent\] = true) {}

            \# Enable all referenced player spawners

            PlayerSpawners : \[\]player_spawner_device = array{

                Player1Spawner,

                Player2Spawner,

                Player3Spawner,

                Player4Spawner

            }

            for (Spawner : PlayerSpawners):

                Spawner.Enable()

        else:

            Print("TowerDefenseDialog already shown to this agent. Not showing again.")



    \# Handle exit button

    else if (ButtonIndex = ExitGameButtonIndex):

        EndGameDevice.Enable()

        EndGameDevice.Activate(Agent)

        Print("Exit_Game_Button pressed - EndGameDevice enabled.")



HideAllDialogs():void=

    Players := GetPlayspace().GetPlayers()

    for (Player : Players):

        TitleScreenDialog.Hide(Player)

tower_defesne_manager script:

using { /Fortnite.com/Devices }

using { /Fortnite.com/Characters }

using { /Verse.org/Simulation }

using { /UnrealEngine.com/Temporary/Diagnostics }

using { /UnrealEngine.com/Temporary/SpatialMath }

using { /UnrealEngine.com/Temporary/UI }

using { /Fortnite.com/UI }

# Persistable data structure for round stats

round_data := class:

RoundNumber : int = 1

PointScore : int = 0

CoinScore : int = 500

# Device to manage tower defense rounds and settings

tower_defense_manager := class(creative_device):

\# Dialogs & Round infrastructure

@editable

TowerDefenseDialog : popup_dialog_device = popup_dialog_device{}

@editable

GateSequencer : cinematic_sequence_device = cinematic_sequence_device{}



\# NPC Spawners

@editable

FastNPCSpawner : npc_spawner_device = npc_spawner_device{}

@editable

MediumNPCSpawner : npc_spawner_device = npc_spawner_device{}

@editable

StrongNPCSpawner : npc_spawner_device = npc_spawner_device{}



\# End round

@editable

EndRound : end_game_device = end_game_device{}



\# Stats

@editable

PointsStat : stat_creator_device = stat_creator_device{}

@editable

CoinsStat : stat_creator_device = stat_creator_device{}



@editable

RoundTimer : timer_device = timer_device{}



@editable

FixedPointCamera : gameplay_camera_fixed_point_device = gameplay_camera_fixed_point_device{}

@editable

ThirdPersonControls : gameplay_controls_third_person_device = gameplay_controls_third_person_device{}

@editable

Objective : objective_device = objective_device{}



@editable

Player1Spawner : player_spawner_device = player_spawner_device{}

@editable

Player2Spawner : player_spawner_device = player_spawner_device{}

@editable

Player3Spawner : player_spawner_device = player_spawner_device{}

@editable

Player4Spawner : player_spawner_device = player_spawner_device{}



@editable

Rounds : \[\]round_settings_device = array{}



\# Round timer and logic

var CurrentRound : int = 1

TotalRounds : int = 30

RoundDuration : float = 300.0 # seconds (5 min)

var RoundActive : logic = false



var IsRoundActive : logic = false



var DialogShown : logic = false

var RoundStarted : logic = false

var DialogSubscription : ?cancelable = false



\# Persistent storage for player round data

var PlayerRoundData : weak_map(player, round_data) = map{}



\# Array of round settings devices for 30 rounds

var RoundSettings : \[\]round_settings_device = array{}



var CurrentRoundIndex : int = 0

var AgentsInZone : \[agent\]logic = map{}

var PlayerDialogSubscriptions : \[player\]cancelable = map{}

var ActiveDialogPlayer : ?player = false



OnBegin<override>()<suspends>:void=

    \# Start: disable all spawners (idle)

    FastNPCSpawner.Disable()

    MediumNPCSpawner.Disable()

    StrongNPCSpawner.Disable()

    \# Subscribe to the 1Player spawner's spawn event

    Player1Spawner.SpawnedEvent.Subscribe(OnPlayer1Spawned)

    Player2Spawner.SpawnedEvent.Subscribe(OnOtherPlayerSpawned)

    Player3Spawner.SpawnedEvent.Subscribe(OnOtherPlayerSpawned)

    Player4Spawner.SpawnedEvent.Subscribe(OnOtherPlayerSpawned)

    \# Subscribe to dialog button interaction

    TowerDefenseDialog.RespondingButtonEvent.Subscribe(OnTowerDefenseButtonPressed)

    \# Cancel and hide timer at game start—do this FIRST no matter what!

    Self.RoundTimer.Reset()

    Self.RoundTimer.Disable()

    set Self.DialogSubscription = option{Self.TowerDefenseDialog.RespondingButtonEvent.Subscribe(Self.OnDialogButtonPressed)}

    \# Notice: No call to Self.TowerDefenseDialog.Show() or Enable() here

    \# Keep Player1 enabled, and leave Player2, Player3, and Player4 disabled until the game starts.

    Self.Player1Spawner.Enable()

    Self.Player2Spawner.Disable()

    Self.Player1Spawner.Disable()

    Self.Player1Spawner.Disable()



OnPlayer1Spawned(Player: agent): void =

    \# Show dialog and subscribe only for the player spawned from 1Player spawner

    if (FirstPlayer := player\[Player\]):

        TowerDefenseDialog.Show(FirstPlayer)

        Subscription := TowerDefenseDialog.RespondingButtonEvent.Subscribe(OnDialogButtonPressed)

        if (set PlayerDialogSubscriptions\[FirstPlayer\] = Subscription) {}

        set ActiveDialogPlayer = option{FirstPlayer}

        Print("Waiting for round start...")



OnOtherPlayerSpawned(Player: agent): void =

    Print("Player from non-priority spawner joined, dialog not shown.")

    \# No dialog/subscription for 2/3/4Player



OnDialogButtonPressed(Agent:agent, ButtonIndex:int):void =

    if (ActivePlayer := ActiveDialogPlayer?, Agent = ActivePlayer, ButtonIndex = 0):

        if (ButtonIndex = 0 and Self.RoundStarted = false):

            set Self.RoundStarted = true

            if (DialogSub := Self.DialogSubscription?):

                DialogSub.Cancel()

            set Self.DialogSubscription = false

            \# Dismiss dialog and clean up only for the active player

            \# Only the 1Player spawner player can trigger start logic!

           if (Subscription := PlayerDialogSubscriptions\[ActivePlayer\]):

                Subscription.Cancel()

                TowerDefenseDialog.Hide(ActivePlayer)

            set PlayerDialogSubscriptions = map{}

            set ActiveDialogPlayer = false

            Print("Game Started!")

            \# Proceed with starting the game...

            \# Switch camera back to Fortnite's default third-person view

            Self.FixedPointCamera.Disable()

            Self.ThirdPersonControls.Enable()

            Self.RoundTimer.Enable()

            Self.RoundTimer.Reset()

            Self.RoundTimer.Start()

            \# Reset camera for all players

            for (Player : GetPlayspace().GetPlayers()):

                TowerDefenseDialog.Show(Player)

                DialogSub := TowerDefenseDialog.RespondingButtonEvent.Subscribe(OnDialogButtonPressed)

                if (set PlayerDialogSubscriptions\[Player\] = DialogSub) {}

                    FixedPointCamera.Disable()

                    spawn{HandleTimerExpiration()}

                    TowerDefenseDialog.Hide(Player)

                set PlayerDialogSubscriptions = map{}

                \# Proceed with starting the game...



            for (Player -> DialogSub : PlayerDialogSubscriptions):

                if (DialogSubscription?):

                    if (Sub := DialogSubscription?):

                        Sub.Cancel()             # Cancel this player's dialog event subscription

                TowerDefenseDialog.Hide(Player)  # Hide the dialog for this player



            set PlayerDialogSubscriptions = map{} # Clear the map afterwards



            \# Create 30 round settings devices and store them in the array

            set RoundSettings = for (Index := 0..29):

                round_settings_device{}



            \# Example: Configure each round's settings

            for (RoundIndex -> RoundDevice : RoundSettings):

                \# Set up round-specific settings here

                \# For example, you could enable/disable certain features per round

                \# or configure round-specific rules

                Print("Configured round {RoundIndex + 1}")



\# Example function to get a specific round's settings

GetRoundDevice(RoundIndex : int) : void =

    if (RoundDevice := RoundSettings\[RoundIndex\]):

        RoundDevice



\# Call this when a round ends

HandleRoundEnd() : void =

    for (Player : GetPlayspace().GetPlayers()):

        if (RoundDevice := RoundSettings\[CurrentRoundIndex\]):

            Players := GetPlayspace().GetPlayers()

            if (FirstPlayer := Players\[0\]):

                RoundDevice.EndRound(FirstPlayer) # Pass agent type!

       

HandleTimerExpiration()<suspends>:void =

    Self.RoundTimer.SuccessEvent.Await()

    for (Player : Self.GetPlayspace().GetPlayers()):

        Self.EndRound.Activate(Player)

    Self.RoundTimer.Reset()

    Self.RoundTimer.Disable()

    \# Implementation for timer expiration handling

    Print("Timer expired")



    Players := Self.GetPlayspace().GetPlayers()

    if (Players.Length > 0):

        \# Start the configured round timer (automatically shows on player HUD)

        Self.RoundTimer.Start()

        \# Wait for the timer to complete

        Self.RoundTimer.SuccessEvent.Await()

        \# After the timer finishes, end the round for all players

        for (Player : Players):

            Self.EndRound.Activate(Player)

    else:

        Print("Can't find player")



\# Initialize or load persistent data for a player

InitializePlayerData(Player:player):void =

    if (ExistingData := PlayerRoundData\[Player\]):

        \# Data already exists, do nothing

        Print("Loaded existing data for player")

    else:

        \# Create new data with default values

        NewData := round_data{}

        if (set PlayerRoundData\[Player\] = NewData):

            Print("Initialized new data for player")



\# Save updated round data for a player

SavePlayerRoundData(Player:player):void =

    if (CurrentData := PlayerRoundData\[Player\]):

        \# Create new data instance with updated values

        NewData := round_data{

            RoundNumber := CurrentData.RoundNumber + 1,

            PointScore := CurrentData.PointScore + 100,

            CoinScore := CurrentData.CoinScore + 50

        }

        if (set PlayerRoundData\[Player\] = NewData):

            Print("Saved updated data for player")



OnTowerDefenseButtonPressed(Data:tuple(agent, int)):void=

    ButtonIndex := Data(1)

    case(ButtonIndex):

        \# 0: Start round, enable all spawners, move players, keep dialog shown

        0 => HandleStartRound()

        \_ => Print("Unknown button pressed")



HandleStartRound():void=

    if (CurrentRound > TotalRounds):

        Print("All rounds complete.")

        return

    if (RoundActive = false):

        set RoundActive = true

        FastNPCSpawner.Enable()

        MediumNPCSpawner.Enable()

        StrongNPCSpawner.Enable()

        FastNPCSpawner.Spawn()

        MediumNPCSpawner.Spawn()

        StrongNPCSpawner.Spawn()

        GateSequencer.Play()

        Print("Round {CurrentRound} started.")

        \# Move all players to new location & keep dialog shown

        for (Player : GetPlayspace().GetPlayers()):

            if (Character := Player.GetFortCharacter\[\]):

                TargetLocation := vector3{X := 1000.0, Y := 1000.0, Z := 200.0}

                if (Character.TeleportTo\[TargetLocation, rotation{}\]):

                    Print("Moved player to target location at round start.")

        \# Start timer for this round

        spawn{RoundTimerLoop()}



\# Each round lasts 5 min, up to 30 rounds; stops spawners & activates EndRound each time.

RoundTimerLoop()<suspends>:void=

    Print("Started round timer for {RoundDuration} seconds.")

    Sleep(RoundDuration)

    set RoundActive = false

    \# Stop spawners

    FastNPCSpawner.Disable()

    MediumNPCSpawner.Disable()

    StrongNPCSpawner.Disable()

    Print("Round {CurrentRound} ended.")

    \# Activate EndRound device for all players

    EndRound.Enable()



    for (Player : GetPlayspace().GetPlayers()):

        if (CurrentRoundIndex = 29):

            EndRound.Activate(Player)

    \# RoundSettings: notify round end

    for (Player : GetPlayspace().GetPlayers()):

        if (RoundDevice := RoundSettings\[CurrentRoundIndex\]):

            RoundDevice.EndRound(Player) # Pass Player (agent)

    set CurrentRound += 1

No worries, I didn’t calrify it haha

I’ll be checking your code and reach you again when I have a solution! :smiley:

Sound good, thank you so much dude. :+1:

Hey @NoahD1 how are you?

I’ve been trying to fix this issue but I realized that I don’t understand the flow the game is trying to follow.

Currently, you have the Main Menu, with Start Game and Exit Game buttons. When any player press “Start Game”, the main manu is closed and a new menu with only a “Start Round” button.

Pressing the “Start Game” button on the main menu also starts the round, and that is one of the problems, right?

I assume you want to start the round after everybody is ready (all the players pressed Start Game), is that correct?

I also assume that your don’t want to be able to start the round if there is any “unready” player, do you?

Another thing I’d like to understand is if you want all the players to be able to start the round or onlye the Player 1.

And the last thing, are you sure you need a second “Start” button? Maybe you can start a timer and start the round automatically when everybody is ready.

I’ll be waiting for your answers to keep working on this!