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…
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)()
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)
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.
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)