Remote Event Doesn't Fire When Joining Game

Hi guys,

My game has CharacterAutoLoads set to false and I need to clone the game’s GUI to the player. I achieve this by doing the following in a server script in ServerScriptService:

game.Players.PlayerAdded:Connect(function(player)
    local UI = player:WaitForChild("PlayerGui")
    for i,v in pairs(game.StarterGui:GetChildren()) do
        v:Clone().Parent = UI
    end
end)

In the main game loop, I need to check to see if there are enough players to start the game. However, I have noticed that there are times when the code below does not work as intended, even though I have no errors in my output. It seems like the client has not fully loaded before this RemoteEvent is fired, and I have no idea how to go about fixing it. I already tried to fix this by using a repeat wait(), but it doesn’t seem to be efficient:

--------------------
--|----SERVER----|--
--------------------
--main game loop
local playersInserted = {}
while wait() do
    if #game.Players:GetPlayers() < 3 then
        for i,v in pairs(game.Players:GetPlayers()) do
            if not playersInserted[v.Name] then
                playersInserted[v.Name] = true
                repeat wait() print("waiting") until v.PlayerGui:FindFirstChild("UI"):FindFirstChild("Main"):FindFirstChild("NotEnoughPlayers") and v.PlayerGui:FindFirstChild("UI"):FindFirstChild("Main"):FindFirstChild("Logo")
                RS.RemoteEvent:FireClient(v,"NotEnoughPlayers")
            end
        end
    else
        --start the game
    end
end

--------------------
--|----CLIENT----|--
--------------------
local bin = script.Parent
local main = bin:WaitForChild("Main")

game.ReplicatedStorage.RemoteEvent.OnClientEvent:Connect(function(...)
    local tuple = {...}
    local arg1 = tuple[1]

    if arg1 == "NotEnoughPlayers" then
        print("received")
        main.NotEnoughPlayers.Visible = true
        main.Logo.Visible = true
    end
end)

I’m expecting the client to print “received”, but it only happens about half the time. Here’s a screenshot of the hierarchy in the explorer…

…and here is a screenshot of the expected behavior:

Any help would be greatly appreciated. I hope I was clear about what I’m trying to achieve. Thank you for helping! :slight_smile:

Why are you even doing this, you can’t replicate to StarterUI. PlayerGUI is a clone of StarterUI

Please read the very first line. I have CharacterAutoLoads set to false, so I need to do the cloning manually.

1 Like

Why is there a vararg here? Aren’t you only sending 1 argument?

You could move the print function outside of the condition to check if the client is receiving it

--------------------
--|----CLIENT----|--
--------------------
local bin = script.Parent
local main = bin:WaitForChild("Main")

game.ReplicatedStorage.RemoteEvent.OnClientEvent:Connect(function(...)
    local tuple = {...}
    local arg1 = tuple[1]

    print("remote was fired") -- move it outside of the condition because it may be false

    if arg1 == "NotEnoughPlayers" then
        main.NotEnoughPlayers.Visible = true
        main.Logo.Visible = true
    end
end)

Your main issue could be this:

It pauses the entire loop until it returns true

coroutine.wrap(function() -- wrap it in a coroutine so it doesn't pause it
    repeat wait() print("waiting") until v.PlayerGui:FindFirstChild("UI"):FindFirstChild("Main"):FindFirstChild("NotEnoughPlayers") and v.PlayerGui:FindFirstChild("UI"):FindFirstChild("Main"):FindFirstChild("Logo") 
end)()

Thanks for the reply,

I use the same remote event for multiple things, so that’s what the vararg is for.

I implemented the coroutine and moved the print function like you suggested, but there are still times when the UI appears and times when it doesn’t appear at all.

Edit: adding a wait(1) before firing the remote seems to fix the problem, but I don’t really like this solution. Perhaps there’s a better way to fix it.

You could use Players.PlayerAdded and Players.PlayerRemoving instead of a loop (if possible)

local players = game:GetService("Players") -- players service

local function IsEnoughPlayersInGame() -- function to get the amount of players in-game
   local LIMIT = 3 -- the minimum limit of players in-game

   if #players:GetPlayers() >= LIMIT then -- if the number of players are greater than or equal to 3
      return true -- yes, there are enough people
   end

   return false -- there aren't enough people (default)
end

players.PlayerAdded:Connect(function(player) -- detect when a player joins
    if IsEnoughPlayersInGame() then -- check for the amount of players
        -- there are enough players in the game, do stuff
    else
        -- still waiting
    end
end)

players.PlayerRemoving:Connect(function(player) -- detect when a player leaves
    if IsEnoughPlayersInGame() then -- check for the amount of players
        -- still enough players in the game
    else
        -- nope, have someone else join
    end
end)

Thank you, I will try this out. Thanks for the detailed comments as well. I’ll let you know what happens.

1 Like

Could be that the client’s yield here

is what’s preventing the message from being given to that client-sided function. Try putting a print before and after local main to see if that’s the case.

I will also add that the while wait() do yield is bad practice. You’re not alone in doing this, as many of my old scripts have similarly nasty “polling” methods like that, but if you just fire the remote from within the first PlayerAdded function right after replicating the GUI, then it’s far less prone to other weird bugs and it’s easier to work with even with yourself. While it doesn’t necessarily solve the issue at hand here, it makes way more sense in the code. I’m guessing the first and second pieces of code are in separate scripts, so you’d either put the first code in the second script to share the same table or you’d use modules/_G to share a table between the scripts.

1 Like

Thanks for the reply,

I tried your suggestion and it seems like even though I fire the remote after replicating the GUI, there are still times when the “not enough players” header and logo appears and other times when they do not, which is a bit strange. Perhaps it is because of the WaitForChild. I’ll try messing around with that, too.

Ok, so I tried to implement your suggestion, but what I’m confused about is how am I supposed to “loop” through the main game when there is no loop. Like how would a new round start once the round is over?

You can create an event that the game fires when it detects a round has ended. Then create a script that detects when the event is fired and starts another round (it’ll loop itself).

-- example (rounds being handled by the server)

-- script that detects when a round has ended
local roundEnded = game.ServerStorage:WaitForChild("RoundEnded") -- bindable event for the server
local roundStart = game.ServerStorage:WaitForChild("RoundStart") -- another bindable event for the server

roundEnded.Event:Connect(function() -- detect when the event is fired
   wait(30) -- just a place holder for waiting for a new round 
   -- you could also put other things here like the "IsEnoughPlayersInGame" function

   roundStart:Fire(...) -- start a new round "..." is for any arguments you may send (if any)
end)

-- script to start the round
local roundEnded = game.ServerStorage:WaitForChild("RoundEnded")
local roundStart = game.ServerStorage:WaitForChild("RoundStart")

roundStart.Event:Connect(function(...) -- detect when the round starts
    -- do stuff
    -- insert things here until the round ends

    roundEnded:Fire() -- fire the round ended event
end)

The code above is just a visualization of what something like that could look like
It may be different when you set it up

Edit: The script to detect when a round starts and ends can be in the same script (I recommend that)

Another thing I recommend is making the events, normal functions instead of anonymous functions

local roundEnded = game.ServerStorage:WaitForChild("RoundEnded")
local roundStart = game.ServerStorage:WaitForChild("RoundStart") 

-- functions to call when the events are called
local function onRoundEnded()
    -- do stuff

    roundStart:Fire(...)
end

local function onRoundStarted(...)
    -- do stuff

    roundEnded:Fire()
end

-- connect the events to the functions
roundEnded.Event:Connect(onRoundEnded)
roundStart.Event:Connect(onRoundStarted)

Ok thanks for the info, I’m going to try to work on this now!