Tycoon Dropper Effect/Animation Seems to Cause Lag in Studio; How to Optimize?

Currently remaking this game of mine. I have this dropper effect scripted and programmed:

It looks nice, and I have the effects running on the client, but a player will have close to 40 droppers going at once within their base, and it seems to cause noticeable lag/stuttering in studio, as you can see below:

As you can see, the animation for the droppers isn’t as smooth as they normally would be if there was only a few at a time, and you can see the client window freeze up for a fraction of a second when the droppers are animated.

I used this model tweening system as a basis for what I use for the droppers:

function module.dropperEffect(Model)
	local Tweeninfo = TweenInfo.new(0.1,Enum.EasingStyle.Circular,Enum.EasingDirection.Out,0,true)
	local Size = 1.1

	if typeof(Model) ~= "Instance" then error(Model.." isnt a instance") end
	if not Model:IsA("Model") then error(Model.Name.." isnt a model") end
	if not Model.PrimaryPart then Model.PrimaryPart = Model:FindFirstChildWhichIsA("BasePart") end

	task.spawn(function()
		local Primary = Model.PrimaryPart
		local AnchorState = Primary.Anchored

		local TW = TS:Create(Primary,Tweeninfo,{Size = Primary.Size * Size})

		for _,v in pairs(Model:GetDescendants()) do
			if v:IsA("BasePart") and v ~= Primary then
				local state = v.Anchored
				v.Anchored = true
				local T = TS:Create(v,Tweeninfo,{Size = v.Size * Size;Position = v.Position + (CalculatePosition(Primary,v) * (Size-1))})

				task.spawn(function()
					TW:GetPropertyChangedSignal("PlaybackState"):Wait()
					if v:FindFirstChildWhichIsA("SpecialMesh") then
						local mesh = v:FindFirstChildWhichIsA("SpecialMesh")
						TS:Create(mesh,Tweeninfo,{Scale = mesh.Scale * Size}):Play()
					end
					T:Play()
					v.Anchored = state
				end)
			end

			if v:IsA("Weld") or v:IsA("WeldConstraint") or v:IsA("ManualWeld") or v:IsA("Motor6D") then
				if v.Name ~= "TweenWeld" then
					v.Enabled = false
					task.spawn(function()
						TW.Completed:Wait()
						v.Enabled = true
					end)
				end
			end
		end

		TW:Play()
		TW.Completed:Wait()

		Primary.Anchored = AnchorState
	end)

	return
end

That function is called when a client event is fired onto the client:

clients.dropperAnim.OnClientEvent:Connect(function(dropper:Model,value:number,name:string)
	if dropper then
		local dropPoint:BasePart = dropper:FindFirstChild("dropPoint")
		local infoCashUI = script.info:Clone()
		if not dropPoint then return end
		local actualModel:Model = dropper:FindFirstChild("effects")
		if actualModel then
			tweenModule.dropperEffect(actualModel)
		else
			tweenModule.dropperEffect(dropper)
		end
		local choice =  blocks:FindFirstChild(dropper:GetAttribute("dropType") or "crate")
		local dropped:BasePart 
		if blocks:FindFirstChild(dropper:GetAttribute("dropType") or "crate") then
			dropped = choice:Clone()
		else
			dropped = Instance.new("Part")
			dropped.Size = Vector3.new(1,1,1)
			dropped.CollisionGroup = "dropper"
		end
		infoCashUI.price.Text = "$"..tostring(value)
		infoCashUI.Parent = dropped
		infoCashUI.Adornee = dropped
		dropped.Parent = workspace
		dropped.CFrame = dropPoint.CFrame -- + (-dropPoint.CFrame.RightVector * -Vector3.new(0, 0, 1))
		--garbage:AddItem(dropped,10)
		local gimme:RBXScriptConnection = nil
		local u1,u2,u3
		gimme = dropped.Touched:Connect(function(otherPart:BasePart)
--			print(tostring(otherPart.BrickColor))
--			print(tostring(otherPart.Parent))
--			print(tostring(string.match("Upgrader",otherPart.Parent.Name)))
			if otherPart.BrickColor == BrickColor.new("Ghost grey") and otherPart.Parent and string.match(otherPart.Parent.Name,"Upgrader") then
				if otherPart.Parent:GetAttribute("displayName") == "3rd Upgrader" then
					u3 = true
					value *= 2.5
				elseif otherPart.Parent:GetAttribute("displayName") == "2nd Upgrader" then
					u2 = true
					value *= 2
				else
					u1 = true
					value *= 1.5
				end
				infoCashUI.price.Text = "$"..tostring(value)
			elseif otherPart.Name == "collectionPoint" then
				gimme:Disconnect()
				gimme = nil
				local objVal = otherPart:FindFirstChildOfClass("ObjectValue")
				if objVal and objVal.Value then
					local firePoint:RemoteEvent = objVal.Value
					firePoint:FireServer(name,u1,u2,u3)
				end
				dropped:Destroy()
			end
		end)
		task.delay(10,function()
			if gimme then
				gimme:Disconnect()
				gimme = nil
			end
			if dropped then
				dropped:Destroy()
			end
		end)
	end
end)

How can I optimize this code? I’m also thinking about making a system that uses RayCasting to see if the player is even within view of a dropper when an animation is about to play - and if not, the animation is not played to fix the lag issue.

1 Like

move the tweening to client context script

1 Like

All code posted is on the client, whether it be a ModuleScript or a LocalScript. I indicated that in my post if you read it.

1 Like

dang my bad. somehow i misread it was fire from client to server

1 Like

may it be too many tweens because it seem to do create tween for all non-primary parts. is it possible to only tween the Model Scale property? (and adjust the model pivot so it scale from the pivot)

another thing would be to use Heartbeat or RenderStepped and manually change the CFrame or Scale as some say tweens are less performance

1 Like

It is not possible to tween only the scale property of a model (directly).

1 Like

ahh. forgot Model Scale cannot be tweened directly
i got a workaround if we still use tween

local dropper = script.Parent
local ScaleValue = dropper:WaitForChild("ScaleValue") :: NumberValue
local RemoteEvent = dropper:WaitForChild("RemoteEvent")

-- tween Scale of ScaleValue using TweenService
RemoteEvent.OnClientEvent:Connect(function()
	local tween = game:GetService("TweenService"):Create(ScaleValue, TweenInfo.new(0.5), {Value = 1.1})
	tween:Play()
	tween.Completed:Wait()
	tween:Destroy()
	
	tween = game:GetService("TweenService"):Create(ScaleValue, TweenInfo.new(0.5), {Value = 1})
	tween:Play()
	tween.Completed:Wait()
	tween:Destroy()
end)

-- when scale value changed, we call Model:ScaleTo
ScaleValue.Changed:Connect(function(value: number)
	dropper:ScaleTo(value)
end)

and of course we can connect to heartbeat or renderstepped to call ScaleTo manually

1 Like

This worked A LOT better actually, but there is still quite a large and noticeable slowdown - plus the tween is a little different looking.

Upon further inspection each dropper is ~140 parts; multiply that by 36 droppers and it’s an ugly number.

I’m going to try unionizing the bulk of the parts and see if that fixes things; if not, I will probably have to talk to my modeler and tell him to re-model the droppers to use a less absurd amount of parts for the droppers.

2 Likes

It was indeed about the part count. Got the part count down to 9 (instead of 140!) aka ~330 parts being tweened at once instead of 5000+.

From what i see you could probably convert the whole model to a mesh by exporting it as an object, This way you could switch from model to part tweening, only downside is that if you want to change individual parts, such as change their color or material, or adding new parts would require to export the dropper again

I did so by unionizing a bunch of stuff, and it worked out pretty well.