Best way to tween a group of instances?

I’m trying to tween a group of GUIs’ image transparency, and I’ve decided to use in pairs() to achieve that. However, when the script runs the tween, it tweens the Image Labels separately. Is there anyway to make in pairs() tween all instances at once, or is there a better option I should be using?

							for i, object in pairs(Enemy:GetChildren()) do
								if object.Name == "MalSkullClosed" or object.Name == "SpywareEye" or object.Name == "SpywarePupil" then
									print("First Tier")
									for i, v in pairs(object:GetChildren()) do
										if v.Name == "Closed" or v.Name == "EyeOpen" or v.Name == "Pupil" then
											print(v.Name)
											local TweenFadeOut = TweenService:Create(v, TweenInfo.new(1), {ImageTransparency = 1})
											TweenFadeOut:Play()
											task.wait(1.25)
											v.Parent:Destroy()
										end
									end
								end
							end
3 Likes

i guess you can add a delay to the start of the tween but i dont think there is any other better way

2 Likes

Would that not delay all the tweens it runs though?

2 Likes

LOL nvm the task.wait(1.25) is causing the for loop to lock up each run, which is in turn causing the cascade effect

2 Likes

good for you :+1:
(char limt: ssssssswdwdwddwqd)

An even easier way to manage multiple ui instances is by using a CanvasGroup. By making the group of ui instances children of the CanvasGroup, you can easily set the ‘GroupTransparency’ which affects all children. Without the need to use for loops.

2 Likes

ohh I see, is there any mesh alternative to CanvasGroup? Just for future reference

edit nvm i see it works for meshes as well thank you!

2 Likes

Yup! You can do all sorts of things to a bunch of instances with GroupCanvas. Takes the pain out of what you were trying to do lol.

1 Like

As a PSA for anyone who reads through here, although CanvasGroups could work for that use case, be cautious with how many elements are included in it, as well as how often you use CanvasGroups, as lower graphics settings could make the UI elements contained inside of it appear blurry (for reference, here’s a visual example of that).

Along with that, if the texture memory reaches its limit, the CanvasGroup might not display anything at all, according to the “Important Notes” section on its Roblox Creator Documentation site page:

  • CanvasGroup consumes extra texture memory. The quality of the texture and total memory usage is limited by the Enum.QualityLevel of the client. When exceeding the memory cap, CanvasGroup will render as a blank texture.

There are many examples of the limitations of CanvasGroups within the “CanvasGroup Beta: Group Transparency on UI Groups” thread (scroll through the posts after the graphics quality limitation was introduced to see examples that were posted after the limitation had gone into effect), so it would be wise to avoid using CanvasGroups for important UI elements, when possible. If it won’t be on-screen for long, however, then it might be a decent option.


With the implementation used in the original post, the code could be modified to run the tweens within a task.spawn() call so that it will create a separate thread each time a tween needs to be created for one of the GuiObjects.

This would allow for all of the tweens to play at the same time, as it would no longer be waiting for the task.wait() to conclude before moving on to the next iteration of the loop:

Example Revision

local FirstTierNames = {
	"MalSkullClosed",
	"SpywareEye",
	"SpywarePupil"	
}

local SecondTierNames = {
	"Closed",
	"EyeOpen",
	"Pupil"	
}

for i, object in Enemy:GetChildren() do
	if table.find(FirstTierNames, object.Name) then
		
		for i, v in object:GetChildren() do
			if table.find(SecondTierNames, v.Name) then
				
				task.spawn(function()
					local TweenFadeOut = TweenService:Create(
						v,
						TweenInfo.new(1),
						{ImageTransparency = 1}
					)
					
					TweenFadeOut:Play()
					TweenFadeOut.Completed:Wait()
					v.Parent:Destroy()
				end)
				
			end
		end
		
	end
end

Edit: Fixed typo from TweenFadeOut.Ended to TweenFadeOut.Completed (since Completed is the correct name of the event)

2 Likes

This is super comprehensive tysm! Just wondering, how does the table.find call operate? Is it able to work with any table, and what data can it access from the table and its children? As well as that, is task.spawn() creating seperate tweens at once? Or is it taking all instances that need to be played and playing them at once? Sorry if this is too much to ask, all the info was already more than enough :slight_smile:

2 Likes

table.find() requires two different things to be input:

  1. The table that it should look through
  2. The thing to look for (which in this case, is the name of one of the objects)

It only works with arrays (which are tables that have numerical indexes). An array could be compared to the pages of a book, in a way. For example, if you had a book with 3 pages, the index would be the number of the current page (1, 2, or 3), and its value would be the words on that page.

The two tables in the example from my previous post are both arrays; here’s what the “SecondTierNames” table would look like if you printed it out:

-- Array; works with table.find()
{
    [1] = "Closed",
    [2] = "EyeOpen",
    [3] = "Pupil"
}

However, if it was a dictionary, which consists of key / value pairs that may not be numbers, table.find() would not work. Here’s an example of what table.find() would not be able to look through:

-- Dictionary; does not work with table.find()
{
    ["ObjectName"] = "Closed",
    ["AnotherObjectName"] = "EyeOpen",
    ["FinalObjectName"] = "Pupil"
}

When we call table.find(SecondTierNames, v.Name) later on in the script, that goes through everything on the first layer of the table and checks if any of the values matches v.Name. If it does, the table.find() call will return the index / the position in the list that it was found at. So if v.Name was “EyeOpen”, the table.find() call would return “2”.

In this case, we only need to know if it found a match in the table, which is why the conditional statement is only checking if table.find(SecondTierNames, v.Name) returns a truthy value (which could be true, a string, a number, etc.) If it doesn’t find a match in the table, it will return nil, which would fail the condition.

It’s somewhat similar to how the original code worked, where it manually checked if v.Name equalled any of the specific names. If it didn’t, it would return false and then check the next name. The table.find() call essentially does the same thing, except it returns nil if it doesn’t find any match (with nil meaning “non-existent” / nothingness).


If you had a nested table, like the following, the table.find() call would work a bit differently:

local exampleTable = {

   [1] = {"Closed", "EyeOpen", "Pupil"}

}

If you used table.find(exampleTable, "ValueToLookFor"), it’d only be able to look through the first layer of the “exampleTable”. Since the value at index #1 is a table, table.find() would not return a match when looking for a string / the name of an object.

In order to search through the nested table, you would need to refer to it somehow. For this example, it could be modified to table.find(exampleTable[1], "ValueToLookFor"), as exampleTable[1] would reference the value that was stored at index #1 (which is the nested table) in order to look through that table in particular.


For a super simple explanation, task.spawn() is essentially creating a “new script” in the sense that each time it is called, it will run the code within it separately from everything else that is happening.

As an analogy, imagine that the loop represents the actions that a cashier is responsible for at the checkout line in a store. They have to scan every item on the conveyor belt (in this case, let’s say that’s equivalent to checking if the script can find the name of the given object in the table), and then after that, they have to place the item into a bag so it’s easy for the customer to take it with them (which for this example, will be equivalent to creating and playing the Tween for the Image’s transparency).

However, if the line is long and they need to get work done more quickly, they might need some help from a co-worker. So, they call someone over to help them with bagging the items so that it doesn’t take as long to help each customer. The task.spawn() call in the function could be equivalent to that situation, because the co-worker is able to start bagging items while the cashier is still scanning items. The customer no longer needs to wait for the cashier to finish placing the item into a bag before scanning the next one since their co-worker is able to help at the exact same time.

When looking back at the script, this means that when the code reaches the section of the loop where it knows that it needs to create a Tween for one of the GuiObjects, it runs the relevant code within the task.spawn() call to tell the game that it can do that at the same time. This way, the loop will be able to continue running, immediately looking through the rest of the items within the object.

Essentially:

  1. Script loops through the object
  2. As soon as it finds a match, task.spawn() is called
  3. The code within the task.spawn() call begins to run
  4. The script continues looping through the next objects while the Tween for the previous object plays

This happens so fast that it’ll look like all of the Tweens were created at the exact same moment in time, but it is still creating each Tween separately (on each iteration of the loop where there is a match) and it plays it immediately after creation (so it is not waiting until it has looped through every matching item before beginning to update the ImageTransparency).

I hope this makes sense!

No worries! I love explaining how stuff works so this is always fun :smile:

2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.