The posts above me offer excellent methods to write smarter code! I’d also like to put in my two cents as well;
For systems with fairly simple logic but which manipulate a lot of instances like this crosswalk system (or more commonly, UI) I tend to go with CollectionService tags to keep track of instances without worrying about the explorer hierarchy. Since we’re already manipulating instances, using tags alongside attributes allows us to add our simple logic on-top of the comparatively complex instance without having to recreate a whole class around it (which is/was fairly common practice but in my opinion is unnecessary for simple logic.)
For your use case, that could look something like this.
local crossWalk = script.Parent
local crossWalkDescendants = crossWalk:GetDescendants()
local function findFirstChildWithTag(tag: string): (Instance | false)
for _, child: Instance in crossWalkDescendants do
if child:HasTag(tag) then
return child
end
end
return false
end
local crossWalkComponents = {
promptA = findFirstChildWithTag("CrossButtonPromptA"),
promptB = findFirstChildWithTag("CrossButtonPromptB"),
counterA = findFirstChildWithTag("CrosswalkCounterLabel"),
redA = findFirstChildWithTag("RedPedestrianGui"),
greenA = findFirstChildWithTag("GreenPedestrianGui"),
ledA = findFirstChildWithTag("CrosswalkLedA"),
soundA = findFirstChildWithTag("CrosswalkSoundA"),
-- For "nameX", you may want to cache the instance name elsewhere since all
-- of your other variables are fetching instances.
}
local function validateComponents()
for componentLabel, component in crossWalkComponents do
assert(component, `TrafficPole is missing required instance '{componentLabel}'; Path [{crossWalk:GetFullName()}]`)
end
end
validateComponents()
The downside of this approach is that your script will error when you try to use the variable rather than when you assign it, which in most cases leads to things breaking in worse and harder to find ways. That’s why we call the validateComponents
function on the last line!
You can use the same logic with Instance:FindFirstChild which has a second parameter that will search for all descendants of that name rather than just direct children. This is of course an option but be aware that this relies on instance names which means your logic is still directly tied to the hierarchy and you cannot have duplicate names, unlike with tags. This also comes with the drawbacks of both your original method and the tags but can allow you to write quicker, dirtier code if that’s what you need.
That could look something like this:
local crossWalk = script.Parent
local crossWalkComponents = {
-- The 2nd parameter is key! It lets us search all descendants.
promptA = crossWalk:FindFirstChild("CrossButtonPrompt", true),
promptB = crossWalk:FindFirstChild("CrossButtonPromptB", true),
-- In your original example these have their default names but you will need
-- to give them unique identifiers.
counterA = crossWalk:FindFirstChild("CounterTextLabel", true),
redA = crossWalk:FindFirstChild("RedPedImageLabel", true),
greenA = crossWalk:FindFirstChild("GreenPedImageLabel", true),
ledA = crossWalk:FindFirstChild("CrossLedA", true),
soundA = crossWalk:FindFirstChild("SoundA", true),
}
local function validateComponents()
for componentLabel, component in crossWalkComponents do
assert(component, `TrafficPole is missing required instance '{componentLabel}'; Path [{crossWalk:GetFullName()}]`)
end
end
validateComponents()
I hope this helped and I’d be glad to elaborate on anything you might be confused on. Cheers!