There is an issue many have been facing where referencing GUI items from scripts in the same PlayerGui container immediately after the script starts running throws an error unless a WaitForChild command is used like this:
ObjectName = script.Parent:WaitForChild("ObjectName")
For a while, I’ve used this solution myself to get the scripts to stop throwing errors trying to reference things like ScreenGuis and other scripts, however, this is annoying for three reasons. One is that IntelliSense doesn’t work with things referenced by WaitForChild, making it harder to develop. Another is that it won’t ever throw an error to alert the scripter that a mistake is made. And finally, one has to write a LOT of WaitForChild phrases.
Eventually, I found a cheap way to solve them all.
I figured that the game engine clones every child inside of the StarterGui service one at a time to the Player’s PlayerGui when the player’s character spawns, however, as it adds each LocalScript clone, the game engine starts the script before letting the cloning process continue.
This behavior can be proven by running a test with three scripts and a ScreenGui, as shown in the picture.
Every script has this code to print out what it sees when it starts.
print("--- What " .. script.Name .. " Sees in Player Gui ---") for i, item in ipairs(script.Parent:GetChildren()) do print(item.Name) end
There are no yielding functions, so the engine will have no chance of continuing cloning objects as said before.
Starting this up, this is what I get:
As you can see, some scripts didn’t see some other scripts and none of them saw the ScreenGui when they started.
Assuming that the whole game loads with StreamingEnabled off before cloning the GUI, that PlayerGui cloning behaves the way mentioned before, and that it clones each item before adding rather than cloning every descendant of every item individually, I figured that the solution is turning every script into a ModuleScript and making a lazy waiter script to start them all after everything is cloned, using the StarterGui as a reference of what’s supposed to be in there.
Another benefit of using ModuleScripts is you don’t have to use BindableFunctions nor BindableEvents as much to communicate between GUI scripts, as you can cram native Lua functions in a table returned by ModuleScripts.
I converted all the LocalScripts into ModuleScripts, and then added a LocalScript loader to test this.
-- Wait until everything is loaded before starting the scripts. for i, item in ipairs(game.StarterGui:GetChildren()) do script.Parent:WaitForChild(item.Name) end -- Run a "require" against every ModuleScript in here to start them up. for i, item in ipairs(script.Parent:GetChildren()) do if item:IsA("ModuleScript") then require(item) end end
Now the setup looks like this:
And the test scripts look like this because you have to return something from a ModuleScript:
ScreenGui = script.Parent.ScreenGui -- Just testing referencing. print("--- What " .. script.Name .. " Sees in Player Gui ---") for i, item in ipairs(script.Parent:GetChildren()) do print(item.Name) end return nil
Even after deleting and adding another ScreenGui, this is what I get when I start it:
None of them crashed, and they see everything needed!
So to sum it up, all you need to do is turn all your GUI scripts into ModuleScripts, make a single LocalScript that waits until everything they need load before starting them up, and enjoy the development ease.
A few side notes
You don’t need to worry about waiting for workspace and ReplicatedStorage objects unless you have StreamingEnabled on or they are created after the game server starts.
Developing ForbiddenJ’s Quarry with this solution proves that each object is cloned whole, as I don’t get any reference errors accessing the children of the ScreenGuis after waiting for the objects themselves, however, trying to prove it here would make the post even more complicated.
If a ModuleScript crashes or yields indenfinitely in the middle of starting, the whole starter stops, preventing more scripts from starting. Though I would do a pcall or ypcall if I knew how to make errors show up in the console as errors anyway.
I would assume the same behavior applies to the PlayerScripts container, however, unlike player scripts, GUI things only load when the player’s character spawns. Also, the GUI elements are destroyed and re-cloned when a player respawns unless you change some properties, which may mess up your references when working from PlayerScripts.
Keep those in mind.