My client code is littered with WaitForChild invocations. The reason you have to do this is because you can’t guarantee that everything is loaded when the script first executes.
My question: Using game:IsLoaded() and game.Loaded, could you avoid using WaitForChilds?
For instance:
if (not game:IsLoaded()) then game.Loaded:Wait() end
-- Dot-notation without WaitForChilds:
local gui = game.Players.LocalPlayer.Gui
local frame = gui.Some.Item.Nested.Deep.Down
In most cases, my client code is not required to execute immediately; I can wait for the game to load without causing any issue.
Note: I am not talking about cases when the client is waiting for an object that the server adds in later. I’m strictly talking about items replicated when the player first joins the game. For instance, GUI objects within StarterGui.
So, note that the only use of waiting for the game to load would be when you’re working with a LocalScript inside ReplicatedFirst. From the fact that regular LocalScripts run after the game has loaded anyway, we can probably conclude that this will not remove the necessity of using Instance:WaitForChild a lot.
It’d be nice to have a clear explanation of the difference between “when all initial Instances in the game have finished replicating to the client” (which is what DataModel.Loaded claims to wait for) and “when all initial Instances in the game have been properly parented”.
The only things you should be using Instance:WaitForChild() for is Player.PlayerGui and top-level ScreenGuis.
No, you can’t use game.Loaded to avoid that. GUIs are not cloned until the character spawns from game.StarterGui.
I recommend ditching game.StarterGui entirely so as not to wait on the player’s character, and instead just clone them all from a folder in game.ReplicatedStorage.
You can do something like this on the server and just wait for the Loaded bool value since it was created last it will load in last.
local StarterGuiObjects = {}
for _,x in ipairs(game.StarterGui:GetChildren())do
x.Parent = nil
table.insert(StarterGuiObjects, x)
end
local function PlayerAdded(player)
local playerGui = player:WaitForChild("PlayerGui")
for _,x in ipairs(StarterGuiObjects)do
x:Clone().Parent = playerGui
end
local loaded = Instance.new("BoolValue")
loaded.Name = "Loaded"
loaded.Parent = playerGui
end
for _,x in ipairs(game.Players:GetPlayers())do
coroutine.wrap(PlayerAdded)(x)
end
game.Players.PlayerAdded:Connect(PlayerAdded)
I think just putting wait() at the beginning of the LocalScript is enough.
Otherwise, The only thing that occurs to me so far is a LocalScript in PlayerGui that waits for all children to load and then creates an object called “Loaded” or else you change the name or disable etc, then in the rest of the scripts a line at the beginning like this.
game.Players.LocalPlayer:WaitForChild("Loaded")
You can make a Plugin that generates a script with a WaitForChild for each object in the StarterGui.
Wiki: Instance:WaitForChild, a function which can be used to wait for an individual Instance to replicate without having to wait for the whole game to.
Unless they are parented to ReplicatedFirst, LocalScripts will not run while the game has not loaded.
This would imply that waiting for game to load is enough.
I’ve rarely had any issues with referencing Instances in LocalScripts instantly.
Some tests in studio:
Game loaded: 1547240178.5973
LocalScript started 1547240180.0001
Difference: 1.4027998447418
Parenting 100 GuiObjects to Gui and checking how many are loaded:
They give hints about it, but never explicitly say so: Unless they are parented to ReplicatedFirst, LocalScripts will not run while the game has not loaded.
Although, it’s probably not safe to reference any object which wasn’t there when game started, as theres a possibility they won’t be loaded when you reference them.
That shouldn’t matter, as game.Loaded fires when all of them are replicated.
It doesn’t matter if A gets replicated before B, because you LocalScripts start after they’re both replicated.
NOTICE: I have since renounced the content of this post. This explanation details why, and provides advice for dealing with the game tree in a more robust manner.
Refer to the diagram in this post:
Using the information in this post, I can come up with a system to determine how objects should be accessed:
Use of WaitForChild depends on the location of the invoking script, and whether the target object is
“static”: exists before game starts; i.e. stored in the place file.
“dynamic”: added after the game starts; opposite of static.
Consider the following rules (subsequent rules override previous rules, when applicable):
Generally, scripts may access static objects directly.
Generally, scripts must use WaitForChild to access dynamic objects.
Scripts under ReplicatedFirst must use WaitForChild to access static objects.
Scripts under ReplicatedFirst may access static objects under ReplicatedFirst directly.
Code protected by game.Loaded may access static objects directly.
Services, as targets, are exempt from these rules as long as they are accessed via GetService.
Also worth noting is that an object being static assumes that the object is never removed during the runtime of the game. A supposedly static object that is removed at any point must be treated as dynamic. Technically, because any object can be removed at any time, even before scripts run, static objects do not exist. But it’s probably safe to assume that the runtime has some integrity, and leaves removing objects only to the developer.
I think that in Studio everything goes well, but in ROBLOX Player is a little different, I have happened bugs when I do not put WaitForChild in a LocalScript, I have all those errors in my Game Analytics, do not always occur, I think it depends on the performance of the client, never occur in ROBLOX Studio.
The rules can be applied to server scripts (Static → Direct, Dynamic → WaitForChild). But there are no nuances like ReplicatedFirst and game.Loaded. Basically, rules 3-onward apply only to the client.
Server scripts can access static server objects directly because they are on the same peer, and are therefore visible from the start.
On the client, the game tree starts out empty, and all static objects replicated from the server are actually dynamic. However, there are procedures and APIs (as described in the referred post) that allow us to treat a subset of these objects (“DataModel” in the diagram) as static, as long as certain conditions are met. The rules were created from these conditions.