Animations or TweenService

its the main part of my game
( ✦ ) Brick Inc. :brick: - Roblox
check it out if you want to
also @Tomi1231
microprofile-20250304-133504.txt (9.0 MB)

1 Like

Okay so what weve learned through this post so far is that the TweenService shouldnt play a major role in causing lag when used normally. In this case however, were spamming it.

Let me adjust my question according to this turn:
Can a Tween (Instance) be re-used? This way, TS:Create() wouldn’t spam new instances every few milli-seconds.

Are the parts anchored???

1 Like

yep they are, since i didnt want to use the engine’s physics

Why do you have:

do

end

at the childadded part?

1 Like

I honestly dont know why.

There was a time where I was having issues with memory leaks and I think thats what I tried using to solve it.

This script is kinda old, you see.

Now I know that changes absolutely nothing cus its already in a smaller “scope”? Idk what the correct word for you-know-what is

(I removed this now)

ngl it’s probably the 200 new parts being created every seccond, and you can also instead of doing coroutines and stuff you can do task.spawn and you dont need to make a new local function animate each time just put animate.

Like I said, when I turn off the animations the game runs smoothly, I think the main issue is the TweenService, specifically the “Bounce” Easing style, I tried using Sine (and Linear), those seem to be running much more smoothly.

Also I can show you my “SpawnBrick Code”

local player = game:GetService("Players").LocalPlayer
local BricksFolder = game:GetService("Workspace"):WaitForChild("Bricks"):WaitForChild(player.Name)
local ClientBricks = game:GetService("ReplicatedStorage"):WaitForChild("ClientBricks")
local Enabled = player:WaitForChild("Settings"):WaitForChild("Bricks")
local ClientBricksWSFolder = workspace:WaitForChild("ClientBricks")
local random = math.random
-- Function --

local ServerBricksTable = {}
local ClientBrickTable = {}

local function findIndex(table, value)
	for index, valueB in table do
		if value == valueB then	
			return index
		end
	end
end

-- Get a random character

local function showBrick(Brick: Instance)
	
	task.desynchronize()
	
	if not Brick:IsA("CFrameValue") then return end
	if Brick == nil then return end
	
	local Identifier = random(0, 1000000000)
	
	ServerBricksTable[Identifier] = Brick
	
	task.synchronize()
	
	local ClientBrick = ClientBricks[Brick.Name]:Clone()
	ClientBrickTable[Identifier] = ClientBrick
	ClientBrick:PivotTo(Brick.Value)
	ClientBrick.Transparency = 0
	ClientBrick.Parent = ClientBricksWSFolder

end

-- Removing Bricks --


local function RemoveBrick(Brick: Instance)
	
	task.desynchronize()
	
	if not Brick:IsA("CFrameValue") then return end
	
	local Identifier = findIndex(ServerBricksTable, Brick)
	
	task.synchronize()
	ClientBrickTable[Identifier]:Destroy()
	ClientBrickTable[Identifier] = nil
	ServerBricksTable[Identifier] = nil
	
end

-- Handling the connection --

local Connection = BricksFolder.DescendantAdded:Connect(showBrick)
local Connection2 = BricksFolder.DescendantRemoving:Connect(RemoveBrick)

while true do task.wait(2)
	
	if Enabled.Value == true then
		
		local PastConnection = Connection
		local PastConnection2 = Connection2
		
		Connection = BricksFolder.DescendantAdded:Connect(showBrick)
		Connection2 = BricksFolder.DescendantRemoving:Connect(RemoveBrick)
		
		PastConnection:Disconnect()
		PastConnection2:Disconnect()

	end
	
	if Enabled.Value == false then

		Connection:Disconnect()
		Connection2:Disconnect()
		for _, Brick in ClientBrickTable do
			Brick:Destroy()
		end
		for _, Brick in ServerBricksTable do
			Brick:Destroy()
		end
		ServerBricksTable = {}
		ClientBrickTable = {}
		
	end
	
end

Also ignore the comments, I just realised i forgot to remove some of them from when I was testing some functions

GPT optimized the script here

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
local Workspace = game:GetService("Workspace")

local player = Players.LocalPlayer
local toggle: BoolValue = player:WaitForChild("Settings"):WaitForChild("BrickAnimations")

local bricksFolder = Workspace:WaitForChild("ClientBricks")

-- Predefine TweenInfos
local FallTweenInfo = TweenInfo.new(1.25, Enum.EasingStyle.Bounce, Enum.EasingDirection.Out)
local TransTweenInfo = TweenInfo.new(1, Enum.EasingStyle.Bounce, Enum.EasingDirection.Out)

local function AnimateChild(child: Instance)
    if not child:IsA("BasePart") then return end  -- Ensure it's a part

    child.Transparency = 1
    child.Orientation = Vector3.new(0, math.random(0, 360), 0)

    if child.Name == "BrickD" or child.Name == "BrickE" then
        child.Orientation = Vector3.new(math.random(-25, 55), math.random(0, 45), math.random(-25, 65))
    end

    -- Store original position to avoid multiple lookups
    local originalPosition = child.Position
    child.Position = originalPosition + Vector3.new(0, 40, 0)

    -- Use a single Tween call
    local Tween = TweenService:Create(child, FallTweenInfo, {
        Position = originalPosition,
        Transparency = 0
    })
    
    Tween:Play()
    
    -- Cleanup when finished
    Tween.Completed:Wait()
    Tween:Destroy()

    if child.Name == "BrickD" or child.Name == "BrickE" then
        child.Position = child.Position + Vector3.new(0, 2, 0)
    end
end

bricksFolder.ChildAdded:Connect(function(child: Instance)
    if toggle.Value then
        task.spawn(AnimateChild, child)  -- More efficient than coroutine
    else
        child.Transparency = 0
    end
end)
1 Like

bruh

I didnt reply immedietly cus I WAS ASKING THE GPT THE SAME THING :sob:

anyways thanks ima try that my gpt sucks

Wow the gpt actually cooked
the performance is much more stable now.
The biggest notable difference is that the gpt uses one tween instead of 2

Its still unplayably laggy but eh this’ll do for now :slight_smile:

That didn’t work either, and when trying to open the original html on my pc, it crashes my firefox tab

And this is with a browser that doesn’t crash

But umm, no need to provide a micro profiler dump anymore, as you’ve posted your game’s link and I can look at the micro profiler that way

1 Like

image

Cloning is indeed taking the most significant amount of time
You can avoid this by caching parts, and reusing them

image

Tweens do take a significant amount of time as well, but not much you can do about that, other than combining the transparency and position tween, as you’ve done

1 Like

Sorry for the late reply, I was really busy with school.

How would I go about caching the parts/reusing them?

Caching them would be simple, but how would I reuse them?
Do I teleport them below the map once collected and beam them back up once needed?
Would I set up an array of the parts where I keep track of which parts are in use and which arent?

I dont think that’d be the best method so I’ll have to rely on some input from you here

1 Like

It would basically involve either parenting the parts to nil, or teleporting them very far away (that might be more performant, not sure if it’s still the case). Then when you need to spawn a new part, rather than use cloning, you can one of those old part, change the material color and size, and move it to where the tween will start

Something like that would work

1 Like

Ah thanks, Ill try making a system like that then.

1 Like

The reparenting version works, but still causes lag as reparenting a thousand times per second isnt exactly efficient either.

Repositioning works, BUT:
The tweens cant be overwritten, which means that I cant reposition the bricks correctly as my scripts have to wait for the tweens to finish first.

Would it maybe be a good idea to put the two scripts into a common actor so that I can send shared tables of the TweenInstances around?

1 Like

I ended up merging the two scripts since i didnt get shared tables :slight_smile:

This works beautifully :slight_smile:
Thanks so much

1 Like

In theory you should not have to wait for the tween to end, as you should be using inactive parts

Tweening is not allowed in parallel, so you wont get any benefits from running it in parallel. In fact you’ll get worse performance because shared tables and other manners of sharing data across actors is slow. Currently, parallel lua on roblox can only really be applied to a very limited amount of tasks

Glad you eventually got something that’s more optimized :P

1 Like

thats what Im doing. The problem is that the tweens are extremely dominant.
Meaning, if I teleport a brick very far away while a tween is playing, it’ll only get teleported there for a split second until it gets beamed to the next position of the tween. Same goes for transparency, so i cant just hide it.

Im not using parallel luau for my current solution at ALL, but my scripts are still parented to an actor. Could this also cause issues?

I’ll also attach my code here, if you’re interested.

local player = game:GetService("Players").LocalPlayer
local BricksFolder = game:GetService("Workspace"):WaitForChild("Bricks"):WaitForChild(player.Name)
local ClientBricks = game:GetService("ReplicatedStorage"):WaitForChild("ClientBricks")
local Enabled = player:WaitForChild("Settings"):WaitForChild("Bricks")
local ClientBricksWSFolder = workspace:WaitForChild("ClientBricks")
local random = math.random
local BrickEEffect = game:GetService("ReplicatedStorage"):WaitForChild("ClientBricks"):WaitForChild("BrickE"):WaitForChild("ParticleEmitter")
local BrickA = game:GetService("ReplicatedStorage"):WaitForChild("ClientBricks"):WaitForChild("BrickA")
local BrickB = game:GetService("ReplicatedStorage"):WaitForChild("ClientBricks"):WaitForChild("BrickB")
local BrickC = game:GetService("ReplicatedStorage"):WaitForChild("ClientBricks"):WaitForChild("BrickC")
local BrickD = game:GetService("ReplicatedStorage"):WaitForChild("ClientBricks"):WaitForChild("BrickD")
local BrickE = game:GetService("ReplicatedStorage"):WaitForChild("ClientBricks"):WaitForChild("BrickE")
local depositCFrame = CFrame.new(Vector3.new(9999999, -9999999, 9999999), Vector3.new(0, 0, -1))
local TweenService = game:GetService("TweenService")
local toggle: BoolValue = player:WaitForChild("Settings"):WaitForChild("BrickAnimations")

-- Animating --
local tweens = {}

local bricksFolder = workspace:WaitForChild("ClientBricks")

local FallTweenInfo = TweenInfo.new(1.25, Enum.EasingStyle.Bounce, Enum.EasingDirection.Out)
local TransTweenInfo = TweenInfo.new(1, Enum.EasingStyle.Bounce, Enum.EasingDirection.Out)


local function AnimateChild(child: Instance)
	if not child:IsA("BasePart") then return end 

	child.Transparency = 1
	child.Orientation = Vector3.new(0, random(0, 360), 0)

	if child.Name == "BrickD" or child.Name == "BrickE" then
		child.Orientation = Vector3.new(random(-25, 55), random(0, 45), random(-25, 65))
	end

	local originalPosition = child.Position
	child.Position = originalPosition + Vector3.new(0, 40, 0)

	local Tween = TweenService:Create(child, FallTweenInfo, {
		Position = originalPosition,
		Transparency = 0
	})
	tweens[#tweens+1] = {child, Tween}
	Tween:Play()

	if Tween then Tween:Destroy() end

	if child.Name == "BrickD" or child.Name == "BrickE" then
		child.Position = child.Position + Vector3.new(0, 2, 0)
	end
end

local function animateBrick(child)
	if toggle.Value then
		task.spawn(AnimateChild, child)
	else
		child.Transparency = 0
	end
end

-- Functions --

local ServerBricksTable = {}
local ClientBrickTable = {}
local ClientBrickAReserve = {}
local ClientBrickBReserve = {}
local ClientBrickCReserve = {}
local ClientBrickDReserve = {}
local ClientBrickEReserve = {}

local function destroy(Brick: Part)
	if Brick == nil then return end
	for index, brickAndTween in tweens do
		if brickAndTween[1] == Brick then
			brickAndTween[2]:Cancel()
			brickAndTween[2]:Destroy()
			Brick.CFrame = depositCFrame
			table.remove(tweens, index)
			return
		end
	end
	Brick.CFrame = depositCFrame
end

local function registerBrickReserves()
	for counter = 1, 900, 1 do
		local Clone = BrickA:Clone()
		Clone.CFrame = depositCFrame
		Clone.Parent = ClientBricksWSFolder
		ClientBrickAReserve[#ClientBrickAReserve+1] = Clone
	end

	for counter = 1, 900, 1 do
		local Clone = BrickB:Clone()
		Clone.CFrame = depositCFrame
		Clone.Parent = ClientBricksWSFolder
		ClientBrickBReserve[#ClientBrickBReserve+1] = Clone
	end

	for counter = 1, 900, 1 do
		local Clone = BrickC:Clone()
		Clone.CFrame = depositCFrame
		Clone.Parent = ClientBricksWSFolder
		ClientBrickCReserve[#ClientBrickCReserve+1] = Clone
	end

	for counter = 1, 900, 1 do
		local Clone = BrickD:Clone()
		Clone.CFrame = depositCFrame
		Clone.Parent = ClientBricksWSFolder
		ClientBrickDReserve[#ClientBrickDReserve+1] = Clone
	end

	for counter = 1, 900, 1 do
		local Clone = BrickE:Clone()
		Clone.CFrame = depositCFrame
		Clone.Parent = ClientBricksWSFolder
		ClientBrickEReserve[#ClientBrickEReserve+1] = Clone
	end
end

registerBrickReserves()

local function findIndex(table, value)
	for index, valueB in table do
		if value == valueB then	
			return index
		end
	end
end

local function getFreeBrickOfType(Type: string)
	if Type == "A" then
		for _, Brick in ClientBrickAReserve do
			if Brick.CFrame.Position == depositCFrame.Position then
				return Brick
			end
		end
	elseif Type == "B" then
		for _, Brick in ClientBrickBReserve do
			--print(Brick.CFrame.Position)
			if Brick.CFrame.Position == depositCFrame.Position then
				return Brick
			end
		end
	elseif Type == "C" then
		for _, Brick in ClientBrickCReserve do
			if Brick.CFrame.Position == depositCFrame.Position then
				return Brick
			end
		end
	elseif Type == "D" then
		for _, Brick in ClientBrickDReserve do
			if Brick.CFrame.Position == depositCFrame.Position then
				return Brick
			end
		end
	elseif Type == "E" then
		for _, Brick in ClientBrickEReserve do
			if Brick.CFrame.Position == depositCFrame.Position then
				return Brick
			end
		end
	end
end
-- Get a random character

local function showBrick(Brick: Instance)
	
	if not Brick:IsA("CFrameValue") then return end
	if Brick == nil then return end
	
	local Identifier = random(0, 1000000000)
	
	ServerBricksTable[Identifier] = Brick
	
	local BrickType = string.gsub(Brick.Name, "Brick", "")
	
	local ClientBrick = getFreeBrickOfType(BrickType)
	ClientBrickTable[Identifier] = ClientBrick
	ClientBrick.CFrame = Brick.Value
	animateBrick(ClientBrick)

end

-- Removing Bricks --


local function RemoveBrick(Brick: Instance)	
	
	if not Brick:IsA("CFrameValue") then return end
	
	local Identifier = findIndex(ServerBricksTable, Brick)
	
	destroy(ClientBrickTable[Identifier])
	ClientBrickTable[Identifier] = nil
	ServerBricksTable[Identifier] = nil
	
end


-- Handling the connection --

local Connection = BricksFolder.DescendantAdded:Connect(showBrick)
local Connection2 = BricksFolder.DescendantRemoving:Connect(RemoveBrick)

while true do task.wait(2)
	
	if Enabled.Value == true then
		
		local PastConnection = Connection
		local PastConnection2 = Connection2
		
		Connection = BricksFolder.DescendantAdded:Connect(showBrick)
		Connection2 = BricksFolder.DescendantRemoving:Connect(RemoveBrick)
		
		PastConnection:Disconnect()
		PastConnection2:Disconnect()

	end
	
	if Enabled.Value == false then

		Connection:Disconnect()
		Connection2:Disconnect()
		for _, Brick in ClientBrickTable do
			destroy(Brick)
			Brick = nil
		end
		for _, Brick in ServerBricksTable do
			destroy(Brick)
			Brick = nil
		end
		ServerBricksTable = {}
		ClientBrickTable = {}
		
	end
	
end