Better alternative to WaitForChild

I’ve been loading more sounds in my game recently and realized how repetitive the code is. Is there a way to shorten this code while still being assigned to a specific variable for future use?

The segment of this script I posted below doesn’t include the actual sound names and are stored in a local script in the starter gui. I’ve used the WaitForChild for all the Gui elements in my game too.

-- Calls and waits for all the sounds
local soundService = game:GetService("SoundService")
local sound1 = soundService:WaitForChild("Sound1")
local sound2 = soundService:WaitForChild("Sound2")
local sound3 = soundService:WaitForChild("Sound3")
local sound4= soundService:WaitForChild("Sound4")

2 Likes

You could make a local function before to shorten it (Unfortunately you cant really do anything to make it less repetitive).

local function toWaitChild(waitingParent, childWaited)
      waitingParent:WaitForChild(childWaited)
end
toWaitChild(soundService, "Sound1")

There isn’t a alternative to this actually, the only one is FindFirstChild()

3 Likes

So, from what I’ve learned, “containers” load all of the elements within it, so WaitForChild is not necessary. If you put all these sounds in a folder, then use WaitForChild for the folder, then the sounds should work fine for sounds. At least, that’s how containers work for UIs (like ScreenGuis).

7 Likes

I think both of these are good solutions, and it seems like the folder way works the best! I’ll keep this thread opened for now.

Hey there! Keep in mind that repetition like this isn’t as bad as it seems!

If you just want to minimize WaitForChild calls, consider it done! However, there’s still a chance it fails.

You could also recur FindFirstChild calls but that’s a pretty absurd approach unless if your squeezing performance out of something and know what you’re doing. Even then, though, I think that’s not viable. Just theory crafting.

Before reading below, the things I’m about to mention are generalizations of my observations in teaching many people in the field of computer science from those who are completely new to those who have programmed close to eight years. Nothing listed or assumed is directed to any one person.

If you are more on the “less keystrokes” side of things, don’t! There’s a difference between not repeating yourself and exhaustively listing dependencies on what is needed for your script to function. For instance, if you could create a solution such as this:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local MyAwesomeModule = require(ReplicatedStorage:WaitForChild("MyAwesomeModule"));
local sound1, sound2, sound3, sound4 = MyAwesomeModule:GetAllTheSounds();

Now, intuitively to many programmers, that doesn’t sound too bad. However, I’d argue it’s a logical flaw many people seem to attach to “less keystrokes” or "less lines:

  • The code is easier to read.
  • The code is easier to maintain.
  • The code is more efficient.

This is wrong, it’s often times none of the above. The proposed solution is anything but intuitive Lua code or intuitive Roblox API calls; it’s a weird, unfamiliar micro-service. It’s not easier to maintain as it’s not easily scalable nor can we have any confidence that it’s a pure call; side effects from GetAllTheSounds or even the require call to “MyAwesomeModule” might do something unexpected. Furthermore, the function call adds a (negligible) amount of overhead in terms of efficiency.

TL;DR: Don’t try to jank your way out of writing a few lines of code.


Further reading: Check out @megatushar100’s great thread and my feedback describing in further detail why micro-services and “side effects” are a big issue: Feedback on my Player Value creation system - #4 by TheEdgyDev

3 Likes

I see. If I could clarify, sometimes it would be better to just write it all out, instead of trying to create a function that looks efficient but could actually give you unexpected consequences?

Yes! Absolutely. Writing it out, in this context, is the more maintainable option.

3 Likes

You can store an array of all the sound names then load them into another or the same array:

local SoundService = game:GetService("SoundService")
local SoundNames = {"Sound1", "Sound2", "Sound3", "Sound4"}
local LoadedSounds = {}

for _, SoundName in ipairs(SoundNames) do
    table.insert(LoadedSounds, SoundService:WaitForChild(SoundName))
end

--then to access sound 1 you would do:

print(LoadedSounds[1])


Only do this if your grouping a bunch of sounds that will be used the same way but randomly, apart from that it will be much more readable if you assign the sound to meaningful variable names. Also I recommend you have one module that plays all the sounds in your game, that way if the client wants to disable sound in some setting you just update that module.

This is equivalent to local toWaitChild = game.WaitForChild, for the record. No reason to wrap it in a function when you’re keeping the parameter order.


To tidy this up, though, you might want to require a Module that creates/waits for the sounds so it doesn’t clutter the main script. Splitting stuff into sections goes a long way to making neater code.

3 Likes

These are some very interesting solutions. You need to compare performance over readability or repetition though. Additional functions, loops, scripts, etc. might decrease performance and be less future-proof to rbx engine updates than simply repeating the :WaitForChild() function.

In most of my projects I put sounds and such in folders, wait for the folder child, then use the children in my code. I haven’t had any issues with it thus far.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Sounds = ReplicatedStorage:WaitForChild("SoundsFolder")

I think the most important takeaway from this is realizing that code is unique to the programmer and sometimes different methods make more sense to others. If it’s obscure enough, we write comments to help others understand our code.

3 Likes

Just use WaitForChild if the target instance is one hierarchical level from the parent.
If it is multiple levels below, please use this WaitForPath implementation @ChipioIndustries made for everyone:

1 Like

If you’d like a function to wait for many children, I made this and feel free to use it

function SharedUtils.WaitFor(par, list, timeout)
	local items = {}
	local start = tick()
	
	local function doList(par, list)
		for i,name in pairs(list) do
			if type(name) == "table" then
				local item = par:WaitForChild(i, math.max(.01, timeout - (tick() - start)))
				if not item then return nil end
				items[i] = item
				
				doList(item, name)
			elseif type(i) == "string" and type(name) == "string" then -- Rename
				local item = par:WaitForChild(i, math.max(.01, timeout - (tick() - start)))
				if not item then return nil end
				items[name] = item
			else
				local item = par:WaitForChild(name, math.max(.01, timeout - (tick() - start)))
				if not item then return nil end
				items[name] = item
			end
		end
		
		return items
	end
	
	
	return doList(par, list)
end

Example use:


local CharacterMembers = {
	"Humanoid",
	Head = {
		"face"
    },
    "HumanoidRootPart",
	"UpperTorso",
	LowerTorso={"Root"},
	"LeftUpperLeg",
	"RightUpperLeg",
	"LeftLowerLeg",
	"RightLowerLeg",
	"LeftUpperArm","LeftLowerArm","RightUpperArm","RightLowerArm","LeftHand","RightHand","LeftFoot","RightFoot"
}

local CharacterChildren = SharedUtils.WaitFor(character,CharacterMembers,5)
local face = CharacterChildren.face -- works
local leftlowerleg = CharacterChildren.LeftLowerLeg -- also works

However, keep in mind that you don’t need to use WaitForChild if:

  • A: You’re simply accessing something from ReplicatedStorage which exists at the start of the game(isn’t instanced by the server) - you only need to wait for things that are created by the server after the game starts, or things in the workspace

  • B: You can access client sided things by cloning them from ReplicatedStorage instead

What I mean by point B is, for the player gui for instance, I usually have a custom “StarterGui” folder in ReplicatedStorage. The client just clones assets from there into their playergui directly, so they don’t have to wait on anything being instanced from the server

6 Likes

I wrote this module just now because Roblox is having loading issues client-side and it broke my game because I hate having to write WaitForChild. Roblox should implement a safe-get that doesn’t require tons of typing.

function(parent, path)
	for name in string.gmatch(path, "[^%.]+") do
		parent = parent:WaitForChild(name)
	end
	return parent
end

In another script, require and call it like module(game, "Workspace.Model.Part"). Quick and easy. It’s basically WaitForPath but a three-liner. Probably better to use WaitForPath, or if you care about performance, build your own barebones implementation based on the code.

Edit: I just upgraded to support multiple paths in one go if you pass an array of strings as an optional third parameter.

return function(parent, path, subpaths)
	local segments = string.split(path, ".")
	for i = 1, #segments do
		parent = parent:WaitForChild(segments[i])
	end
	if subpaths then
		local results = {}
		for i = 1, #subpaths do
			local _segments = string.split(subpaths[i], ".")
			local _parent = parent
			for i = 1, #_segments do
				_parent = _parent:WaitForChild(_segments[i])
			end
			table.insert(results, _parent)
		end
		return table.unpack(results)
	else
		return parent
	end
end

Then you can use like map, cutscenes, maps = module(game, "ServerStorage", {"Chat", "Cutscenes", "Maps"})

1 Like

@Crazyman32 Has a video talking about this:

6 Likes

I’m happy to say I don’t use WaitForChild (or anything like it) in my entire game. My game has one Script and one LocalScript so I have complete control over what gets created and when code runs. All of my gui’s and characters are created using scripts too, which certainly helps.

3 Likes

Wow, this blew up. Thank you for all these possible solutions!

For now, I’ll just use @TheCarbyneUniverse and @beecivil 's solutions of putting every sound into a folder, because I won’t have too much to load anyway. As for the Gui, I’ll keep all the WaitForChild functions, and maybe consider one of the other solutions

2 Likes

Does waiting for an instance also guarantee that every descendant has loaded, not just children?

1 Like

In my experience, sometimes yes but I think that was just luck. Most times no.

Really, you can utilize DataModel:IsLoaded and the DataModel.Loaded event to Wait at the top of a file to sanely check if everything has replicated. Even this has its own holes though.

For most, I’d recommend using an IDE like Visual Studio Code, tooling like Rojo, and just using powerful tools to make WaitForChild chains less painful.

1 Like

Be warned, single scripts is a huge issue with refactoring and modularity. I’d advise you revisit your design paradigm if this truly is the case.

The script is no more than a few lines that starts up the framework. It’s all ModuleScripts. Using one script ensures that things are completely initialized before code runs, hence no WaitForChild needed.

1 Like